1. 程式人生 > >字元裝置驅動----LED驅動程式

字元裝置驅動----LED驅動程式

一. 概念介紹

一般使用者在應用程式裡呼叫的 open, read, write 函式是 c 庫的函式,
這些函式會觸發 swi val異常,從而引發系統呼叫,進入到核心空間,
核心通過VFS(virtual Filesystem)來實現呼叫不同的驅動函式。
這裡寫圖片描述
例如:我們有一個函式,

int main()
{
    int fd1, fd2;
    int val = 1;

    fd1 = open("/dev/led", O_RDWR);
    write(fd1, &val, 4);

    fd2 = open("hello.txt", O_RDWR);
    write(fd2, &val
, 4); }

函式裡相同的open、write函式,引發的不同的行為,一個是操控硬體,一個是寫檔案。
簡單的呼叫關係如下:
使用者 –> 系統呼叫 –> 驅動程式
open –> sys.open –> led.open
write –> sys.write –> led.write

二. 字元裝置驅動框架

實現步驟:
1. 實現驅動的 led.open, led.write, led.read 操作
2. 定義file_operations結構體, 把驅動函式填充到裡面
3. 把這個結構告訴核心, 通個函式 register_chrdev(major, “first_drv”, &first_drv_fops) 來實現
4. 誰來呼叫註冊函式 –>驅動的入口函式來呼叫這個註冊函式, first_drv_init
5. 修飾一下這個函式入口函式,module_init(first_drv_init)

//第一步:驅動功能實現
static int first_drv_open(struct inode *inode, struct file *file)
{
    //printk("first_drv_open\n");
    return 0;
}

static ssize_t first_drv_write(struct file *file, 
        const char __user *buf, 
        size_t count, 
        loff_t * ppos)
{
    //printk("first_drv_write\n");
    return
0; } //第二步:定義結構體,並把驅動函式填充進去 static struct file_operations first_drv_fops = { .owner = THIS_MODULE, .open = first_drv_open, .write = first_drv_write, }; //第四步:實現驅動入口函式 int major; static int first_drv_init(void) { //第三步:把結構體告訴核心 major = register_chrdev(0, "first_drv", &first_drv_fops);// 註冊告訴核心 return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 解除安裝 } //第五步:修飾入口函式,及退出函式 module_init(first_drv_init); module_exit(first_drv_exit);

三. 關聯 [裝置號] 與 [裝置節點]

裝置號要與裝置結點關聯起來,才能通過open(“/dev/xyz”)方便的操作。
1. 設定主裝置號
驅動程式可以自動分配主裝置號, 也可以手工指定

// 設定為 0 時是系統自動分配主裝置號
major = register_chrdev(0, "first_drv", &first_drv_fops); 
// 通過 [cat /proc/device] 看一下系統為我們的first_drv分配的裝置號是多少

// 手動分配 111主裝置號給 first_drv
register_chrdev(111, "first_drv", &first_drv_fops); 

2. 設定裝置節點
當應該程式 執行 open(“/dev/xyz”) 操作時,這個/dev/xyz怎麼來的
2.1 手動建立

// 建立裝置節點
mknod /dev/xyz c(表示是字元裝置) 主裝置號 次裝置號

//檢視裝置資訊:
ls -l /dev/xyz

2.2 自動建立
mdev – 根據系統資訊建立裝置節點

int major;
static int first_drv_init(void)
{
    major = register_chrdev(0, "first_drv", &first_drv_fops);

    //建立裝置資訊,執行後會出現 /sys/class/firstdrv
    firstdrv_class = class_create(THIS_MODULE, "firstdrv"); 

    //建立裝置節點,就是根據上面的裝置資訊來的
    firstdrv_class_dev = class_device_create(firstdrv_class,
    NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */

    return 0;
}

static void first_drv_exit(void)
{
    unregister_chrdev(major, "first_drv");

    //刪除節點及資訊
    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
}

四. 完善LED驅動

完善LED驅動,也就是硬體的操作。
微控制器中可以直接操作實體地址,但在驅動裡只能操作虛擬地址
虛擬地址怎麼來的,用 ioremap( ) 函式來對映,對映完後操作虛擬地址就像操作實體地址一樣。

static int first_drv_init(void)
{
    major = register_chrdev(0, "first_drv", &first_drv_fops);
    firstdrv_class = class_create(THIS_MODULE, "firstdrv");
    firstdrv_class_dev = class_device_create(firstdrv_class,
    NULL, MKDEV(major, 0), NULL, "xyz");

    //對映 GPIO 的實體地址 0x56000050 到虛擬地址, gpfcon操作的是虛擬地址
    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); 
    gpfdat = gpfcon + 1;

    return 0;
}

對映完後,就可以操作這些地址,來控制硬體暫存器

static int first_drv_open(struct inode *inode, struct file *file)
{
    //printk("first_drv_open\n");
    /* 配製GPF4為輸出 */
    *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
    *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
    return 0;
}

另外,當用戶空間的資料要傳到核心空間裡,同樣不能直接用,
也要通過 copy_from_user() 函式,把使用者空間的值傳到核心空間裡。

static ssize_t first_drv_write(struct file *file, 
    const char __user *buf, 
    size_t count, loff_t * ppos)
{
    int val;

    //printk("first_drv_write\n");

    //把使用者空間的值 copy 給核心空間
    copy_from_user(&val, buf, count); //    copy_to_user();

    if (val == 1)
    {
        // 點燈
        *gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
    }
    else
    {
        // 滅燈
        *gpfdat |= (1<<4) | (1<<5) | (1<<6);
    }

    return 0;
}