100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 【嵌入式Linux学习笔记】Linux驱动开发

【嵌入式Linux学习笔记】Linux驱动开发

时间:2020-05-07 09:22:17

相关推荐

【嵌入式Linux学习笔记】Linux驱动开发

Linux系统构建完成后,就可以基于该环境方便地进行开发了,相关的开发流程与MCU类似,但是引入了设备树的概念,编写应用代码要相对复杂一点。但是省去了很多配置工作。

学习视频地址:【正点原子】STM32MP157开发板

字符设备驱动开发

驱动流程

在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

比如现在有个叫做/dev/led 的驱动文件,此文件是 led 灯的驱动文件。

open 函数来打开文件/dev/led,使用完成以后使用 close 函数关闭/dev/led 这个文件。 open和 close 就是打开和关闭 led 驱动的函数点亮或关闭 led,那么就使用 write 函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开 led 的控制参数如果要获取led 灯的状态,就用 read 函数从驱动中读取相应的状态。

内核驱动函数及变量

owner:拥有该结构体的模块的指针变量, 一般设置为 THIS_MODULE

llseek:修改文件当前的读写位置

read:读取设备文件

write:向设备文件写入数据

poll:轮询函数,用于查询设备是否可以进行非阻塞的读写。

unlocked_ioctl:提供对于设备的控制功能,与应用程序中的 ioctl 函数对应

compat_ioctl:函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,

32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。

mmap:用于将将设备的内存映射到进程空间中(也就是用户空间),一般帧

缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。

open:打开设备文件

release:用于释放(关闭)设备文件,与应用程序中的 close 函数对应

fasync:用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中

驱动开发步骤

1. 驱动模块的加载和卸载

将驱动编译进Linux内核中,内核启动时就会自动运行驱动程序将驱动编译为模块(Linux 下模块扩展名为.ko),Linux内核启动后采用modprobe指令加载模块。

module_init(xxx_init); //注册模块加载函数module_exit(xxx_exit); //注册模块卸载函数

2. 字符设备的注册与注销

字符设备的注册与注销放在模块的加载和卸载函数中。

// 只指定主设备号,会造成资源浪费static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)static inline void unregister_chrdev(unsigned int major,const char *name)// 用多少申请多少,比较合理int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)int register_chrdev_region(dev_t from, unsigned count, const char *name)void unregister_chrdev_region(dev_t from, unsigned count)

3. 类和设备的创建

这部分是为了设备可以自动创建和删除文件节点

// 类的创建和删除struct class *class_create (struct module *owner, const char *name)void class_destroy(struct class *cls)// 设备的创建和删除struct device *device_create(struct class *cls,struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)void device_destroy(struct class *cls, dev_t devt)

4. 实例代码

字符设备驱动

#include <linux/types.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/ide.h>#include <linux/init.h>#include <linux/module.h>#define CHRDEVBASE_MAJOR100/* 主设备号 */#define CHRDEVBASE_NAME"devbase"/* 设备名 */static char readbuf[100];/* 读缓冲区 */static char writebuf[100];/* 写缓冲区 */static char kerneldata[] = {"kernel data!"};/** @description: 打开设备* @param - inode : 传递给驱动的inode* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* @return : 0 成功;其他 失败*/static int chrdevbase_open(struct inode *inode, struct file *filp){printk("devbase open! \r\n");return 0;}/** @description: 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){int retvalue = 0;/* 向用户空间发送数据 */memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("kernel senddata ok!\r\n");}else{printk("kernel senddata failed!\r\n");}return 0;}/** @description: 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符* @param - buf : 要写给设备写入的数据* @param - cnt : 要写入的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 写入的字节数,如果为负值,表示写入失败*/static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){int retvalue = 0;/* 接收用户空间传递给内核的数据并且打印出来 */retvalue = copy_from_user(writebuf, buf, cnt);if(retvalue == 0){printk("kernel recevdata:%s\r\n", writebuf);}else{printk("kernel recevdata failed!\r\n");}return 0;}/** @description: 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/static int chrdevbase_release(struct inode *inode, struct file *filp){printk("devbase release! \r\n");return 0;}/** 设备操作函数结构体*/static struct file_operations devbase_fops = {.owner = THIS_MODULE,.open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,};/** @description: 驱动入口函数 * @param : 无* @return : 0 成功;其他 失败*/static int __init chrdevbase_init(void){int retvalue = 0;/* 注册字符设备驱动 */retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &devbase_fops);if(retvalue < 0){printk("chrdevbase driver register failed\r\n");}printk("chrdevbase init!\r\n");return 0;}/** @description: 驱动出口函数* @param : 无* @return : 无*/static void __exit chrdevbase_exit(void){/* 注销字符设备驱动 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("chrdevbase exit!\r\n");}/* * 将上面两个函数指定为驱动的入口和出口函数 */module_init(chrdevbase_init);module_exit(chrdevbase_exit);/* * LICENSE和作者信息*/MODULE_LICENSE("GPL");MODULE_AUTHOR("JozenLee");

