《嵌入式驱动(三):字符设备驱动开发》
一、字符设备驱动开发框架
1.驱动根据设备功能分为不同类型的设备
查看设备类型 cat /proc/devices
2.Linux系统下的设备号都有一个设备号
设备号 = 主设备号(设备类型) + 次设备号(该种类第几个设备)
3.添加设备
每添加一个设备就需要在文件系统中添加一个该设备的设备结点
4.应用层通过文件IO操作设备节点
假设设备路径 /dev/led0
open read write loctl close -> /dev/led0
二、字符设备驱动编写流程
字符设备操作接口
函数名 | 功能 | 特点 |
---|---|---|
register_chrdev_region | 静态注册已知设备号范围 | 需提前确定主设备号,适合已知空闲设备号的情况 |
alloc_chrdev_region | 动态分配设备号 | 内核自动分配主设备号,避免冲突 |
unregister_chrdev_region | 释放注册的设备号 | 配合上述两个函数使用 |
register_chrdev | 注册设备号并关联file_operations | 一次性完成注册和操作关联,但灵活性差 |
unregister_chrdev | 注销设备号 | 与register_chrdev配套使用 |
1. 用 alloc_chrdev_region让系统自动分配主设备号,并占用该主设备号开头的若干设备编号(相当于申请设备号资源)。
2. 用 cdev_alloc 申请cdev 结构(cdev 是内核里描述字符设备的核心结构体,用来管理字符设备)。
3. 用 cdev_add 把申请到的 cdev 结构,加入内核的字符设备管理列表(让内核能识别、管理这个字符设备)。
4. 自己创建设备对应的设备节点(比如在 `/dev` 目录下生成 `/dev/xxx` 这样的文件,应用程序后续通过这个节点访问设备)。
应用层创建mknod /dev/led0/ c 248 0
5.初始化结构体file_operations(read,write...)。
6.class_creat sys文件系统中创建设备类型,device_creat sys文件系统中创建设备(mdev -s检测内核插拔事件,发送uevet,mdev会创建)
7. 编写应用层代码,通过 open、read、write 等操作,和字符设备交互。
三、Linux中查看内核驱动信息的常用命令
1.cat /proc/devices 查看设备类型
2.ls /sys/class 查看sys文件系统中的设备类型
ls /sys/class/myled/led0 查看myled设备类型下的led0
3.cat /sys/firmware/devicetree/base/节点名 查看当前系统中所有注册的中断号信息
4.cat /proc/interrupts 查看当前系统中所有注册的中断号信息
5. insmod module_drv.ko,加载驱动
rmmod moudule_drv.ko,删除
6.make -C
是 GNU Make 工具中的一个命令行选项,用于在执行 make 命令时指定工作目录。-C
是 --directory
的简写形式,后跟一个目录路径,表示在该目录下执行 make 操作。
7.devm_ioremap建立映射后,会自动解除映射
device_create_file创建调试节点
device_remove_file卸载调试节点
四、代码
led
1.led_app.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>int main(void)
{int fd = 0;int status = 0;int curstat = 0;fd = open("/dev/led0", O_RDWR);if (-1 == fd){perror("fail to open");return -1;}while (1){status = 1;write(fd, &status, sizeof(status));read(fd, &curstat, sizeof(curstat));printf("curstat = %d\n", curstat);sleep(1);status = 0;write(fd, &status, sizeof(status));read(fd, &curstat, sizeof(curstat));printf("curstat = %d\n", curstat);sleep(1);}/*status = 1;write(fd, &status, sizeof(status));*/close(fd);return 0;
}
2.Makefile
#模块名
modulename := led_app#工具链
CC := arm-linux-gnueabihf-gcc all:$(CC) $(modulename).c -o $(modulename)cp $(modulename) ~/nfs/rootfs .PHONY:
distclean:rm $(modulename)rm ~/nfs/rootfs/$(modulename)
clean:rm $(modulename)
3.led_drv.c
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/mutex.h>#define IMX6ULL_SW_MUX_CTL 0x20E0068
#define IMX6ULL_SW_PAD_CTL 0x20E02F4
#define IMX6ULL_GPIO_DIR 0x209C004
#define IMX6ULL_GPIO_DAT 0x209C000static int led_open(struct inode *node, struct file *fp);
static ssize_t led_write(struct file *fp, const char __user *puser, size_t n, loff_t *off);
static int led_release(struct inode *node, struct file *fp);
static ssize_t led_read(struct file *fp, char __user *puser, size_t n, loff_t *off);static dev_t devno = 0;
static int status = 0;
static struct cdev *pcdev = NULL;
static void __iomem *pmuxreg = NULL;
static void __iomem *ppadreg = NULL;
static void __iomem *pgpiodir = NULL;
static void __iomem *pgpiodat = NULL;
static struct class *pclass = NULL;
static struct device *pdevice = NULL;
static struct mutex lock;static ssize_t led_show(struct device *dev, struct device_attribute *attr, char *buf)
{int nret = 0;mutex_lock(&lock);nret = sprintf(buf, "%s\n", status ? "LED_ON" : "LED_OFF");mutex_unlock(&lock);return nret;
}static ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{char tmpbuff[32] = {0};sscanf(buf, "%s", tmpbuff);mutex_lock(&lock);if (!strcmp(tmpbuff, "LED_ON")) {writel((readl(pgpiodat) & (~(0x1 << 3))), pgpiodat);status = 1;} else if (!strcmp(tmpbuff, "LED_OFF")) {writel((readl(pgpiodat) | (0x1 << 3)), pgpiodat);status = 0;}mutex_unlock(&lock);return count;
}static struct device_attribute led_attr = __ATTR(attr, 0664, led_show, led_store);static struct file_operations fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,
};static int led_open(struct inode *node, struct file *fp)
{pr_info("drv:led_open\n");return 0;
}static ssize_t led_write(struct file *fp, const char __user *puser, size_t n, loff_t *off)
{unsigned long nret = 0;int status = 0;mutex_lock(&lock);nret = copy_from_user(&status, puser, n);if (nret) {pr_info("copy_from_user failed\n");return -1;}if (status) {writel((readl(pgpiodat) & (~(0x1 << 3))), pgpiodat);} else {writel((readl(pgpiodat) | (0x1 << 3)), pgpiodat);}mutex_unlock(&lock);pr_info("drv:led_write\n");return 0;
}static int led_release(struct inode *node, struct file *fp)
{pr_info("drv:led_release\n");return 0;
}static ssize_t led_read(struct file *fp, char __user *puser, size_t n, loff_t *off)
{unsigned long nret = 0;mutex_lock(&lock);nret = copy_to_user(puser, &status, sizeof(status));if (nret) {pr_info("copy_to_user failed\n");return -1;}mutex_unlock(&lock);pr_info("drv:led_read\n");return 0;
}static __init int led_drv_init(void)
{int ret = 0;mutex_init(&lock);//申请主设备号,及此设备号ret = alloc_chrdev_region(&devno, 0, 1, "myled");if (ret) {pr_info("alloc_chrdev_region failed\n");return -1;}pr_info("major:%d\n", MAJOR(devno));//构建一个新的设备类型pcdev = cdev_alloc();if (!pcdev) {pr_info("cdev_alloc failed\n");return -1;}pcdev->ops = &fops;//将新的cdev加入到字符设备类型列表中ret = cdev_add(pcdev, devno, 1);if (ret) {pr_info("cdev_add failed\n");return -1;}//sys文件系统中创建设备类型pclass = class_create(THIS_MODULE, "myled");if (NULL == pclass) {pr_info("class_create failed\n");return -1;}//sys文件系统中创建设备pdevice = device_create(pclass, NULL, devno, NULL, "led0");if (NULL == pdevice) {pr_info("device_create failed\n");return -1;}//映射寄存器地址pmuxreg = ioremap(IMX6ULL_SW_MUX_CTL, 4);if (NULL == pmuxreg) {pr_info("ioremap failed\n");return -1;}ppadreg = ioremap(IMX6ULL_SW_PAD_CTL, 4);if (NULL == ppadreg) {pr_info("ioremap failed\n");return -1;}pgpiodir = ioremap(IMX6ULL_GPIO_DIR, 4);if (NULL == pgpiodir) {pr_info("ioremap failed\n");return -1;}pgpiodat = ioremap(IMX6ULL_GPIO_DAT, 4);if (NULL == pgpiodat) {pr_info("ioremap failed\n");return -1;}//对寄存器赋值writel(((readl(pmuxreg) & ~0xf) | 0x5), pmuxreg);writel(0x10B0, ppadreg);writel((readl(pgpiodir) | (0x1 << 3)), pgpiodir);writel((readl(pgpiodat) | (0x1 << 3)), pgpiodat);pr_info("led drv init success\n");return 0;
}static __exit void led_drv_exit(void)
{iounmap(pmuxreg);iounmap(ppadreg);iounmap(pgpiodir);iounmap(pgpiodat);device_remove_file(pdevice, &led_attr);device_destroy(pclass, devno);cdev_del(pcdev);unregister_chrdev_region(devno, 1);mutex_destroy(&lock);pr_info("led drv exit success\n");return;
}module_init(led_drv_init);
module_exit(led_drv_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("pute");
4.Makefile
#模块名
modulename := led_drv#内核路径
kerdir := /home/linux/imx6ull/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek#当前目录路径
pwd := $(shell pwd)#加入模块
obj-m += $(modulename).oall:make -C $(kerdir) modules M=$(pwd)cp $(modulename).ko ~/nfs/rootfs .PHONY:
distclean:make -C $(kerdir) modules M=$(pwd) cleanrm ~/nfs/rootfs/$(modulename).ko
clean:make -C $(kerdir) modules M=$(pwd) clean
5.Makefile
modulename := led_all:make -C $(modulename)appmake -C $(modulename)drv.PHONY:
distclean:make -C $(modulename)app distcleanmake -C $(modulename)drv distclean
clean:make -C $(modulename)app cleanmake -C $(modulename)drv clean
beep
1.beep_app.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>int main(void)
{int fd = 0;int status = 0;fd = open("/dev/beep0", O_RDWR);if (-1 == fd){perror("fail to open");return -1;}while (1){status = 1;write(fd, &status, sizeof(status));sleep(1);status = 0;write(fd, &status, sizeof(status));sleep(1);}/*status = 1;write(fd, &status, sizeof(status));*/close(fd);return 0;
}
2.Makefile
#模块名
modulename := beep_app#工具链
CC := arm-linux-gnueabihf-gcc all:$(CC) $(modulename).c -o $(modulename)cp $(modulename) ~/nfs/rootfs .PHONY:
distclean:rm $(modulename)rm ~/nfs/rootfs/$(modulename)
clean:rm $(modulename)
3.beep_drv.c
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>#define SW_MUX_CTL 0x229000C
#define SW_PAD_CTL 0x2290050
#define GPIO_DIR 0x20AC004
#define GPIO_DAT 0x20AC000static int beep_open(struct inode *node, struct file *fp);
static ssize_t beep_write(struct file *fp, const char __user *puser, size_t n, loff_t *off);
static int beep_release(struct inode *node, struct file *fp);
static ssize_t beep_read(struct file *fp, char __user *puser, size_t n, loff_t *off);static dev_t devno = 0;
static struct cdev *pcdev = NULL;
static void __iomem *pmuxreg = NULL;
static void __iomem *ppadreg = NULL;
static void __iomem *pgpiodir = NULL;
static void __iomem *pgpiodat = NULL;
static struct class *pclass = NULL;
static struct device *pdevice = NULL;extern void __iomem *devm_ioremap(struct device *dev, resource_size_t offset, resource_size_t size);static struct file_operations fops = {.owner = THIS_MODULE,.open = beep_open,.read = beep_read,.write = beep_write,.release = beep_release,
};static int beep_open(struct inode *node, struct file *fp)
{pr_info("drv:beep_open\n");return 0;
}static ssize_t beep_write(struct file *fp, const char __user *puser, size_t n, loff_t *off)
{unsigned long nret = 0;int status = 0;nret = copy_from_user(&status, puser, n);if (nret) {pr_info("copy_from_user failed\n");return -1;}if (status) {writel((readl(pgpiodat) & (~(0x1 << 1))), pgpiodat);} else {writel((readl(pgpiodat) | (0x1 << 1)), pgpiodat);}pr_info("drv:beep_write\n");return 0;
}static int beep_release(struct inode *node, struct file *fp)
{pr_info("drv:beep_release\n");return 0;
}static ssize_t beep_read(struct file *fp, char __user *puser, size_t n, loff_t *off)
{pr_info("drv:beep_read\n");return 0;
}static __init int beep_drv_init(void)
{int ret = 0;//申请主设备号,及此设备号ret = alloc_chrdev_region(&devno, 0, 1, "mybeep");if (ret) {pr_info("alloc_chrdev_region failed\n");return -1;}pr_info("major:%d\n", MAJOR(devno));//构建一个新的设备类型pcdev = cdev_alloc();if (!pcdev) {pr_info("cdev_alloc failed\n");return -1;}pcdev->ops = &fops;//将新的cdev加入到字符设备类型列表中ret = cdev_add(pcdev, devno, 1);if (ret) {pr_info("cdev_add failed\n");return -1;}//sys文件系统中创建设备类型pclass = class_create(THIS_MODULE, "mybeep");if (NULL == pclass) {pr_info("class_create failed\n");return -1;}//sys文件系统中创建设备pdevice = device_create(pclass, NULL, devno, NULL, "beep0");if (NULL == pdevice) {pr_info("device_create failed\n");return -1;}//映射寄存器地址// pmuxreg = ioremap(IMX6ULL_SW_MUX_CTL, 4);// if (NULL == pmuxreg) {// pr_info("ioremap failed\n");// return -1;// }// ppadreg = ioremap(IMX6ULL_SW_PAD_CTL, 4);// if (NULL == ppadreg) {// pr_info("ioremap failed\n");// return -1;// }// pgpiodir = ioremap(IMX6ULL_GPIO_DIR, 4);// if (NULL == pgpiodir) {// pr_info("ioremap failed\n");// return -1;// }// pgpiodat = ioremap(IMX6ULL_GPIO_DAT, 4);// if (NULL == pgpiodat) {// pr_info("ioremap failed\n");// return -1;// }/*devm_ioremap自动解除映射*/pmuxreg = devm_ioremap(pdevice, SW_MUX_CTL, 4);if (NULL == pmuxreg) {pr_info("devm_ioremap failed\n");return -1;}ppadreg = devm_ioremap(pdevice, SW_PAD_CTL, 4);if (NULL == ppadreg) {pr_info("devm_ioremap failed\n");return -1;}pgpiodir = devm_ioremap(pdevice, GPIO_DIR, 4);if (NULL == pgpiodir) {pr_info("devm_ioremap failed\n");return -1;}pgpiodat = devm_ioremap(pdevice, GPIO_DAT, 4);if (NULL == pgpiodat) {pr_info("devm_ioremap failed\n");return -1;}//对寄存器赋值writel(((readl(pmuxreg) & ~0xf) | 0x5), pmuxreg);writel(0x10B0, ppadreg);writel((readl(pgpiodir) | (0x1 << 1)), pgpiodir);writel((readl(pgpiodat) | (0x1 << 1)), pgpiodat);pr_info("beep drv init success\n");return 0;
}static __exit void beep_drv_exit(void)
{// iounmap(pmuxreg);// iounmap(ppadreg);// iounmap(pgpiodir);// iounmap(pgpiodat);device_destroy(pclass, devno);class_destroy(pclass);/*cdev_del(pcdev);unregister_chrdev_region(devno, 1);*/pr_info("beep drv exit success\n");return;
}module_init(beep_drv_init);
module_exit(beep_drv_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("pute");
4.Makefile
#模块名
modulename := beep_drv#内核路径
kerdir := /home/linux/imx6ull/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek#当前目录路径
pwd := $(shell pwd)#加入模块
obj-m += $(modulename).oall:make -C $(kerdir) modules M=$(pwd)cp $(modulename).ko ~/nfs/rootfs .PHONY:
distclean:make -C $(kerdir) modules M=$(pwd) cleanrm ~/nfs/rootfs/$(modulename).ko
clean:make -C $(kerdir) modules M=$(pwd) clean
5.Makefile
modulename := beep_all:make -C $(modulename)appmake -C $(modulename)drv.PHONY:
distclean:make -C $(modulename)app distcleanmake -C $(modulename)drv distclean
clean:make -C $(modulename)app cleanmake -C $(modulename)drv clean