目录
- 1.简介
- 2.前置知识
- 2.1 重要函数及结构体
- 2.2 程序框架流程
- 3. 代码详解:
1.简介
在上节,我对linux-IMX6ULL-字符设备驱动简单框架实验进行了说明和构建,但是也存在几个问题;
- 需要手动指定设备号,不能自动申请;
- 需要在linux端手动创造设备节点,也就是要用maknod命令;
- 没有引入实际设备;
因此这节内容就根据上节的驱动框架,然后结合LED,实现设备号的自动分配和设备节点的自动创建;
2.前置知识
由于本篇博客不属于教程类博客,只是作为学习总结和复盘,因此先把相关的重点知识给提前说明,也能起到一个便于快速回顾的目的;
2.1 重要函数及结构体
下面的函数均进行了实参带入,具体原定义可以参考源码;
static void __iomem *IMX6U_CCM_CCGR1;
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
register_chrdev_region(newchrled.devid,NEWCHRLED_COUNT,NEWCHRLED_NAME);
alloc_chrdev_region(&newchrled.devid,0,NEWCHRLED_COUNT,NEWCHRLED_NAME);
struct cdev cdev;
struct class *class;
struct device *device;
cdev_init(&newchrled.cdev, &newchrled_fops);
cdev_add(&newchrled.cdev, newchrled.devid,1);
class_create(THIS_MODULE, NEWCHRLED_NAME);
device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
2.2 程序框架流程
3. 代码详解:
注意几个点:
- 在写驱动程序时不能直接操控物理寄存器,我们只能操控虚拟化的地址,然后虚拟化的地址通过映射间接操控真实的寄存器;
- 操控虚拟化的寄存器地址时是通过
read(),write()
函数来完成的,不能直接赋值; - 我们接收用户端的写的数据时要通过
copy_from_user(databuf,buffer,count)
函数来实现,不能直接获取; - 注意出口函数里面的注销和删除顺序是有要求的,我们最开始是先注册的设备号,然后注册操作结构体,但是我们在出口函数里面是先删除操作结构体,然后再删除设备号,注意顺序是有要求的,其它也是一样的;
#define LED_MAJOR 200
#define NEWCHRLED_NAME "newchrled1"
#define NEWCHRLED_COUNT 1
/*物理地址*/
#define CCM_CCGR1_BASE (0x020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0x020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0x020E02F4)
#define GPIO1_DR_BASE (0x0209C000)
#define GPIO1_GDIR_BASE (0x0209C004)
/*虚拟地址,这些地址用于存储物理地址映射的虚拟地址*/
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/*宏定义,开关*/
#define LEDOFF 0
#define LEDON 1
/**LED 设备结构体**/
struct newchrled_dev{
struct cdev cdev; /*创建设备结构体*/
struct class *class; /*返回值都是指针类型*/
struct device *device; /*创建设备的返回值,是个结构体指针*/
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
};
/*创建LED设备的结构体,这里没有初始化*/
struct newchrled_dev newchrled;
/*led开关函数封装*/
void led_switch(u8 sta)
{
u32 val=0;
if(sta==LEDON){
val = readl(GPIO1_DR);
val &= ~(1<<3);
writel(val,GPIO1_DR);
}
if(sta==LEDOFF){
val = readl(GPIO1_DR);
val |= (1<<3);
writel(val,GPIO1_DR);
}
}
/*led初始化封装*/
void led_inti(void)
{
unsigned int val = 0;
/*把物理地址进行虚拟化映射,映射完后把虚拟地址赋值给前面定义的虚拟地址*/
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);
/*开时钟*/
val=readl(IMX6U_CCM_CCGR1);
val &= ~(3<<26);/*clear*/
val |= (3<<26);/*set bit 27 26 into 1*/
writel(val,IMX6U_CCM_CCGR1);
/*配置寄存器*/
writel(0x5,SW_MUX_GPIO1_IO03);
writel(0x10B0,SW_PAD_GPIO1_IO03);
val = readl(GPIO1_GDIR);
val |= (1<<3);
writel(val,GPIO1_GDIR);
}
static int newchrled_release(struct inode *inode, struct file *file)
{
printk("Close ok\r\n");
//struct newchrled_dev *dev=(struct newchrled_dev*)file->private_data;
return 0;
}
static int newchrled_open(struct inode *inode, struct file *file)
{
printk("Open ok\r\n");
//file->private_data = &newchrled;
return 0;
}
static ssize_t newchrled_write(struct file *file, const char __user *buffer,size_t count, loff_t *pos)
{
unsigned int retvalue;
unsigned char databuf[1];
/*从用户哪里获取写入的数据,这里不能直接获得,要通过下面的函数进行获得*/
retvalue=copy_from_user(databuf,buffer,count);
if(retvalue<0)
{
printk("Kernel write failed!\r\n");
return -EFAULT;
}
/*判断是开灯还是关灯*/
led_switch(databuf[0]);
return 0;
}
static const struct file_operations newchrled_fops={
.owner = THIS_MODULE,
.write = newchrled_write,
.open = newchrled_open,
.release = newchrled_release,
};
/**into**/
static int __init newchrled_init(void)
{
int ret = 0;
printk("newchrled init!\r\n");
/*1.初始化LED灯,地址映射*/
led_inti();
/*2.注册设备号*/
newchrled.major = 0;
if(newchrled.major){
newchrled.devid = MKDEV(newchrled.major,0);
ret = register_chrdev_region(newchrled.devid,NEWCHRLED_COUNT,NEWCHRLED_NAME);
}else{
ret = alloc_chrdev_region(&newchrled.devid,0,NEWCHRLED_COUNT,NEWCHRLED_NAME);
newchrled.major = MAJOR(newchrled.devid);
newchrled.minor = MINOR(newchrled.devid);
}
if(ret<0){
printk("newchrled chrdev err!\r\n");
return -1;
}
printk("major=%d,minor=%d\r\n",newchrled.major,newchrled.minor);
/*3 注册操作函数*/
newchrled.cdev.owner=THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
cdev_add(&newchrled.cdev, newchrled.devid,1);/*添加到linux内核中*/
// 第二步和第三歩本来在前两节是通过下面的函数实现的:
// register_chrdev(LED_MAJOR, LED_NAME,&led_fops);
// 这里改写成了改写成了两步,第一步是申请设备号,第二步是注册设备操作函数
/*4.自动创建设备节点*/
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if (IS_ERR(newchrled.class)){
return PTR_ERR(newchrled.class);
}
/*5.创建一个设备*/
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid,NULL,NEWCHRLED_NAME);
if (IS_ERR(newchrled.device))
{
return PTR_ERR(newchrled.device);
}
return 0;
}
/**exit**/
static void __exit newchrled_exit(void)
{
printk("newchrled exit!\r\n");
/*1.注销字符操作函数*/
cdev_del(&newchrled.cdev);
/*2.注销设备号*/
unregister_chrdev_region(newchrled.devid,NEWCHRLED_COUNT);
/*3.先摧毁设备*/
device_destroy(newchrled.class, newchrled.devid);
/*4.后摧毁类*/
class_destroy(newchrled.class);
}
module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");