新字符驱动代码(以LED为例)

#include <linux/types.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/ide.h>#include <linux/init.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/gpio.h>#include <linux/cdev.h>#include <linux/device.h>#include <asm/mach/map.h>#include <asm/uaccess.h>#include <asm/io.h>#define NEWCHRLED_CNT1 /* 设备号个数 */#define NEWCHRLED_NAME"newchrled"/* 名字 */#define LEDOFF 0/* 关灯 */#define LEDON 1/* 开灯 *//* 寄存器物理地址 */#define PERIPH_BASE(0x40000000)#define MPU_AHB4_PERIPH_BASE(PERIPH_BASE + 0x10000000)#define RCC_BASE (MPU_AHB4_PERIPH_BASE + 0x0000)#define RCC_MP_AHB4ENSETR(RCC_BASE + 0XA28)#define GPIOI_BASE(MPU_AHB4_PERIPH_BASE + 0xA000)#define GPIOI_MODER(GPIOI_BASE + 0x0000)#define GPIOI_OTYPER(GPIOI_BASE + 0x0004)#define GPIOI_OSPEEDR(GPIOI_BASE + 0x0008)#define GPIOI_PUPDR(GPIOI_BASE + 0x000C)#define GPIOI_BSRR(GPIOI_BASE + 0x0018)/* 映射后的寄存器虚拟地址指针 */static void __iomem *MPU_AHB4_PERIPH_RCC_PI;static void __iomem *GPIOI_MODER_PI;static void __iomem *GPIOI_OTYPER_PI;static void __iomem *GPIOI_OSPEEDR_PI;static void __iomem *GPIOI_PUPDR_PI;static void __iomem *GPIOI_BSRR_PI;/* newchrled设备结构体 */struct newchrled_dev{dev_t devid;/* 设备号 */struct cdev cdev;/* cdev */struct class *class;/* 类 */struct device *device;/* 设备 */int major;/* 主设备号 */int minor;/* 次设备号 */};struct newchrled_dev newchrled;/* led设备 *//** @description: LED打开/关闭* @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return : 无*/void led_switch(u8 sta){u32 val = 0;if(sta == LEDON) {val = readl(GPIOI_BSRR_PI);val |= (1 << 16);writel(val, GPIOI_BSRR_PI);}else if(sta == LEDOFF) {val = readl(GPIOI_BSRR_PI);val|= (1 << 0);writel(val, GPIOI_BSRR_PI);}}/** @description: 取消映射* @return : 无*/void led_unmap(void){/* 取消映射 */iounmap(MPU_AHB4_PERIPH_RCC_PI);iounmap(GPIOI_MODER_PI);iounmap(GPIOI_OTYPER_PI);iounmap(GPIOI_OSPEEDR_PI);iounmap(GPIOI_PUPDR_PI);iounmap(GPIOI_BSRR_PI);}/** @description: 打开设备* @param - inode : 传递给驱动的inode* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* @return : 0 成功;其他 失败*/static int led_open(struct inode *inode, struct file *filp){filp->private_data = &newchrled; /* 设置私有数据 */return 0;}/** @description: 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){return 0;}/** @description: 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符* @param - buf : 要写给设备写入的数据* @param - cnt : 要写入的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 写入的字节数,如果为负值,表示写入失败*/static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];/* 获取状态值 */if(ledstat == LEDON) {led_switch(LEDON);/* 打开LED灯 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF);/* 关闭LED灯 */}return 0;}/** @description: 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/static int led_release(struct inode *inode, struct file *filp){return 0;}/* 设备操作函数 */static struct file_operations newchrled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,};/** @description: 驱动出口函数* @param : 无* @return : 无*/static int __init led_init(void){u32 val = 0;int ret;/* 初始化LED *//* 1、寄存器地址映射 */MPU_AHB4_PERIPH_RCC_PI = ioremap(RCC_MP_AHB4ENSETR, 4);GPIOI_MODER_PI = ioremap(GPIOI_MODER, 4);GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER, 4);GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR, 4);GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR, 4);GPIOI_BSRR_PI = ioremap(GPIOI_BSRR, 4);/* 2、使能PI时钟 */val = readl(MPU_AHB4_PERIPH_RCC_PI);val &= ~(0X1 << 8); /* 清除以前的设置 */val |= (0X1 << 8); /* 设置新值 */writel(val, MPU_AHB4_PERIPH_RCC_PI);/* 3、设置PI0通用的输出模式。*/val = readl(GPIOI_MODER_PI);val &= ~(0X3 << 0); /* bit0:1清零 */val |= (0X1 << 0); /* bit0:1设置01 */writel(val, GPIOI_MODER_PI);/* 3、设置PI0为推挽模式。*/val = readl(GPIOI_OTYPER_PI);val &= ~(0X1 << 0); /* bit0清零,设置为上拉*/writel(val, GPIOI_OTYPER_PI);/* 4、设置PI0为高速。*/val = readl(GPIOI_OSPEEDR_PI);val &= ~(0X3 << 0); /* bit0:1 清零 */val |= (0x2 << 0); /* bit0:1 设置为10*/writel(val, GPIOI_OSPEEDR_PI);/* 5、设置PI0为上拉。*/val = readl(GPIOI_PUPDR_PI);val &= ~(0X3 << 0); /* bit0:1 清零*/val |= (0x1 << 0); /*bit0:1 设置为01*/writel(val,GPIOI_PUPDR_PI);/* 6、默认关闭LED */val = readl(GPIOI_BSRR_PI);val |= (0x1 << 0);writel(val, GPIOI_BSRR_PI);/* 注册字符设备驱动 *//* 1、创建设备号 */if (newchrled.major) {/* 定义了设备号 */newchrled.devid = MKDEV(newchrled.major, 0);ret = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);if(ret < 0) {pr_err("cannot register %s char driver [ret=%d]\n",NEWCHRLED_NAME, NEWCHRLED_CNT);goto fail_map;}} else {/* 没有定义设备号 */ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);/* 申请设备号 */if(ret < 0) {pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", NEWCHRLED_NAME, ret);goto fail_map;}newchrled.major = MAJOR(newchrled.devid);/* 获取分配号的主设备号 */newchrled.minor = MINOR(newchrled.devid);/* 获取分配号的次设备号 */}printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);/* 2、初始化cdev */newchrled.cdev.owner = THIS_MODULE;cdev_init(&newchrled.cdev, &newchrled_fops);/* 3、添加一个cdev */ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);if(ret < 0)goto del_unregister;/* 4、创建类 */newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);if (IS_ERR(newchrled.class)) {goto del_cdev;}/* 5、创建设备 */newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);if (IS_ERR(newchrled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(newchrled.class);del_cdev:cdev_del(&newchrled.cdev);del_unregister:unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);fail_map:led_unmap();return -EIO;}/** @description: 驱动出口函数* @param : 无* @return : 无*/static void __exit led_exit(void){/* 取消映射 */led_unmap();/* 注销字符设备驱动 */cdev_del(&newchrled.cdev);/* 删除cdev */unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */device_destroy(newchrled.class, newchrled.devid);class_destroy(newchrled.class);}module_init(led_init);module_exit(led_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("ALIENTEK");MODULE_INFO(intree, "Y");

