前言 字元設備是Linux驅動中三大設備之一,字元(char)設備是個能夠像位元組流(類似文件)一樣被訪問的設備,由字元設備驅動程式來實現這種特性。字元設備驅動程式通常至少要實現open、close、read和write的系統調用。字元終端(/dev/console)和串口(/dev/ttyS0以及類 ...
前言
字元設備是Linux驅動中三大設備之一,字元(char)設備是個能夠像位元組流(類似文件)一樣被訪問的設備,由字元設備驅動程式來實現這種特性。字元設備驅動程式通常至少要實現open、close、read和write的系統調用。字元終端(/dev/console)和串口(/dev/ttyS0以及類似備)就是兩個字元設備,它們能很好的說明“流”這種抽象概念。字元設備可以通過文件節點來訪問,比如/dev/tty1和/dev/lp0等。這些設備文件和普通文件之間的唯一差別在於對普通文件的訪問可以前後移動訪問位置,而大多數字元設備是一個只能順序訪問的數據通道。然而,也存在具有數據區特性的字元設備,訪問它們時可前後移動訪問位置。
一、字元設備的編寫步驟
1.實現入口函數xxx_init()和卸載函數xxx_exit()
2.申請設備號register_chrdev(與內核有關)
3.註冊字元設備驅動cdev_alloc cdev_init cdev_add(與內核有關)
4.利用udev/mdev機制創建設備文件(節點),
class_create,device_create(與內核有關)
5.硬體部分初始化 io資源映射ioremap,內核提供gpio庫函數(與硬體相關)
註冊中斷(與硬體相關)
初始化等待隊列(與內核有關)
初始化定時器(與內核有關)
6.構建file_operation結構(與內核相關)
實現操作硬體方法xxx_open,xxx_read,xxx_write...(與硬體有關)
二、字元設備應用
1.視頻教學中的使用
1.實現模塊載入和卸載入口函數
module_init(chr_dev_init);
module_exit(chr_dev_exit);
2.在模塊載入入口函數中
A.申請主設備號(內核中用於區分和管理不通風字元設備)
register_chrdev(unsigned int major, const char * name, const struct file_operations * fops
B.創建設備節點文件(為用戶提供喲個課操作到文件介面---open());
struct class class_create(owner, name)
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
C.硬體的初始化
1.地址的映射
gpx2conf = ioremp(GPX2_CON,GPX2_SIZE);
2.中斷的申請
3.實現硬體的寄存器的初始化
//需要1配置gpio功能為輸出
*gpx2conf &= ~(0xf<<28);
*gpx2conf |=(0x1<<28);
D.實現file_operations
const struct file_operations my_fops= {
.open = chr_drv_open,
.read = chr_drv_read,
.write = chr_drv_write,
.release = chr_drv_close,
};
規範:
1.
//0,實例化全局的設備對象--分配空間
//GFP_KERNEL如果當前空間記憶體不夠用的時候,該函數會一直阻塞(休眠)
//#include <linux/slab.h>
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNE);
If(led_dev == NULL)
{
printk(KERN_ERR “malloc error\n”);
return -ENOMEM;
}
led_dev->dev_major = 250;
2.做出錯處理
在某個位置出錯了,要將之前申請到的資源進行釋放
led_dev->dev_major = register_che_dev(0, “led_dev_test”,&my_fops);
if(led_dev->dev_major < 0)
{
printk(KERN_ERR “register_chrdev error\n”);
ret = -ENODEV;
goto ↓err_0;
}
err_0:
kfree(led_dev);
return ret;
2.個人觀點
按照視頻的來最終在使用時,可能會遇到一些bug,比如:
在使用kzalloc申請記憶體的時候總是申請失敗,在參考網上的解決辦法後依舊未能解決,實例化對象需要用到kzalloc這一種方法在我學習的時候,並沒有得到驗證,始終卡在記憶體申請這兒,讓後我使用傳統方法,才達到自己想要的結果。
總結
字元設備驅動編寫方法有多種,其中區別主要在於硬體資源的獲取:傳統字元設備驅動編寫;平臺匯流排;設備樹。前面兩種本質上區別其實並不大,但引入了分層的思想。後續將慢慢和大家分享。
附錄
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
/* 寄存器物理地址 */
#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 volatile unsigned int *IMX6U_CCM_CCGR1;
static volatile unsigned int *SW_MUX_GPIO1_IO03;
static volatile unsigned int *SW_PAD_GPIO1_IO03;
static volatile unsigned int *GPIO1_DR;
static volatile unsigned int *GPIO1_GDIR;
//靜態指定
struct led_desc {
unsigned int dev_major;//設備號
struct class *cls;
struct device *dev;//創建設備文件
/* 映射後的寄存器虛擬地址指針 */
};
struct led_desc *led_dev;
static int kernel_val = 555;
//read(fd,buf,size);
ssize_t chr_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
int ret;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
ret = copy_to_user(buf, &kernel_val, count);
if(ret > 0)
{
printk("failed copy_to_user\r\n");
return -EFAULT;
}
return 0;
}
ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
int ret;
int value;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
ret = copy_from_user(&value, buf, count);
if(ret > 0)
{
printk("failed copy_from_user\r\n");
return -EFAULT;
}
if(value){
*GPIO1_DR |= (1 << 3);
}else{
*GPIO1_DR &= ~(1 << 3);
}
return 0;
}
int chr_drv_open (struct inode *inode, struct file *filp)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
int chr_drv_close (struct inode *inode, struct file *filp)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
const struct file_operations my_fops = {
.open = chr_drv_open,
.read = chr_drv_read,
.write = chr_drv_write,
.release = chr_drv_close,
};
/*
②在模塊載入入口函數中
a 申請主設備號(內核中用於區分和管理不同字元設備)
register_chrdev()
b 創建設備節點文件(為用戶提供一個可操作到文件介面--open())
struct class *class_create()
struct device *device)create()
c 硬體初始化
1.地址映射
2.中斷申請
3.實現硬體的寄存器的初始化
d 實現file_operation
*/
static int __init chr_dev_init(void)
{
int ret;
//0 實例化全局的設備對象
led_dev = kzalloc(sizeof(struct led_desc), GFP_KERNEL);
if(led_dev == NULL);
{
printk("kmalloc failed\r\n");
return -ENOMEM;
}
//一般都是申請設備號資源
//1.申請設備號
led_dev->dev_major = register_chrdev(0, "chr_dev_test", &my_fops);
if(led_dev->dev_major < 0){
printk(KERN_ERR"register_chrdev failed\n");
ret = -ENODEV;
goto err_0;
}
//2.創建設備文件
led_dev->cls = class_create(THIS_MODULE, "chr_cls");
if(IS_ERR(led_dev->cls))
{
printk(KERN_ERR"class_create failed\n");
ret = PTR_ERR(led_dev->cls);//將指針出錯原因轉換為一個出錯碼
goto err_1;
}
// /dev/led0
led_dev->dev = device_create(led_dev->cls, NULL, MKDEV(led_dev->dev_major, 0), NULL, "led%d", 0);
if(IS_ERR(led_dev->dev))
{
printk(KERN_ERR"device_create failed\n");
ret = PTR_ERR(led_dev->dev);//將指針出錯原因轉換為一個出錯碼
goto err_2;
}
//3,硬體初始化
/* 初始化LED */
/* 1、寄存器地址映射 */
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);
/* enable GPIO1
* configure GPIO1_io3 as gpio
* configure GPIO1_io3 as output
*/
*IMX6U_CCM_CCGR1 |= 0x0C000000;
*SW_MUX_GPIO1_IO03 &= 0xfffffff0;
*SW_MUX_GPIO1_IO03 |= 0x5;
*SW_PAD_GPIO1_IO03 &= 0xfffffff0;
*SW_PAD_GPIO1_IO03 |= 0x10B0;
*GPIO1_GDIR &= 0xfffffff0;
*GPIO1_GDIR |= 0x8;
/*
readl writel
static inline u32 readl(const volatile void __iomem *addr)
static inline void writel(u32 value, void *addr)
2、使能GPIO1時鐘
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); 清楚以前的設置
val |= (3 << 26); 設置新值
writel(val, IMX6U_CCM_CCGR1);
3、設置GPIO1_IO03的復用功能,將其復用為
GPIO1_IO03,最後設置IO屬性。
writel(5, SW_MUX_GPIO1_IO03);
寄存器SW_PAD_GPIO1_IO03設置IO屬性
bit 16:0 HYS關閉
bit [15:14]: 00 預設下拉
bit [13]: 0 kepper功能
bit [12]: 1 pull/keeper使能
bit [11]: 0 關閉開路輸出
bit [7:6]: 10 速度100Mhz
bit [5:3]: 110 R0/6驅動能力
bit [0]: 0 低轉換率
writel(0x10B0, SW_PAD_GPIO1_IO03);
4、設置GPIO1_IO03為輸出功能
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); 清除以前的設置
val |= (1 << 3); 設置為輸出
writel(val, GPIO1_GDIR);
5、預設關閉LED
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
或
writel(readl(led_dev->GPIO1_DR) | (1 << 3),led_dev->GPIO1_DR);
*/
return 0;
err_2:
class_destroy(led_dev->cls);
err_1:
unregister_chrdev(led_dev->dev_major, "chr_dev_test");
err_0:
kfree(&led_dev);
return ret;
return ret;
}
static void __exit chr_dev_exit(void)
{
//一般都是釋放資源
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
class_destroy(led_dev->cls);
unregister_chrdev(led_dev->dev_major, "chr_dev_test");
}
//①實現模塊的載入和卸載入口函數
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");