Linux設備驅動學習三 設備節點的生成和調用:雜項設備驅動的註冊和調用

設備註冊,驅動註冊和生成設備節點的區別:

生成設備節點是對上的,爲了應用程序可以調用和驅動進行通信。

設備註冊,驅動註冊是驅動嵌入到內核中。

 

關於雜項設備
雜項設備(設備號10)
對一部分字符設備的封裝,還有一部分不好歸類驅動也歸到了雜項設備
爲什麼引入雜項設備

節省主設備號
如果所有的驅動都是用字符設備,那麼所有的設備號很快就用完了,總共255個設備號
驅動寫起來相對簡單
如果直接使用封裝好的雜項設備,那麼就可以減少一步註冊主設備號的過程

1.區分概念:

    設備註冊:platform_device,查看:/sys/devices/platform

    驅動註冊:platform_driver

    生成設備節點:爲了讓應用程序可以調用,是"對上"的接口。

                             與設備註冊無關,設備節點名稱不需要與設備名稱相同。查看:/dev/*

                             一般將設備節點的註冊放在probe中,也可以放到init函數中。

    雜項設備,或者說是對一部分字符設備的封裝或者一部分不好分類的。

    可以節省主設備號,驅動寫起來相對簡單(用封裝好的雜項設備可以減少一步註冊主設備號的過程)

Linux設備驅動一般分爲:字符設備,塊設備,網絡設備。也可以按照子系統來分類,如:mm,led等

2.源代碼位置

    雜項設備初始化源代碼:/drivers/char/misc.c,屬於內核中強制編譯的。

    雜項設備註冊頭文件:include/linux/miscdevice.h,主要的結構體爲miscdevice

struct miscdevice  {
        int minor;     //設備號,一般系統分配隨機取值
        const char *name;     //生成設備節點的名稱,無限制
        const struct file_operations *fops;     //指向一個設備節點文件,對文件的操作
        struct list_head list;
        struct device *parent;
        struct device *this_device;
        const char *nodename;
        mode_t mode;
};

extern int misc_register(struct miscdevice * misc);    //生成設備節點的函數,一般在probe中被調用
extern int misc_deregister(struct miscdevice *misc);

雜項設備內核文件結構體,設備節點本質是文件一個特殊文件,包括文件名,打開關閉等,文件結構體的頭文件爲include/linux/fs.h,結構體爲file_operations(非常重要)

//相當重要的結構體
/*
 * NOTE:
 * all file operations except setlease can be called without
 * the big kernel lock held in all filesystems.
 */
struct file_operations {
        struct module *owner;        //必選參數,一般是THIS_MODULE
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
/* remove by cym 20130408 support for MT660.ko */
#if 0
//#ifdef CONFIG_SMM6260_MODEM
#if 1// liang, Pixtree also need to use ioctl interface...
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
#endif
#endif
/* end remove */
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);     //非必選,對GPIO操作,應用向底層驅動傳值的
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);            //必選的參數,打開文件函數
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);          //必選的參數,關閉文件函數
        int (*fsync) (struct file *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
/* add by cym 20130408 support for MT6260 and Pixtree */
#if defined(CONFIG_SMM6260_MODEM) || defined(CONFIG_USE_GPIO_AS_I2C)
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);    //此處ioctl與上面的不一樣
#endif
/* end add */
};

參數很多,根據要求選擇,必選的是

  • .owner 一般是THIS_MODULE
  • .open 打開文件函數
  • .release 關閉文件函數
    這裏在必選之外使用參數
  • .unlocked_ioctl對GPIO操作,應用向底層驅動傳值

3. 驅動代碼案例

 在probe_linux_module基礎上寫devicenode_linux_module驅動,注意函數調用順序,/dev下查看

devicenode_linux_module.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
//misc
#include <linux/miscdevice.h>
//註冊設備節點的文件結構體
#include <linux/fs.h>

#define DRIVER_NAME "hello_ctl"
#define DEVICE_NAME "hello_ctl123"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("NANZH");

static int hello_open(struct inode *inode, struct file *file){
    printk(KERN_EMERG "hello open\n");        //調用步驟4-1
    return 0;
}
static int hello_release(struct inode *inode, struct file *file){
    printk(KERN_EMERG "hello release\n");        //調用步驟4-2
    return 0;
}
static long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
    //打開之後操作的,所以參數裏面不需要inode
    printk(KERN_EMERG "hello ioctl\n");           //調用步驟4-3
    printk("cmd is %d, arg is %d\n", cmd, arg);
    return 0;
}
//文件設備操作結構體
static struct file_operations hello_ops = {
    .owner = THIS_MODULE,
     // int (*open) (struct inode *, struct file *);
    .open = hello_open,                     //調用步驟3-1
         // int (*release) (struct inode *, struct file *)
    .release = hello_release,               //調用步驟3-2
     // long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    .unlocked_ioctl = hello_ioctl,          //調用步驟3-3
};
//雜項設備結構體
static struct miscdevice hello_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &hello_ops,       //調用步驟2
};