设备应用

#include "stdio.h"#include "unistd.h"#include "sys/types.h"#include "sys/stat.h"#include "fcntl.h"#include "stdlib.h"#include "string.h"/* 用户数据, 用于写入设备测试 */static char usrdata[] = {"usr data!"};int main(int argc, char *argv[]){int fd, retvalue;char *filename;char readbuf[100], writebuf[100];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开驱动文件 */fd = open(filename, O_RDWR);if(fd < 0){printf("Can't open file %s\r\n", filename);return -1;}/* 从驱动文件读取数据测试 */if(atoi(argv[2]) == 1){retvalue = read(fd, readbuf, 50);if(retvalue < 0){printf("read file %s failed!\r\n", filename);}else{printf("read data:%s\r\n",readbuf);}}/* 向设备驱动写数据测试 */if(atoi(argv[2]) == 2){memcpy(writebuf, usrdata, sizeof(usrdata));retvalue = write(fd, writebuf, 50);if(retvalue < 0){printf("write file %s failed!\r\n", filename);}}/* 关闭设备 */retvalue = close(fd);if(retvalue < 0){printf("Can't close file %s\r\n", filename);return -1;}return 0;}

编译

将以下内容写入MakeFile

KERNELDIR := /home/zuozhongkai/linux/atk-mp1/linux/my_linux/linux-5.4.31 #Linux内核路径CURRENT_PATH := $(shell pwd)obj-m := newchrled.o #要跟设备驱动代码文件名称一致build: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译驱动

