嵌入式快速入门学习笔记-字符设备驱动开发(LED)

it2024-11-08  14

字符设备驱动开发

字符设备驱动定义准备工作系统整体工作原理代码重要部分分析file_operation结构体register_chrdev函数unregister_chrdev函数 代码编写Makefilemodule_test.capp.c编译module_test,将.ko放入NFS共享目录通过NFS传输 .ko 文件到开发板 安装、查看、卸载 GPIOLIB方式注册重新编译内核代码编写module_test.cmakefile 编译、安装、测试 platform

字符设备驱动

定义

以字节为单位进行操作,如:(LCD、串口、LED、蜂鸣器、触摸屏……)。

准备工作

编译任何驱动程序前,需要先编译内核,因为驱动程序需要用到内核中的一些文件。 原因: 驱动程序要用到内核中的一些文件。 编译驱动时用的内核要与开发板上运行的内核一致。 更换板子上的内核后,板子上其他驱动也要重新编译。

系统整体工作原理

应用层->API->设备驱动->硬件 API:open、read、write、closa等 驱动源码中提供真正的open、read、write、close等函数实体 (此处需要添加一张图片)

代码重要部分分析

file_operation结构体

所在目录:kernel/include/linux/fs.h

struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); };

作用: 驱动向内核注册时,用于挂接实体函数的地址。 每一个驱动程序代码中都需要该结构体类型的变量。

register_chrdev函数

所在目录:kernel/include/linux/fs.h

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }

作用: 用于驱动向内核注册自己的file_operations。 将要注册的驱动存储在内核驱动数组的相应位置。 补: 查看内核中已经注册的驱动指令

cat /proc/devices

unregister_chrdev函数

所在目录:kernel/include/linux/fs.h

static inline void unregister_chrdev(unsigned int major, const char *name) { __unregister_chrdev(major, 0, 256, name); }

作用: 用于在内核中卸载安装的驱动。

代码编写

Makefile

# 开发板的linux内核的源码树目录 KERN_DIR = /x210v3_bsp/qt_x210v3/kernel obj-m += module_test.o all: make -C $(KERN_DIR) M=`pwd` modules arm-none-linux-gnueabi-gcc app.c -o app cp: cp *.ko /home/ww/nfs_rootfs cp app /home/ww/nfs_rootfs .PHONY: clean clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf app

module_test.c

该示例为内核自动分配主设备号

#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> //包含file_operations,register_chrdv,unregister_chrdv声明的头文件 #include <asm/uaccess.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> #include <linux/string.h> #define MYNAME "chartest" #define rGPJ0CON *((volatile unsigned int *)S5PV210_GPJ0CON) #define rGPJ0DAT *((volatile unsigned int *)S5PV210_GPJ0DAT) int MYMAJOR; char kbuf[100]; // 内核空间的buf //驱动硬件操作函数 //该函数中放置打开设备的硬件操作代码部分 static int test_chrdev_open(struct inode *inode, struct file *file) { printk(KERN_INFO "test_chrdev_open success\n"); return 0; } //驱动硬件关闭函数 static int test_chrdev_release(struct inode *inode, struct file *file) { printk(KERN_INFO "test_chrdev_release success\n"); return 0; } // 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。 static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { if (!copy_from_user(kbuf, ubuf, count)) { printk(KERN_INFO "test_chrdev_write success\n"); if(kbuf[0] == '1') { rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); } else if(kbuf[0] == '0') { rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); } else { printk(KERN_INFO "please input '1' or '0'\n"); } } else { printk(KERN_INFO "test_chrdev_write fail\n"); } return 0; } //驱动硬件读函数 ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { if(!copy_to_user(ubuf, kbuf, count)) { printk(KERN_INFO "test_chrdev_read success\n"); } else { printk(KERN_INFO "test_chrdev_read fail\n"); } return 0; } //自定义一个file_operations结构体变量,完成结构体内参数的填充 static const struct file_operations test_fops ={ .owner = THIS_MODULE, //默认即可 .open = test_chrdev_open, //对应驱动硬件操作函数 .release = test_chrdev_release, //对应驱动硬件关闭函数 .write = test_chrdev_write, .read = test_chrdev_read, }; //在module_init宏调用的函数中去注册字符设备驱动 static int __init chrdev_init(void) { printk(KERN_INFO "chrdev_init \n"); MYMAJOR= register_chrdev(0, MYNAME, &test_fops);//0-表示内核自动分配一个主设备号 if(MYMAJOR < 0) { printk(KERN_ERR "register_chrdev fail\n"); return -EINVAL; } else { printk(KERN_INFO "register_chrdev success\n"); return 0; } } //在module_exit宏调用的函数中去注销字符设备驱动 static void __exit chrdev_exit(void) { printk(KERN_INFO "chrdev_exit \n"); unregister_chrdev(MYMAJOR, MYNAME); } module_init(chrdev_init); module_exit(chrdev_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("aston"); // 描述模块的作者 MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息 MODULE_ALIAS("alias xxx"); // 描述模块的别名信息