static int hello_probe(struct platform_device *pdv){
    printk(KERN_EMERG "initialized\n");
    misc_register(&hello_dev);      //一般在probe中調用.調用步驟1
    return 0;
}
static int hello_remove(struct platform_device *pdv){
    printk(KERN_EMERG "remove\n");
    misc_deregister(&hello_dev);
    return 0;
}
static void hello_shutdown(struct platform_device *pdv){
    ;
}
static int hello_suspend(struct platform_device *pdv, pm_message_t pmt){
    return 0;
}
static int hello_resume(struct platform_device *pdev){
    return 0;
}

struct platform_driver hello_driver = {
    .probe = hello_probe,
    .remove = hello_remove,
    .shutdown = hello_shutdown,
    .suspend = hello_suspend,
    .resume = hello_resume,
    .driver = {
        .name = DRIVER_NAME,
        .owner = THIS_MODULE,
    }
};

static int hello_init(void)
{
    int DriverState;
    printk(KERN_EMERG "hello world enter!\n");
    DriverState=platform_driver_register(&hello_driver);
    printk(KERN_EMERG "DriverState is %d\n", DriverState);
        return 0;
}

static void hello_exit(void)
{
    printk(KERN_EMERG "hello world exit!\n");
    platform_driver_unregister(&hello_driver); //unregister會查找release,如果找不到會報錯
}

module_init(hello_init);
module_exit(hello_exit);

代碼分析
接下來我們對代碼進行簡要的分析,下面的部分和我們前面註冊驅動的代碼是基本相同的,我們在hello_probe (也就是驅動的初始化函數)中調用了misc_register()來註冊雜項設備節點,雜項設備節點就像是一個掛載在設備上的設備一樣,它的本質也是一個設備,所以說如果註冊成功我們應該可以在/dev/中查看到我們註冊的設備hello_ctl_dev。我們在hello_remove中調用了misc_deregister()來卸載雜項設備節點,說明我們rmmod這個模塊後/dev/中的hello_ctl_dev就不存在了
Makefile

obj-m += devicenode_linux_module.o

KDIR := /home/nan/iTOP4412/iTop4412_Kernel_3.0

PWD ?= $(shell pwd)

all:
  make -C $(KDIR) M=$(PWD) modules

clean:
  rm -rf *.o

測試結果:

# 板子上:
root@iTOP4412-ubuntu-desktop:# insmod devicenode_linux_module.ko
[  112.202074] hello world enter!
[  112.203980] initialized
[  112.236250] DriverState is 0
查看生成的設備節點:
root@iTOP4412-ubuntu-desktop:# ls -l /dev/hello_ctl123
crw------- 1 root root 10, 46 Nov 22 05:55 /dev/hello_ctl123
root@iTOP4412-ubuntu-desktop:# rmmod devicenode_linux_module
[  136.243713] hello world exit!
[  136.246474] remove

以上

編寫簡單應用調用驅動
基本原理
在前面我們生成了設備節點,而且也給設備節點註冊了內核文件結構體,同時在內核文件結構體中我們也註冊了open、close、ioctl的函數,我們做這些的目的是什麼呢,當然是將這些函數供給上層的應用使用,這樣才能完成我們驅動開發的使命。
當然,Linux一切皆文件的準則大大的方便了我們的操作,我們只要把/dev/中的設備節點作爲一個文件進行操作那麼就可以調用我們的驅動, Linux系統調用中的文件操作被映射爲我們所寫的驅動函數(這個過程還不是特別的瞭解),我們調用系統調用的函數,實質上執行了我們驅動中的函數。
繼編寫簡單的應用來調用設備節點:

根據上面生成的/dev/hello_ctl123節點來調用,基於應用層的,所以使用基本的C文件形式。

4. 函數頭文件

    # include <stdio.h>     //printf

    # include <sys/types.h>   //基本系統數據類型,依據編譯環境保持32位值還是64位值

    # include <sys/stat.h>    //系統調用頭文件,如目錄、管道、socket、字符、塊的屬性等

    # include <fcntl.h>    //定義了open函數

    # include <unistd>   //定義了close函數

    # include <sys/ioctl.h>    //定義了ioctl函數