make -j16

编译应用文件

arm-none-linux-gnueabihf-gcc xxx.c -o xxx #xxx为应用代码文件名

将编译所得的库文件(如led.ko)和应用文件(如ledApp)放至arm的根目录下,即可在开发板中使用。

代码运行

进入到上述文件所在的目录

depmod # 第一次加载模块时需运行modprobe led # 加载驱动模块./ledApp /dev/newchrled 1 # 执行应用代码

pinctrl和gpio子系统

pinctrl

作用:根据所提供的信息配置PIN功能

pinctrl: pin-controller@50002000 {#address-cells = <1>;#size-cells = <1>;compatible = "st,stm32mp157-pinctrl";ranges = <0 0x50002000 0xa400>;interrupt-parent = <&exti>;st,syscfg = <&exti 0x60 0xff>;hwlocks = <&hsem 0 1>;pins-are-numbered;......};

pinctrl下需要有子节点,子节点中有pins节点,以配置特定的PIN属性

&pinctrl {......m_can1_pins_a: m-can1-0 {pins1 {pinmux = <STM32_PINMUX('H', 13, AF9)>; /* CAN1_TX */slew-rate = <1>;drive-push-pull;bias-disable;};pins2 {pinmux = <STM32_PINMUX('I', 9, AF9)>; /* CAN1_RX */bias-disable;};};};

pins属性

pinmux 属性

pinmux = <STM32_PINMUX(port, line, mode)>;#例pinmux = <STM32_PINMUX('H', 13, AF9)>;

port:表示用那一组 GPIO(例:H 表示为 GPIO 第 H 组,也就是 GPIOH)

line:表示这组 GPIO 的第几个引脚(例:13 表示为 GPIOH_13,也就是 PH13)

mode:表示当前引脚要做那种复用功能(例:AF9 表示为用第 9 个复用功能)

电气属性配置

gpio子系统

作用:用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。

创建节点

led {compatible = "atk,led";gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;status = "okay";};

API函数

int gpio_request(unsigned gpio, const char *label) // 申请GPIO管脚void gpio_free(unsigned gpio)// 释放GPIOint gpio_direction_input(unsigned gpio)// 设置GPIO为输入int gpio_direction_output(unsigned gpio, int value)// 设置GPIO为输出int __gpio_get_value(unsigned gpio)// 获取GPIO当前值void __gpio_set_value(unsigned gpio, int value)// 设定GPIO当前值

of_gpio_named_count:用于获取设备树某个属性里面定义了几个 GPIO 信息

int of_gpio_named_count(struct device_node *np, const char *propname)

of_gpio_count:和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息

int of_gpio_count(struct device_node *np)

of_get_named_gpio:获取 GPIO 编号

int of_get_named_gpio(struct device_node *np,const char *propname,int index)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。