通过platform实现LED设备驱动一些基础知识

首先要知道一个字符设备驱动的流程为:分配file_operations结构体,设置file_operations结构体,注册file_operations结构体。具体来说就是:1.确定一个主设备号,2.定义自己的file_operations结构体,3.实现file_operations结构体中的open/wirte/read函数,4.把file_operations结构体告诉内核,即注册file_operations结构体,5.实现入口函数,在这个入口函数中注册file_operations结构体。

首先要区分:设备号(devid),主设备号(major),次设备号(minor)

主设备号:用于标识不同的驱动程序

次设备号:用于区分同一主设备号下的不同设备实例。

设备号:内核设备的唯一标识符。是主设备号和次设备号的组合,通过MKDEV(major,minor)宏生成。

三者之间的关系:

  1. 设备号devid通过devid=MKDEV(marjor,minor)获得
  2. 主设备号major通过marjor=MAJOR(devid)获得
  3. 次设备号minor通过minor=MINOR(devid)获得

举例1:动态分配设备号

  1. 通过alloc_chrdev_region获得设备号,使用MAJOR和MINOR得到主设备号major和次设备号MINOR
  2. class_create会在/sys/class/下创建一个名为“GPIO_NAME”的类
  3. 根据设备号devid,通过device_create()创建GPIOLED_NAME设备 。会在/sys/class/GPIO_NAME的类下面再创建一个名为GPIO_NAME的设备节点。同样该节点在/dev/GPIO_NAME中,应用程序可以open打开这个设备
1
2
3
4
5
6
7
8
9

alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
...
/* 4、创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
/* 5,创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);

举例2:分配设备号

  1. 通过register_chrdev注册lfile_operation结构体,得到主设备号
  2. 再使用MKDEV(major,i)得到设备号devid,根据devid创建100ask_led0等设备
1
2
3
4
major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/100ask_led */
led_class = class_create(THIS_MODULE, "100ask_led_class");
for (i = 0; i < LED_NUM; i++)
device_create(led_class, NULL, MKDEV(major, i), NULL, "100ask_led%d", i); /* /dev/100ask_led0,1,... */

再来看下如何分配设备号:

设备号的分配可以分为动态分配和静态分配。

静态分配函数原型:

1
static inline int regiser_chrdev(unsigned int major,const char *name,const struct file_operations* fops)
使用示例:
1
major = register_chrdev(0, "100ask_led", &led_drv);
参数:
  • major:主设备号,一般为0,让系统自动分配一个未使用的主设备号
  • name:名字,可以通过cat /proc/devices查看
  • fops:需要注册的file_operations结构体

返回值:成功则返回主设备号

动态分配函数原型:

1
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
使用示例:
1
alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
参数:
  • dev:用于保存申请到的设备号,dev_t为unsigned int 类型数据
  • baseminor:次设备号的起始地址,一般为0表示次设备号从0开始
  • count:需要申请的设备号数量,一般为1
  • name:设备名字,同register_chrdev一样,可以在/proc/devices查看
返回值:
  • 成功返回

如果给定了设备的主设备号和次设备号,使用register_chrdev_region

函数原型:
1
int register_chrdev_region(dev_t from, unsigned count, const char *name)

动态分配设备号下注册字符设备驱动

静态分配不仅分配了设备号,还直接完成了字符设备file_operations的注册,但是会占据主设备号下面的所有次设备号。

动态分配仅仅分配了字符设备的设备号(devid),需要配合cdev_init和cdev_add将设备和文件操作绑定。在分析cdev_init和cdev_add之前,先来看看cdev结构体有哪些成员

cdev结构体:

1
2
3
4
5
6
7
8
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};

其中dev_t dev就是设备号,file_operations *ops就是最重要的那个设备文件操作结构体指针,一般包括以下内容,这里仅作为示例:

1
2
3
4
5
6
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_release,
};

cdev_init

定义好cdev变量之后,cdev_add用于向Linux系统添加字符设备(cdev结构体),但在使用cdev_add之前需要先对cdev结构体变量进行初始化,cdev_init函数原型如下:

1
void cdev_init(struct cdev* cdev,const struct file_operations *fops)

参考示例:

1
cdev_init(&gpioled.cdev, &led_fops);

参数:

  • cdev:cdev就是要进行初始化的cdev结构体变量
  • fops:就是我们一直提到的file_operations结构体,通过cdev_init将cdev和file_operations绑定

cdev_add

对cdev初始化之后就可以通过cdev_add向linux系统中添加这个字符设备。cdev_add原型为:

1
int cdev_add(struct cdev *p,dev_t dev,unsigned count)

参考示例:

1
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

参数:

  • p:指向cdev指针
  • dev:显示传入的设备号,应该会将devid(通过alloc_chrdev_region)保存到cdev->dev(设备号)中
  • count:要添加的设备的数量

以上便实现了一个字符设备驱动的大部分内容了:包括确定主设备号(动态分配和静态分配),定义自己的file_operations结构体,3.实现file_operations结构体中的open/wirte/read函数(没写出细节),4.把file_operations结构体告诉内核,即注册file_operations结构体(静态注册register_chrdev,cdev_add),5.实现入口函数(实际上上述所有案例代码都位于init函数或者probe函数中)

设备树

  1. 在根节点下创建gpioled
1
2
3
4
5
6
7
8
9
gpioled {
18 #address-cells = <1>;
19 #size-cells = <1>;
20 compatible = "atkalpha-gpioled";
21 pinctrl-names = "default";
22 pinctrl-0 = <&pinctrl_leds>;
23 led-gpio = <&gpio5 3 GPIO_ACTIVE_LOW>;
24 status = "okay";
25 };
  1. 在imx6ul-evk下创建pinctrl_leds节点
1
2
3
4
5
pinctrl_leds: ledgrp {
738 fsl,pins = <
739 MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000110A0
740 >;
741 };

看几个重要的结构体

cdev再看一遍,实际上只用到了owner,其他暂时未用到

1
2
3
4
5
6
7
8
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};

再看class这个庞大的结构体,反正就是通过gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);创建了一个类,啥也没设置,类下边有很多的成员,同样也有owner name啥的。在class结构体前,可以看到这样的一句话:

1
2
3
4
5
* A class is a higher-level view of a device that abstracts out low-level
* implementation details. Drivers may see a SCSI disk or an ATA disk, but,
* at the class level, they are all simply disks. Classes allow user space
* to work with devices based on what they do, rather than how they are
* connected or how they work.

翻译过来大概就是:类是设备的更高的视角,类抽象出了低层次的实现细节。比如,驱动程序会区分SCSI磁盘和ATA磁盘,但是在类看来,他们都是简单的磁盘。类允许用户空间根据他们的功能进行使用,而不是设备的连接方式和工作方式。

1
2
3
4
5
6
7
8
9
10
11
struct class {
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
const struct attribute_group **dev_groups;
struct kobject *dev_kobj;
...省略一些函数
const struct dev_pm_ops *pm;

struct subsys_private *p;
};

device结构体也是一个庞大的结构体,gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);

platform_device结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct platform_device {
const char *name;//会在/sys/devices/platform/name中表现出来
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;

const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */

/* MFD cell pointer */
struct mfd_cell *mfd_cell;

/* arch specific additions */
struct pdev_archdata archdata;
};

platform_driver -> device_driver->

///代码源码在nfs_rootfs/myled_driver目录下