編譯器使用的是arm-2009q3,即arm-none-linux-gnueabi-gcc。編譯使用的頭文件在編譯器的libc下,如:

    /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/usr/include/fcntl.h

5. 函數

    open:返回文件描述符

    ioctl:應用向驅動傳值

    close:關閉文件

編譯方法:arm-none-linux-gnueabi-gcc -o invoke_hello123 invoke_hello123.c

6.詳細的C文件:invoke_hello123.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main()
{
    int fd;
    char *hello_node = "/dev/hello_ctl123";  //設備節點的路徑

    fd = open(hello_node, O_RDWR|O_NDELAY);  //只讀,非阻塞方式
    if (fd < 0)
        perror("open");
    else{
        printf("App open %s success\n",hello_node);
        ioctl(fd, 1, 6);
    }

    close(fd);
    return 0;
}

在開發板上運行./invoke_hello123出錯的log和解決如下

# ERROR
root@iTOP4412-ubuntu-desktop:~/tests/3# ./invoke_hello123 
-bash: ./invoke_hello123: No such file or directory
解決方法:https://blog.csdn.net/qq_22863619/article/details/80294232
iTOP4412-ubuntu-desktop:~/tests/3# readelf -a invoke_hello123  | grep Requesting 
      [Requesting program interpreter: /lib/ld-linux.so.3]

由此發現缺少的是庫文件:/lib/ld-linux.so.3
遂到toolchain中查找:
nan@nanzh:~/iTOP4412/4-1$ find /usr/local/arm/arm-2009q3/ -iname "ld-linux.so.3"
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/ld-linux.so.3
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/thumb2/lib/ld-linux.so.3
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/armv4t/lib/ld-linux.so.3
nan@nanzh:~/iTOP4412/4-1$ ls -l /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/ld-linux.so.3
lrwxrwxrwx 1 nan nan 12 10月 17  2009 /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/ld-linux.so.3 -> ld-2.10.1.so
於是將ld-2.10.1.so拷貝到開發板中並設置軟連接
root@iTOP4412-ubuntu-desktop:# cp /mnt/ld-2.10.1.so /lib
root@iTOP4412-ubuntu-desktop:/lib# ln -s ld-2.10.1.so ld-linux.so.3

#ERROR 2
root@iTOP4412-ubuntu-desktop:~/tests/3# ./invoke_hello123 
./invoke_hello123: error while loading shared libraries: libgcc_s.so.1: cannot open shared object file: No such file or directory
方法:
nan@nanzh:~/iTOP4412/4-1$ find /usr/local/arm/arm-2009q3/ -iname "libgcc_s.so.1"
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libgcc_s.so.1
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/thumb2/lib/libgcc_s.so.1
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/armv4t/lib/libgcc_s.so.1
nan@nanzh:~/iTOP4412/4-1$ ls -l /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libgcc_s.so.1
-rw-r--r-- 1 nan nan 69371 10月 17  2009 /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libgcc_s.so.1
拷貝到板子上繼續
root@iTOP4412-ubuntu-desktop:~/tests/3# cp /mnt/libgcc_s.so.1 /lib/
root@iTOP4412-ubuntu-desktop:~/tests/3# ./invoke_hello123 
./invoke_hello123: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory

nan@nanzh:~/iTOP4412/4-1$ find /usr/local/arm/arm-2009q3/ -iname "libc.so.6"
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libc.so.6
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/thumb2/lib/libc.so.6
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/armv4t/lib/libc.so.6
nan@nanzh:~/iTOP4412/4-1$ ls -l /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libc.so.6
lrwxrwxrwx 1 nan nan 14 10月 17  2009 /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libc.so.6 -> libc-2.10.1.so
root@iTOP4412-ubuntu-desktop:~/tests/3# mount /dev/sdb3 /mnt
root@iTOP4412-ubuntu-desktop:~/tests/3# cp /mnt/libc-2.10.1.so /lib
root@iTOP4412-ubuntu-desktop:~/tests/3# cd /lib
root@iTOP4412-ubuntu-desktop:/lib# ln -s libc-2.10.1.so libc.so.6

以上總結,缺少庫:/lib/ld-linux.so.3,libgcc_s.so.1,libc.so.6

使用readelf查看的具體內容如下。(ldd用於動態庫)

重點區分

設備節點是“對上”的,爲了讓應用程序可以調用
一定注意生成設備節點和設備註冊沒有關係,而且設備節點名稱不需要和設備名稱相同
一般情況下,是將設備節點註冊放到probe中,但是放到init函數中的驅動也是有的

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章