Linux内核里,绝大部分的设备都是字符设备,所写的驱动99%也都是处理字符设备。
一、字符设备驱动
字符设备的驱动大致步骤:
1、相应的设备初始化(根据开发板的原理图以及芯片datasheet配置引脚等)-->
2、注册字符设备主次设备号[register_chrdev_region()或alloc_chrdev_region()]-->
3、分配字符结构体[struct cdev *cdev_alloc(void)或者struct cdev led_cdev;]-->
4、初始化字符设备结构体,绑定fops到cdev结构体中[void cdev_init(struct cdev *cdev, struct file_operations *fops);]-->
5、绑定主次设备号,并注册给Linux内核[int cdev_add(struct cdev *p, dev_t dev, unsigned count);]
3、4、5、步骤字符设备的注册
相应的设备初始化会根据开发板需要驱动的模块原理图芯片datasheet的不同会改变,而后面的步骤一般都基本上一样,所以相应的设备初始化留到最后来说
2、注册字符设备主次设备号:
内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。
register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);这个函数是linux版本2.4之前的注册方式:
(1)确定一个主设备号,如果major=0,则会自动分配设备号
(2)构造一个file_operations结构体, 然后放在chrdevs数组中
(3)注册:register_chrdev,cat /proc/devices查看内核中已经注册过的字符设备驱动(和块设备驱动),注意这里并不是驱动文件设备节点!
静态设定主次设备号:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
第一个参数first,是你要分配的起始设备编号.first 的次编号部分常常是 0, 但不是强制的.
第二个参数count是你请求的连续设备编号的总数. 注意, 如果 count 太大, 你要求的范围可能溢出到下一个主设备号;但是只要你要求的编号范围可用, 一切都仍然会正确工作.
第三个name是应当连接到这个编号范围的设备的名字; 它会出现在 /proc/devices 和 sysfs 中.
如同大部分内核函数, 如果分配成功进行, register_chrdev_region 的返回值是 0. 出错的情况下, 返回一个负的错误码, 你不能存取请求的区域。
Linux系统中正在使用的主设备号会保存在 /proc/devices 文件中:
~ >: cat /proc/devices
编写驱动时可以选定一个未用的主设备号,如251来使用:
dev_t devno;
int result;
int major=251;
devno = MKDEV(major, 0);
result = register_chrdev_region (devno, 4, "led");
if (result < 0)
{
printk(KERN_ERR "LED driver can't use major %d\n", major);
return -ENODEV;
}
MKDEV(ma,mi)
#define MKDEV(ma,mi) ((ma)<<8 | (mi))
所以MKDEV(5, 0),最后得到的值为【5*(2的8次幂)+0】,等于1280。
dev_t
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
当驱动的主、次设备号申请成功后,/proc/devices里将会出现该设备,但是/dev 路径下并不会创建该设备文件。需要创建该文件,则需要使用mknod命令创建。当然我们也可以在驱动里调用相应的函数,来通知应用程序空间自动创建该设备文件。
~ >: mknod --help
BusyBox v1.27.1 (2019-05-01 20:12:50 CST) multi-call binary.
Usage: mknod [-m MODE] NAME TYPE MAJOR MINOR
Create a special file (block, character, or pipe)
-m MODE Creation mode (default a=rw)
TYPE:
b Block device
c or u Character device
p Named pipe (MAJOR and MINOR are ignored)
~ >: mknod -m 755 /dev/led0 c 251 0
~ >: mknod -m 755 /dev/led1 c 251 1
~ >: mknod -m 755 /dev/led2 c 251 2
~ >: mknod -m 755 /dev/led3 c 251 3
动态申请主次设备号:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
第一个参数:dev是一个只输出的参数, 获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,将主设备号和次设备号,提取打印出来,看是自动分配的是多少.
第二个参数 fisetminor 应当是请求的第一个要用的次编号; 它常常是0.
count 和 name 参数如同给 request_chrdev_region 的一样。
使用动态申请主次设备号示例:
int result;
int dev_major
dev_t devno;
result = alloc_chrdev_region(&devno, 0, 4, "led");
dev_major = MAJOR(devno);
/* Alloc for device major failure */
if (result < 0)
{
printk(KERN_ERR "led driver allocate device major number failure: %d\n",result);
return -ENODEV;
}
printk(KERN_ERR "led driver choose device major number: %d\n", dev_major);
/* MAJOR 和 MINOR
#define MAJOR(dev) ((dev)>>8)
#define MINOR(dev) ((dev) & 0xff) */
如果使用alloc_chrdev_region()函数来动态分配主设备号,那我们驱动在安装后并不知道这个主设备号是多少,那么只能通过 查看 /proc/devices 文件才能知道它的值,然后再创建设备节点(mknod)。
释放主次设备号
在Linux内核中,主、次设备号是一种资源,不管你使用何种方式分配的设备编号, 你应当在不再使用它们时释放它. 设备编号的释放使用下面函数:
void unregister_chrdev_region(dev_t first, unsigned int count);