app.c

#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> //需与 mknod /dev/test c 250 0 指令中的路径相同 #define FILE "dev/test" char buf[100]; int main(void) { int fd = -1; int i = 0; fd = open(FILE, O_RDWR); if (fd < 0) { printf("open %s error.\n", FILE); return -1; } printf("open %s success..\n", FILE); while (1) { memset(buf, 0 , sizeof(buf)); printf("please input on | off | flash | quit | read\n"); scanf("%s", buf); if (!strcmp(buf, "on")) { write(fd, "1", 1); } else if (!strcmp(buf, "off")) { write(fd, "0", 1); } else if (!strcmp(buf, "flash")) { for (i=0; i<3; i++) { write(fd, "1", 1); sleep(1); write(fd, "0", 1); sleep(1); } } else if (!strcmp(buf, "quit")) { break; } else if (!strcmp(buf, "read")) { memset(buf, 0, sizeof(buf)); read(fd, buf, 1); if(buf[0] == '1') { printf("led is on\n"); } else if(buf[0] == '0') { printf("led is off\n"); } else { printf("error\n"); } } } // 关闭文件 close(fd); return 0; }

编译module_test,将.ko放入NFS共享目录

进入到Makefile和module_test.c这两个文件的文件夹目录

cd /mnt/hgfs/share/x210v3/5.2.10 make all make cp

通过NFS传输 .ko 文件到开发板

假设Windows IP为192.168.1.100,在开发板上执行以下命令(注意:必须指定port为2049、 mountport为9999):

mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 192.168.1.100:/home/ww/nfs_rootfs /mnt

安装、查看、卸载

查看内核中已注册的驱动主设备号

cat /proc/devices

查看已经安装的驱动

lsmod

查看已有的设备文件

ls /dev/ -l

安装驱动

insmod module_test.ko

查看内核中已注册的驱动主设备号

cat /proc/devices

查看已经安装的驱动

lsmod

使用mknod创建设备文件

mknod /dev/test c 250 0

查看设备文件

ls /dev/test -l

测试app

./app

先卸载驱动

rmmod module_test

再删除设备文件

rm -r /dev/test

查看内核中已注册的驱动主设备号

cat /proc/devices

查看已经安装的驱动

lsmod

GPIOLIB方式注册

重新编译内核

执行 make menuconfig,去掉九鼎之前定义led驱动 选择上内核自己定义的led class 执行 make ,然后重新下载 zImage

代码编写

采用gpiolib的方式来控制led,实际使用的函数 (1)gpio_request: 向内核的gpiolib部分申请,得到允许后才可以去使用这个gpio。 (2)gpio_free: 对应gpio_request,用来释放申请后用完了的gpio (3)gpiochip_is_requested: 接口用来判断某一个gpio是否已经被申请了 (4)gpio_direction_input / gpio_direction_output: 接口用来设置gpio为输入/输出模式 (5)gpio_set_value: 设置gpio输出值 (6)gpio_get_value: 获取gpio值

module_test.c

#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/leds.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> #include <linux/io.h> #include <linux/ioport.h> #include <mach/gpio.h> #define GPIO_LED1 S5PV210_GPJ0(3) //驱动硬件写函数 static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness value) { if(value == 1) { gpio_set_value(GPIO_LED1 , 0); } else if(value == 0) { gpio_set_value(GPIO_LED1 , 1); } else { printk(KERN_INFO "please input 0 or 1\n"); } } //自定义一个led_classdev 结构体变量,完成结构体内参数的填充 static struct led_classdev led1 = { .name = "led1", .brightness = 0, .max_brightness = 255, .brightness_set = s5pv210_led_set, }; //在module_init宏调用的函数中去注册gpio_led设备驱动 static int __init s5pv210_led_init(void) { int ret = -1; printk(KERN_INFO "s5pv210_led_init start\n"); ret = gpio_request(GPIO_LED1, "led1_gpjo.3"); if (ret < 0) { printk(KERN_INFO "gpio_register failed\n"); return -EINVAL; } else { printk(KERN_INFO "gpio_register success\n"); gpio_direction_output(GPIO_LED1, 1); } ret = led_classdev_register(NULL, &led1); if (ret < 0) { printk(KERN_INFO "led_classdev_register failed\n"); return -EINVAL; } else { printk(KERN_INFO "led_classdev_register success\n"); } return 0; } //在module_exit宏调用的函数中去注销gpio_led设备驱动 static void __exit s5pv210_led_exit(void) { printk(KERN_INFO "s5pv210_led_exit start\n"); gpio_free(GPIO_LED1); printk(KERN_INFO "s5pv210_led_exit end\n"); } module_init(s5pv210_led_init); module_exit(s5pv210_led_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("ww"); // 描述模块的作者 MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息 MODULE_ALIAS("alias xxx"); // 描述模块的别名信息

makefile

