欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【13】Linux驱动开发第一阶段学习总结【20200608 ——202000719】

程序员文章站 2022-06-09 08:43:27
...

目录

1、添加字符设备驱动方式

2、将驱动编译成.ko的模块及其注册注销方式

3、驱动模块加载使用方式

4、设备树及设备树节点添加,dts语法,of函数的使用

5、Linux并发与竞争

6、嵌入式调试环境搭建

7、Linux内核中断

8、内核定时器使用

9、Linux阻塞与非阻塞IO

10、Linux异步通知

11、platform平台设备驱动

12、pinctl和gpio子系统

13、杂项驱动

14、input子系统

15、多种LED驱动

(1)手动创建设备节点【无设备树,操作的寄存器】

(2)自动创建设备节点+自建设备树节点【操作的寄存器】

(3)自动创建设备节点+自建设备树节点+pinctl和gpio子系统【操作的设备树节点】

(4)自动创建设备节点+无设备树+platform平台驱动【操作的寄存器】

(5)自动创建设备节点+自建设备树节点+platform平台驱动+pinctl和gpio子系统【操作的设备树节点】

(6)Linux内核自带的LED驱动

16、多种KEY驱动

(1)自动创建设备节点+自建设备树节点+pinctl和gpio子系统

(2)自动创建设备节点+自建设备树节点+pinctl和gpio子系统+中断

(3)自动创建设备节点+自建设备树节点+pinctl和gpio子系统+中断+阻塞IO

(4)自动创建设备节点+自建设备树节点+pinctl和gpio子系统+中断+非阻塞IO

(5)自动创建设备节点+自建设备树节点+pinctl和gpio子系统+中断+信号异步通知

(6)自动创建设备节点+自建设备树节点+pinctl和gpio子系统+中断+INPUT子系统


1、添加字符设备驱动方式

(1)只是添加字符设备,还需要通过mknod手动在/dev目录下创建设备节点

int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
void unregister_chrdev(unsigned int major, const char *name)

(2)添加字符设备后,通过udev机制,在/dev目录下 自动创建对应的设备节点

注册过程:

alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); /* 申请设备号 */ 
cdev_init(struct cdev *cdev, const struct file_operations *fops); /* 初始化cdev */ 
cdev_add(struct cdev *p, dev_t dev, unsigned count); /* 添加cdev */ 
class_create(struct module *owner, const char *name); /* 创建类 */
device_create(struct class *class, struct device *parent, dev_t devt, 
                void *drvdata, const char *fmt, ...)); /* 创建设备 */

注销过程:

cdev_del(struct cdev *p)); /* 删除cdev */ 
unregister_chrdev_region(dev_t from, unsigned count); /* 注销设备号 */ 
device_destroy(struct class *class, dev_t devt); /* 删除设备 */ 
class_destroy(struct class *cls); /* 删除类 */

2、将驱动编译成.ko的模块及其注册注销方式

