目錄
- 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");