# 开发板的linux内核的源码树目录 KERN_DIR = /x210v3_bsp/qt_x210v3/kernel obj-m += module_test.o all: make -C $(KERN_DIR) M=`pwd` modules cp: cp *.ko /home/ww/nfs_rootfs .PHONY: clean clean: make -C $(KERN_DIR) M=`pwd` modules clean

编译、安装、测试

执行 make 指令,make cp指令,将编译得到的module_test.ko复制到nfs文件夹。 开发板执行nfs指令,传到开发板的 /mnt 目录

mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 192.168.1.100:/home/ww/nfs_rootfs /mnt

安装前先查看开发板原状态

mount -t debugfs debugfs /tmp cat /tmp/gpio

使用完后umount /tmp卸载掉debugfs,防止引入错误。

安装驱动查看开发板状态

insmod module_tes.ko

测试驱动

cd /sys/class/leds/led1 ls

cat brightness

echo 1 > brightness cat brightness

led灯亮 再次输入一个其他值,查看结果

从此处可以看出,cat brightness 实际执行的是s5pv210_led_set()函数

platform

static struct S5pv210_led_platdata s5pv210_led1_pdata = { .name = "led1", .gpio = S5PV210_GPJ0(3), .flags = S5pv210_LEDF_ACTLOW | S5pv210_LEDF_TRISTATE, }; static struct S5pv210_led_platdata s5pv210_led2_pdata = { .name = "led2", .gpio = S5PV210_GPJ0(4), .flags = S5pv210_LEDF_ACTLOW | S5pv210_LEDF_TRISTATE, }; static struct S5pv210_led_platdata s5pv210_led3_pdata = { .name = "led3", .gpio = S5PV210_GPJ0(5), .flags = S5pv210_LEDF_ACTLOW | S5pv210_LEDF_TRISTATE, }; struct platform_device s5pv210_led1 = { .name = "s5pv210_led", .id = 1, .dev = { .platform_data = &s5pv210_led1_pdata }, }; struct platform_device s5pv210_led2 = { .name = "s5pv210_led", .id = 2, .dev = { .platform_data = &s5pv210_led2_pdata }, }; struct platform_device s5pv210_led3 = { .name = "s5pv210_led", .id = 3, .dev = { .platform_data = &s5pv210_led3_pdata }, }; static struct platform_device *smdkc110_devices[] __initdata = { &s5pv210_led1, &s5pv210_led2, &s5pv210_led3, #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> //包含file_operations,register_chrdv,unregister_chrdv声明的头文件 #include <linux/leds.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h #include <linux/io.h> #include <linux/ioport.h> #include <mach/gpio.h> #include <linux/platform_device.h> #include <mach/leds-gpio.h> #include <linux/slab.h> struct s5pv210_gpio_led { struct led_classdev cdev; struct S5pv210_led_platdata *pdata; }; static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev) { return platform_get_drvdata(dev); } static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev) { return container_of(led_cdev, struct s5pv210_gpio_led, cdev); } static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness value) { struct s5pv210_gpio_led *led = to_gpio(led_cdev); struct S5pv210_led_platdata *pd = led->pdata; if(value == 1) { gpio_set_value(pd->gpio , 0); } else if(value == 0) { gpio_set_value(pd->gpio , 1); } else { printk(KERN_INFO "please input 0 or 1\n"); } } static int s5pv210_led_probe(struct platform_device *dev) { struct S5pv210_led_platdata *pdata = dev->dev.platform_data; struct s5pv210_gpio_led *led; int ret; led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL); if (led == NULL) { printk(KERN_INFO "No memory for device\n"); return -ENOMEM; } platform_set_drvdata(dev, led); led->cdev.name = pdata->name; led->cdev.brightness = 0; led->cdev.brightness_set = s5pv210_led_set; led->pdata = pdata; /* register our new led device */ ret = led_classdev_register(&dev->dev, &led->cdev); if (ret < 0) { printk(KERN_INFO "led_classdev_register failed\n"); kfree(led); return ret; } ret = gpio_request(pdata->gpio, pdata->name); if (ret < 0) { printk(KERN_INFO "gpio_register failed\n"); } else { printk(KERN_INFO "gpio_register success\n"); gpio_direction_output(pdata->gpio, 1); } return 0; } static int s5pv210_led_remove(struct platform_device *dev) { struct s5pv210_gpio_led *led = pdev_to_gpio(dev); led_classdev_unregister(&led->cdev); gpio_free(led->pdata->gpio); kfree(led); return 0; } static struct platform_driver s5pv210_led_driver = { .probe = s5pv210_led_probe, .remove = s5pv210_led_remove, .driver = { .name = "s5pv210_led", .owner = THIS_MODULE, }, }; static int __init s5pv210_led_init(void) { return platform_driver_register(&s5pv210_led_driver); } static void __exit s5pv210_led_exit(void) { platform_driver_unregister(&s5pv210_led_driver); } module_init(s5pv210_led_init); module_exit(s5pv210_led_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("ww"); // 描述模块的作者 MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息 MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
最新回复(0)