/*注册驱动和卸载驱动*/
module_init(newchar_led_init);
module_exit(newchar_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("denghengli");

3、驱动模块加载使用方式

第一步:将.ko驱动模块拷贝至根文件系统的 /lib/modules/4.1.15下,启动Linux
第二步:在终端 cd /lib/modules/4.1.15 
第三步:通过如下命令加载模块
depmod 
modprobe xxx.ko
rmmod xxx.ko

【13】Linux驱动开发第一阶段学习总结【20200608 ——202000719】

4、设备树及设备树节点添加,dts语法,of函数的使用

1、DTS也以 ’/’ 开始

2,从/根节点开始描述设备信息

3,在/根节点外有一些&cpu0这样的语句是“追加“

【13】Linux驱动开发第一阶段学习总结【20200608 ——202000719】

4,节点名字,完整的要求

aaa@qq.com

unit-address一般都是外设寄存器的起始地址,有时候是I2C的设备地址,或者其他含义,具体节点具体分析。设备树里面常常遇到如下所示节点名字:

intc: aaa@qq.com 
: 前面的intc是标签label,后面才是名字,完整的名字是aaa@qq.com,在后面追加的时候直接使用&intc

5、Linux并发与竞争

原子操作(atomic)、自旋锁(spinlock)、信号量(semaphore)、互斥体(mutex)原理及API使用

6、嵌入式调试环境搭建

(1)gdb+gdbserver环境搭建与调试

(2)vscode+gdbserver图形环境搭建与调试

7、Linux内核中断

(1)Linux中断的上半部 和 下半部(软中断、tasklet、工作队列work)处理方式

(2)设备树中添加中断节点信息

①、 #interrupt-cells,指定中断源的信息 cells个数。

②、 interrupt-controller,表示当前节点为中断控制器。

③、 interrupts,指定中断号,触发方式等。

④、 interrupt-parent,指定父中断,也就是中断控制器。

8、内核定时器使用

其实就是一个软件定时器,通过全局变量 jiffies 记录当前的系统时钟节拍数

9、Linux阻塞与非阻塞IO

(1)阻塞式IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。对于非阻塞IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作【13】Linux驱动开发第一阶段学习总结【20200608 ——202000719】

(2) Linux内核提供了等待队列 (wait queue)来实现阻塞进程的唤醒工作

(3)如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。 poll、epoll和 select可以用于处理轮询,应用程序通过 select、 epoll或 poll函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用 select、 epoll或 poll函数的时候设备驱动程序中的poll函数就会执行,因此需要在设备驱动程序中编写 poll函数

10、Linux异步通知

拿按键输入来说

(1)只是通过中断的方式,设置好中断以后,驱动程序会在中断发生的时候,在中断处理函数中置一个标志位,为了安全一般使用通过原子操作。驱动是不会告诉应用已经有按键按下了,所以应用程序需要时刻的使用read去查看按键有没有被按下(也就是标志有没有被置位),不会产生调度,这样应用程序就会一直占用CPU资源,cpu使用率会很高

(2)通过中断+阻塞IO的方式,应用程序使用read查看按键状态的时候,如果没有被按下,驱动会将应用程序放进等待队列中,并将该进程设置为休眠状态;在按键按下产生一个中断的时候,驱动程序会在中断函数中将等待队列中的进程唤醒,然后应用程序才再去查询按键状态就是被按下了

(3)通过中断+非阻塞IO的方式,应用程序使用select或poll去超时查询按键状态的时候,驱动程序的poll函数会被执行,select或poll去查询按键状态的时候,如果设备不可用直至超时,则会返回超时并再次查询,如果设备可用了则会立即返回,在等待期间不会引起阻塞

(4)通过中断+异步通知的方式,异步通知就是通过“信号”模拟硬件中断的方式,应用程序不再需要时刻去查看按键是否按下,应用程序需要先注册信号处理函数,设置要接收的信号,然后使用fcntl调用驱动程序的 fasync 开启异步通知,驱动程序在接收到按键输入的时候会直接在中断处理函数中发送想要的信号,内核就会直接调用与该信号匹配的应用程序注册的信号处理函数

11、platform平台设备驱动

为了方便驱动的编写,提高软件的重用以及跨平台性能,Linux中提出了驱动的通过驱动--总线--设备的分层与分离。根据 驱动 -- 总线 -- 设备驱动模型,IIC、SPI、USB这样实实在在的总线是完全匹配,但是有一些外设是没法归结为具体的总线的,比如SOC内部的定时器、RTC、LCD等。为此linux内核创造了一个虚拟的平台驱动模型:platform驱动 -- 总线 -- platform设备

【13】Linux驱动开发第一阶段学习总结【20200608 ——202000719】

如果platform驱动跟platform设备匹配成功以后,platform_driver 中的probe就会执行。

(1)在没有设备树的情况下,需要编写驱动和设备程序

/********************驱动程序********************/
static struct platform_driver led_driver = 
{
    .probe		= led_probe,
    .remove		= led_remove,
    .driver		= {
        .name	= "imx6ull-led", //驱动名字,无设备树下 用于与设备名字匹配,匹配成功之后 probe 就会执行
    },
};
/*驱动模块加载*/
static int __init led_driver_init(void)
{
    return platform_driver_register(&led_driver);/*注册platform驱动*/
}
/*驱动模块卸载*/
static void __exit led_driver_exit(void)
{
    platform_driver_unregister(&led_driver);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("denghengli");



/********************设备程序********************/
static struct platform_device led_device = 
{
    .name = "imx6ull-led",
    .id   = -1, //表示次设备无ID
    .dev = {
        .release = led_device_release,
    },
    .num_resources = ARRAY_SIZE(led_device_resources),//表示资源数量
    .resource =   led_device_resources,//表示资源,也就是设备信息
};
/*设备模块加载*/
static int __init led_device_init(void)
{
    return platform_device_register(&led_device);/*注册platform设备*/
}
/*设备模块卸载*/
static void __exit led_device_exit(void)
{
    platform_device_unregister(&led_device);
}
module_init(led_device_init);
module_exit(led_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("denghengli");

(2)在有设备树的情况下,只需要编写驱动程序,设备信息是在设备树中描述的

/*设备树匹配表*/
static struct of_device_id led_of_match[] = {
    {.compatible = "alientek-gpioled"},//兼容性属性可以有多个
    {/*sentinel*/},//结尾必须这样
};
static struct platform_driver led_driver = 
{
    .probe		= led_probe,
    .remove		= led_remove,
    .driver		= {
        .name	= "imx6ull-led", //驱动名字,无设备树下 用于与设备名字匹配,匹配成功之后 probe 就会执行
        .of_match_table = led_of_match,//匹配表,有设备树下,用于与设备树中的节点进行匹配,匹配成功之后 probe 就会执行
    },
};
/*驱动模块加载*/
static int __init led_driver_init(void)
{
    return platform_driver_register(&led_driver);/*注册platform驱动*/
}
/*驱动模块卸载*/
static void __exit led_driver_exit(void)
{
    platform_driver_unregister(&led_driver);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("denghengli");

12、pinctl和gpio子系统

pinctrl子系统重点是设置 PIN(有的 SOC叫做 PAD)的复用和电气属性,gpio子系统就是对gpio进行读写操作。只需要在设备树里面设置好某个pin的相关属性,其他的初始化工作均由 pinctrl子系统来完成, pinctrl子系统源码目录为 drivers/pinctrl,嵌套在 platform 总线驱动中。pinctrl子系统主要工作内容如下:

①、获取设备树中 pin信 息。

②、根据获取到的 pin信息来设置 pin的复用功能

③、根据获取到的 pin信息来设置 pin的电气特性,比如上 /下拉、速度、驱动能力等

13、杂项驱动

在Linux中,有些外设无法进行分类的时候可以使用MISC驱动,MISC驱动其实就是最简单的字符设备驱动,嵌套在 platform 总线驱动中所有的 MISC设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。随着 Linux字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主设备号, MISC设备驱动就用于解决此问题。 MISC设备会自动创建 cdev,不需要像我们以前那样手动创建,因此采用 MISC设备驱动可以简化字符设备驱动的编写。

14、input子系统

针对 按键、鼠标、键盘、触摸屏等都属于输入 (input)字符设备,用户只需要负责上报输入事件,比如按键值、坐标等信息, input核心层负责处理这些事件。和pinctrl 和gpio 子系统一样,都是Linux 内核针对某一类设备而创建的框架。input 子系统分为input 驱动层、input 核心层、input 事件处理层【13】Linux驱动开发第一阶段学习总结【20200608 ——202000719】

15、多种LED驱动

使用了设备树+pinctl和gpio子系统的led驱动使用流程:

1、首先,获取到GPIO所处的设备节点,比如of_find_node_by_path。

2、获取GPIO编号, of_get_named_gpio函数,返回值就是GPIO编号。

3、请求此编号的GPIO,gpio_request函数

4、设置GPIO,输入或输出,gpio_direction_input或gpio_direction_output。

5、如果是输入,那么通过gpio_get_value函数读取GPIO值,如果是输出,通过gpio_set_value设置GPIO值。

(1)手动创建设备节点【无设备树,操作的寄存器】

源码路径:https://github.com/denghengli/linux_driver/blob/master/2_led/led.c

(2)自动创建设备节点+自建设备树节点【操作的寄存器】

源码路径:https://github.com/denghengli/linux_driver/blob/master/5_dtsled/dtsled.c

/*led节点(没有通过pinctrl子系统和GPIO子系统)*/
alphaled{
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "alientek-dtsled";
    status = "okay";
    reg = <0X020C406C 0x04		/*CCM_CCGR1_BASE*/
            0X020E0068 0x04		/*SW_MUX_GPIO1_IO03_BASE*/
            0X020E02F4 0x04		/*SW_PAD_GPIO1_IO03_BASE*/
            0X0209C000 0x04		/*GPIO1_DR_BASE*/
            0X0209C004 0x04>;	/*GPIO1_GDIR_BASE*/
};

(3)自动创建设备节点+自建设备树节点+pinctl和gpio子系统【操作的设备树节点】

源码路径:https://github.com/denghengli/linux_driver/blob/master/6_gpioled/gpioled.c

/*led节点(通过pinctrl子系统和GPIO子系统)*/
gpioled{
    compatible = "alientek-gpioled";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_gpio_leds>;/*pinctrl子系统*/
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;/*GPIO子系统*/ 
    status = "okay";
};

(4)自动创建设备节点+无设备树+platform平台驱动【操作的寄存器】

源码路径:https://github.com/denghengli/linux_driver/blob/master/18_led_platform/led_driver.c

(5)自动创建设备节点+自建设备树节点+platform平台驱动+pinctl和gpio子系统【操作的设备树节点】

源码路径:https://github.com/denghengli/linux_driver/blob/master/19_led_dtsplatform/led_dtsdriver.c

/*led节点(通过pinctrl子系统和GPIO子系统)*/
gpioled{
    compatible = "alientek-gpioled";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_gpio_leds>;/*pinctrl子系统*/
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;/*GPIO子系统*/ 
    status = "okay";
};

(6)Linux内核自带的LED驱动

/*自行添加的Linux内核LED驱动*/
linux_leds {
    compatible = "gpio-leds";
    pinctrl-0 = <&pinctrl_gpio_leds>;/*pinctrl子系统*/
    led0 {
        label = "sysrun";
        gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
        linux,default-trigger = "heartbeat";
        default-state = "off";
    };
};

16、多种KEY驱动

都使用了pinctl和gpio子系统和按键输入中断,操作的设备树节点,设备树节点如下:

/*key节点(通过pinctrl子系统和GPIO子系统)*/
key{
    compatible = "alientek-key";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_gpio_keys>;/*pinctrl子系统*/
    key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;/*GPIO子系统*/ 
    interrupt-parent = <&gpio1>;
    interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
    status = "okay";
};

(1)自动创建设备节点+自建设备树节点+pinctl和gpio子系统

源码路径:https://github.com/denghengli/linux_driver/blob/master/11_key/key.c

(2)自动创建设备节点+自建设备树节点+pinctl和gpio子系统+中断

源码路径:https://github.com/denghengli/linux_driver/blob/master/14_keyirq/keyirq.c

(3)自动创建设备节点+自建设备树节点+pinctl和gpio子系统+中断+阻塞IO

源码路径:https://github.com/denghengli/linux_driver/tree/master/15_blockio

(4)自动创建设备节点+自建设备树节点+pinctl和gpio子系统+中断+非阻塞IO

源码路径:https://github.com/denghengli/linux_driver/tree/master/16_noblockio

(5)自动创建设备节点+自建设备树节点+pinctl和gpio子系统+中断+信号异步通知

源码路径:https://github.com/denghengli/linux_driver/tree/master/17_fasync

(6)自动创建设备节点+自建设备树节点+pinctl和gpio子系统+中断+INPUT子系统

源码路径:https://github.com/denghengli/linux_driver/tree/master/21_input

 

 

相关标签: Linux