@TOCnode
撰寫日期 | 2018.12.08 |
---|---|
完稿日期 | 2019.10.06 |
最近維護 | 暫無 |
本文做者 | multimicro |
聯繫方式 | multimicro@qq.com |
GitHub | https://github.com/wifialan |
本文地址 | https://blog.csdn.net/multimicro/article/details/84898135 |
環境說明 | 詳細信息 | 備註信息 |
---|---|---|
操做系統 | Ubunut 18.04.3 LTS | |
開發板 | S3C2440(JZ2440-V3) | |
kernel版本 | linux-3.4.2 | 官網地址 |
busybox版本 | busybox-1.22.1 | 官網地址 |
編譯器 | arm-linux-gcc-4.4.3 | 下載地址 |
編譯器路徑 | /opt/FriendlyARM/toolschain/4.4.3/bin | 絕對路徑 |
引自宋寶華《Linux設備驅動開發詳解--基於最新的Linux 4.0內核》P138內容:linux
在Linux中,字符設備驅動由以下幾個部分組成。 1. 字符設備驅動模塊加載與卸載函數 2. 字符設備驅動的file_operations 結構體中的成員函數git
這裏先介紹一下字符設備的開發流程:字符設備驅動是經過設備號 與上位機程序鏈接。而上位機程序對驅動的控制則是經過文件操做,即read、write、ioctl
等完成。github
/proc/devices
裏面添加驅動節點號信息)所以一個字符設備驅動應包含1. 設備號的註冊、卸載
和2. 文件操做
兩個功能,註冊的設備號用於提供接口,而文件操做用於對驅動的操做。redis
字符設備驅動的結構以下圖所示: 對於cdev_init
函數中,創建file_operations
之間的鏈接的疑問,看一下cdev_init
的實現函數
void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }
能夠看出,最後的一個語句cdev->ops = fops;
完成了在cdev中的file_operations
的綁定 下面從程序語言角度感性的認識一下設備號的註冊、卸載函數原型,和文件操做函數原型。ui
//加載函數 static int __init xxx_init(void) { ... ... } //卸載函數 static int __exit xxx_exit(void) { ... ... }
static const struct file_operations xxx_fileops = { .owner = THIS_MODULE, .write = xxx_write, .read = xxx_read, .open = xxx_open, .unlocked_ioctl = xxx_ioctl, ... ... }; static int xxx_open( struct inode *inodes, struct file *filp ) { ... ... } static long xxx_ioctl( struct file *file, unsigned int cmd, unsigned long arg ) { ... ... } static ssize_t xxx_write( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos ) { ... ... } static ssize_t xxx_read( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos ) { ... ... }
以我寫的字符設備驅動源代碼爲例,路徑爲linux-3.4.2\drivers\char\s3c2440_leds.c
,文章後附有完整的代碼 設備號的註冊由static int __init s3c2440_leds_init(void)
完成 設備號的卸載由static int __init s3c2440_leds_exit(void)
完成 首先分析設備號的註冊,而後分析卸載this
設備號分爲主設備號和次設備號,若源代碼中定義了主設備號(次設備號通常爲0),那麼能夠直接完成設備號的註冊,其流程爲 註冊成功後,可經過cat /proc/devices
命令查看設備號 spa
相比設備號的註冊,註銷流程就十分簡單: 操作系統
上位機程序首先要調用open
函數打開此驅動,具體方法就是,打開該設備號對應的文件,通常而言,該設備號文件在/dev/
文件夾下,驅動在內核中註冊成功後會在/proc/devices
中包含設備號信息,但/dev/
文件夾內並無建立該設備號對應的文件,所以須要手動建立該設備號文件,命令爲:
mknod /dev/leds c 230 0
表示在/dev
文件夾下建立名爲leds
的字符設備文件,其主設備號爲230,次設備號爲0。 字符設備文件名能夠另取,但設備號必定要對應/proc/devices
裏面的設備號。
而後經過fd = open("/dev/leds",0);
完成設備驅動的打開
當上位機程序經過調用open
函數打開(連接上)相應的驅動程序後,open
函數會返回一個==文件描述符==暫且記爲fd
,而後對該驅動的read、write、ioctl
等操做均可以經過使用fd
完成。簡單的字符設備驅動程序大多采用ioctl
函數控制驅動程序,而這個ioctl
函數自己也不難,其實現爲:
static long s3c2440_leds_ioctl( struct file *file, unsigned int cmd, unsigned long arg )
函數中 第一個參數:表示要操做的文件描述符 第二個參數:表示傳遞的命令字 第三個參數:表示傳遞的變量字 第二個參數和第三個參數的含義沒有硬性規定,傳遞的參數符合對應的關鍵字限定類型便可
下面的給出示例參考
static long s3c2440_leds_ioctl( struct file *file, unsigned int cmd, unsigned long arg ) { printk(DRV_NAME "\tRecv cmd: %u\n", cmd); printk(DRV_NAME "\tRecv arg: %lu\n", arg); //IO operations function. if(arg > 4) { return -EINVAL; } switch (cmd) { case IOCTL_LED_ON: //#define IOCTL_LED_ON 1 s3c2410_gpio_setpin(S3C2410_GPF(arg+3), 0);//Set pin printk("Open LED %lu ",arg); return 0; case IOCTL_LED_OFF: //#define IOCTL_LED_OFF 0 s3c2410_gpio_setpin(S3C2410_GPF(arg+3), 1); printk("Close LED %lu ",arg); return 0; default: return -EINVAL; } }
1. 宋寶華《Linux設備驅動開發詳解–基於最新的Linux 4.0內核》 第6章 字符設備驅動 2. Jonathan Corbet《linux設備驅動程序第三版》 P50-P51
/* * Driver for S3C2440 base board. * * Copyright (C) 2019 Alan NWPU <alantian.at@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * MULTIBEANS, NPU Youyi West Ave, Beilin District, Xi'an, China. */ #include <linux/module.h> /* Every Linux kernel module must include this head */ #include <linux/init.h> /* Every Linux kernel module must include this head */ #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* struct fops */ #include <linux/errno.h> /* error codes */ #include <linux/cdev.h> /* cdev_alloc() */ #include <linux/ioport.h> /* request_mem_region() */ #include <linux/delay.h> #include <linux/moduleparam.h> #include <linux/types.h> #include <linux/gpio.h> #include <linux/device.h> #include <linux/types.h> #include <linux/miscdevice.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <asm/irq.h> #include <mach/gpio-nrs.h> #include <mach/gpio.h> #include <mach/hardware.h> #include <plat/gpio-cfg.h> #define DRV_NAME "s3c2440_leds" #define DRV_AUTHOR "Alan Tian <alantian.at@gmail.com>" #define DRV_DESC "S3C2440 LED Pin Driver" #define S3C2440_LED_SIZE 0x1000 #define S3C2440_LED_MAJOR 230 #define S3C2440_LED_MINOR 0 static int major = S3C2440_LED_MAJOR; static int minor = S3C2440_LED_MINOR; /* 應用程序執行ioctl(fd, cmd, arg)時的第2個參數 */ #define IOCTL_LED_ON 0 #define IOCTL_LED_OFF 1 static int s3c2440_leds_open( struct inode *inodes, struct file *filp ); static long s3c2440_leds_ioctl( struct file *file, unsigned int cmd, unsigned long arg ); static ssize_t s3c2440_leds_write( struct file *filp, const char __user *buffer, \ size_t size, loff_t *f_pos ); static ssize_t s3c2440_leds_read( struct file *filp, const char __user *buffer, \ size_t size, loff_t *f_pos ); struct s3c2440_leds_dev_t { struct cdev cdev; unsigned char mem[S3C2440_LED_SIZE]; } *s3c2440_leds_dev; //Step 2: Add file operations static const struct file_operations s3c2440_leds_fileops = { .owner = THIS_MODULE, .write = s3c2440_leds_write, .read = s3c2440_leds_read, .open = s3c2440_leds_open, .unlocked_ioctl = s3c2440_leds_ioctl, }; static int s3c2440_leds_open( struct inode *inodes, struct file *filp ) { //int ret; filp->private_data = s3c2440_leds_dev; printk(DRV_NAME"\tS3C2440 open function...\n"); return 0; } static long s3c2440_leds_ioctl( struct file *file, unsigned int cmd, unsigned long arg ) { printk(DRV_NAME "\tRecv cmd: %u\n", cmd); printk(DRV_NAME "\tRecv arg: %lu\n", arg); //IO operations function. if(arg > 4) { return -EINVAL; } switch (cmd) { case IOCTL_LED_ON: s3c2410_gpio_setpin(S3C2410_GPF(arg+3), 0);//Set pin printk("Open LED %lu ",arg); return 0; case IOCTL_LED_OFF: s3c2410_gpio_setpin(S3C2410_GPF(arg+3), 1); printk("Close LED %lu ",arg); return 0; default: return -EINVAL; } } static ssize_t s3c2440_leds_write( struct file *filp, const char __user *buffer, \ size_t size, loff_t *f_pos ) { unsigned long p = *f_pos; unsigned int count = size; int ret = 0; struct s3c2440_leds_dev_t *dev = filp->private_data; if(p >= S3C2440_LED_SIZE) return 0; if(count > S3C2440_LED_SIZE - p) count = S3C2440_LED_SIZE - p; memset(dev->mem, 0, S3C2440_LED_SIZE); if(copy_from_user(dev->mem + p, buffer, count)) { ret = -EFAULT; } else { *f_pos += count; ret = count; printk(KERN_INFO "writter %u bytes(s) from %lu\n", count, p); } return ret; } static ssize_t s3c2440_leds_read( struct file *filp, const char __user *buffer, \ size_t size, loff_t *f_pos ) { unsigned long p = *f_pos; unsigned int count = size; int ret = 0; struct s3c2440_leds_dev_t *dev = filp->private_data; if(p >= S3C2440_LED_SIZE) return 0; if(count > S3C2440_LED_SIZE - p) count = S3C2440_LED_SIZE - p; if(copy_to_user(buffer, dev->mem + p, count)) { ret = -EFAULT; } else { *f_pos += count; ret = count; printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p); } return ret; } static int __init s3c2440_leds_init(void) { int ret,err; dev_t devid; if(major) { devid = MKDEV(major, 0); ret = register_chrdev_region(devid, 1, DRV_NAME); printk("Origin Creat node %d\n",major); } else { ret = alloc_chrdev_region(&devid, 0, 1, DRV_NAME); major = MAJOR(devid); printk("Arrage1 Creat node %d\n",major); } if(ret < 0) { printk(DRV_NAME "\ts3c2440 new device failed\n"); //goto fail_malloc; return ret; } s3c2440_leds_dev = kzalloc(sizeof(struct s3c2440_leds_dev_t), GFP_KERNEL); if(!s3c2440_leds_dev) { ret = -ENOMEM; goto fail_malloc; } printk("success init leds\n"); cdev_init(&s3c2440_leds_dev->cdev, &s3c2440_leds_fileops); err = cdev_add(&s3c2440_leds_dev->cdev, devid, 1); if(err) printk(KERN_NOTICE "Error %d adding s2c2440_leds %d",err, 1); return 0; fail_malloc: unregister_chrdev_region(devid, 1); return ret; } static void __exit s3c2440_leds_exit(void) { printk("Starting delet node %d\n",major); cdev_del(&s3c2440_leds_dev->cdev); kfree(s3c2440_leds_dev); unregister_chrdev_region(MKDEV(major, minor), 1); printk("Delete node %d\n",major); } module_init(s3c2440_leds_init); module_exit(s3c2440_leds_exit); MODULE_AUTHOR(DRV_AUTHOR); MODULE_DESCRIPTION(DRV_DESC); MODULE_LICENSE("GPL");