嵌入式Linux設備驅動開發(二)

上一篇中介紹到設備驅動如何匹配設備以及綁定設備的,在Linux系統下進行註冊,這裏將繼續介紹probe函數的功能。
5、probe函數
Probe()函數必須驗證指定設備的硬件是否真的存在,probe()可以使用設備的資源,包括時鐘,platform_data等。一般來說設備是不能被熱插拔的,所以可以將probe()函數放在init段裏面來節省driver運行時候的內存開銷。

probe函數在設備驅動註冊最後收尾工作,當設備的device 和其對應的driver 在總線上完成配對之後,系統就調用platform設備的probe函數完成驅動註冊最後工作。資源、中斷調用函數以及其他相關工作。

probe函數接收到plarform_device這個參數後,就需要從中提取出需要的信息。它一般會通過調用內核提供的platform_get_resource和platform_get_irq等函數來獲得相關信息。如通過platform_get_resource獲得設備的起始地址後,可以對其進行request_mem_region和ioremap等操作,以便應用程序對其進行操作。通過platform_get_irq得到設備的中斷號以後,就可以調用request_irq函數來向系統申請中斷。這些操作在設備驅動程序中一般都要完成。
在完成了上面這些工作和一些其他必須的初始化操作後,就可以向系統註冊我們在/dev目錄下能看在的設備文件了。

/**
 * irfpa_drv_probe -  Probe call for the device.
 //*針對設備探測驅動
 * @pdev:   handle to the platform device structure.
 //*參數是平臺設備的結構體。分配存儲區,註冊設備
 * It does all the memory allocation and registration for the device.//0成功,其他負數
 * Returns 0 on success, negative error otherwise.
 **/
 static int __devinit irfpa_drv_probe(struct platform_device *pdev)
{
    struct resource *irfpa_regs_res;
    struct resource *vdma_regs_res;
    struct resource *vbuf_mem_res;
    struct resource *xinfo_mem_res;
    struct irfpa_drvdata *drvdata;
    dev_t devt;
    int retval;
    //參考設備樹文件中對設備包括的幾個地址區間
    irfpa_regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    vdma_regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    vbuf_mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
    xinfo_mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 3);

    if ((!irfpa_regs_res) || (!vdma_regs_res) || (!vbuf_mem_res) || (!xinfo_mem_res)) {
        dev_err(&pdev->dev, "Invalid address.\n");
        return -ENODEV;
    }

/*組合設備號符合dev_t類型
    devt = MKDEV(IRFPA_MAJOR, IRFPA_MINOR);
    retval = register_chrdev_region(devt, IRFPA_DEVICES, DRIVER_NAME);
    if (retval < 0)
        return retval;
*/
    retval = alloc_chrdev_region(&devt, IRFPA_MINOR, IRFPA_DEVICES, DRIVER_NAME);//動態分配設備編號,該函數需要傳遞給它指定的第一個次設備號firstminor(一般爲0)和要分配的設備數count,以及設備名,調用該函數後自動分配得到的設備號保存在dev中。
    if (retval < 0) {
        dev_err(&pdev->dev, "alloc_chrdev_region fail.\n");
        return retval;
    }

    drvdata = kzalloc(sizeof(struct irfpa_drvdata), GFP_KERNEL);//用kzalloc申請內存的時候, 效果等同於先是用 kmalloc() 申請空間 , 然後用 memset() 來初始化 ,所有申請的元素都被初始化爲 0.GFP_KERNEL 內核內存的正常分配. 可能睡眠.
    if (!drvdata) {
        dev_err(&pdev->dev, "Couldn't allocate device private record.\n");
        retval = -ENOMEM;
        goto failed0;
    }

    dev_set_drvdata(&pdev->dev, (void *)drvdata);

    drvdata->devt = devt;
    drvdata->is_open = 0;

    mutex_init(&drvdata->sem);

    // == request_mem_region
    if (!request_mem_region(irfpa_regs_res->start, irfpa_regs_res->end - irfpa_regs_res->start + 1, DRIVER_NAME)) {
        dev_err(&pdev->dev, "Couldn't lock memory region at %Lx\n", (unsigned long long) irfpa_regs_res->start);
        retval = -EBUSY;
        goto failed1_1;
    }

    if (!request_mem_region(vdma_regs_res->start, vdma_regs_res->end - vdma_regs_res->start + 1, DRIVER_NAME)) {
        dev_err(&pdev->dev, "Couldn't lock memory region at %Lx\n", (unsigned long long) vdma_regs_res->start);
        retval = -EBUSY;
        goto failed1_2;
    }

    if (!request_mem_region(vbuf_mem_res->start, vbuf_mem_res->end - vbuf_mem_res->start + 1, DRIVER_NAME)) {
        dev_err(&pdev->dev, "Couldn't lock memory region at %Lx\n", (unsigned long long) vbuf_mem_res->start);
        retval = -EBUSY;
        goto failed1_3;
    }

    if (!request_mem_region(xinfo_mem_res->start, xinfo_mem_res->end - xinfo_mem_res->start + 1, DRIVER_NAME)) {
        dev_err(&pdev->dev, "Couldn't lock memory region at %Lx\n", (unsigned long long) xinfo_mem_res->start);
        retval = -EBUSY;
        goto failed1_4;
    }

    // ==== ioremap ====
    drvdata->irfpa_base_address = ioremap(irfpa_regs_res->start, (irfpa_regs_res->end - irfpa_regs_res->start + 1));
    if (!drvdata->irfpa_base_address) {
        dev_err(&pdev->dev, "irfpa_reg_res ioremap() failed\n");
        goto failed2_1;
    }

    drvdata->vdma_base_address = ioremap(vdma_regs_res->start, (vdma_regs_res->end - vdma_regs_res->start + 1));
    if (!drvdata->vdma_base_address) {
        dev_err(&pdev->dev, "vdma_reg_res ioremap() failed\n");
        goto failed2_2;
    }

    drvdata->vbuf_base_address = ioremap(vbuf_mem_res->start, (vbuf_mem_res->end - vbuf_mem_res->start + 1));
    if (!drvdata->vbuf_base_address) {
        dev_err(&pdev->dev, "vbuf_mem_res ioremap() failed\n");
        goto failed2_3;
    }
    drvdata->vbuf_current_address = drvdata->vbuf_base_address;

    drvdata->xinfo_base_address = ioremap(xinfo_mem_res->start, (xinfo_mem_res->end - xinfo_mem_res->start + 1));
    if (!drvdata->xinfo_base_address) {
        dev_err(&pdev->dev, "xinfo_mem_res ioremap() failed\n");
        goto failed2_4;
    }

    // ==== print ioremap info ===
    dev_info(&pdev->dev, "ioremap %llx to %p with size %llx\n",
         (unsigned long long) irfpa_regs_res->start,
         drvdata->irfpa_base_address,
         (unsigned long long) (irfpa_regs_res->end - irfpa_regs_res->start + 1));
    dev_info(&pdev->dev, "ioremap %llx to %p with size %llx\n",
         (unsigned long long) vdma_regs_res->start,
         drvdata->vdma_base_address,
         (unsigned long long) (vdma_regs_res->end - vdma_regs_res->start + 1));
    dev_info(&pdev->dev, "ioremap %llx to %p with size %llx\n",
         (unsigned long long) vbuf_mem_res->start,
         drvdata->vbuf_base_address,
         (unsigned long long) (vbuf_mem_res->end - vbuf_mem_res->start + 1));
    dev_info(&pdev->dev, "ioremap %llx to %p with size %llx\n",
         (unsigned long long) xinfo_mem_res->start,
         drvdata->xinfo_base_address,
         (unsigned long long) (xinfo_mem_res->end - xinfo_mem_res->start + 1));

    cdev_init(&drvdata->cdev, &irfpa_fops);
    drvdata->cdev.owner = THIS_MODULE;
    drvdata->cdev.ops = &irfpa_fops;
    retval = cdev_add(&drvdata->cdev, devt, 1);
    if (retval) {
        dev_err(&pdev->dev, "cdev_add() failed\n");
        goto failed3;
    }

    /* create sysfs files for the device */
    retval = sysfs_create_group(&(pdev->dev.kobj), &irfpa_attr_group);
    if (retval) {
        dev_err(&pdev->dev, "Failed to create sysfs attr group\n");
        goto failed4;
    }

    irfpa_device_init(drvdata->irfpa_base_address, drvdata->vdma_base_address, vbuf_mem_res->start);

    drvdata->irfpa_xinfo_pinpon = irfpa_readreg(drvdata->irfpa_base_address + IRFPA_XINFO_PINPON);
    drvdata->nuc_buffer_pinpon = 1;

    return 0;       /* Success */

 failed4:
    cdev_del(&drvdata->cdev);

 failed3:
    iounmap(drvdata->xinfo_base_address);

 failed2_4:
    iounmap(drvdata->vbuf_base_address);

 failed2_3:
    iounmap(drvdata->vdma_base_address);

 failed2_2:
    iounmap(drvdata->irfpa_base_address);

 failed2_1:
    release_mem_region(xinfo_mem_res->start, xinfo_mem_res->end - xinfo_mem_res->start + 1);

 failed1_4:
    release_mem_region(vbuf_mem_res->start, vbuf_mem_res->end - vbuf_mem_res->start + 1);

 failed1_3:
    release_mem_region(vdma_regs_res->start, vdma_regs_res->end - vdma_regs_res->start + 1);

 failed1_2:
    release_mem_region(irfpa_regs_res->start, irfpa_regs_res->end - irfpa_regs_res->start + 1);

 failed1_1:
    kfree(drvdata);

 failed0:
    unregister_chrdev_region(devt, IRFPA_DEVICES);

    return retval;
}

a、struct resource *irfpa_regs_res;

struct resource {
                const char *name;
                unsigned long start, end;
                unsigned long flags;
                struct resource *parent, *sibling, *child;
             };

linux採用struct resource結構體來描述一個掛接在cpu總線上的設備實體(32位cpu的總線地址範圍是0~4G):
resource->start描述設備實體在cpu總線上的線性起始物理地址;
resource->end -描述設備實體在cpu總線上的線性結尾物理地址;
resource->name 描述這個設備實體的名稱,這個名字開發人員可以隨意起,但最好貼切;
resource->flag 描述這個設備實體的一些共性和特性的標誌位;
只需要瞭解一個設備實體的以上4項,linux就能夠知曉這個掛接在cpu總線的上的設備實體的基本使用情況,也就是[resource->start, resource->end]這段物理地址現在是空閒着呢,還是被什麼設備佔用着呢?
linux會堅決避免將一個已經被一個設備實體使用的總線物理地址區間段[resource->start, resource->end],再分配給另一個後來的也需要這個區間段或者區間段內部分地址的設備實體,進而避免設備之間出現對同一總線物理地址段的重複引用,而造成對唯一物理地址的設備實體二義性.

以上的4個屬性僅僅用來描述一個設備實體自身,或者是設備實體可以用來自治的單元,但是這不是linux所想的,linux需要管理4G物理總線的所有空間,所以掛接到總線上的形形色色的各種設備實體,這就需要鏈在一起,因此resource結構體提供了另外3個成員:指針parent、sibling和child:分別爲指向父親、兄弟和子資源的指針。      
以root source爲例,root->child(*pchild)指向root所有孩子中地址空間最小的一個;pchild->sibling是兄弟鏈表的開頭,指向比自己地址空間大的兄弟。

物理內存頁面是重要的資源。從另一個角度看,地址空間本身,或者物理存儲器在地址空間中的位置,也是一種資源,也要加以管理 -- resource管理地址空間資源。

內核中有兩棵resource樹,一棵是iomem_resource, 另一棵是ioport_resource,分別代表着兩類不同性質的地址資源。兩棵樹的根也都是resource數據結構,不過這兩個數據結構描述的並不是用於具體操作對象的地址資源,而是概念上的整個地址空間。
將主板上的ROM空間納入iomem_resource樹中;系統固有的I/O類資源則納入ioport_resource樹  

b、irfpa_regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

struct resource *platform_get_resource(struct platform_device *dev,
                                   unsigned int type, unsigned int num)
{
       int i;

       for (i = 0; i < dev->num_resources; i++) {
              struct resource *r = &dev->resource[i];//不管你是想獲取哪一份資源都從第一份資源開始搜索。

              if (type == resource_type(r) && num-- == 0)//首先通過type == resource_type(r)判斷當前這份資源的類型是否匹配,如果匹配則再通過num-- == 0判斷是否是你要的,如果不匹配重新提取下一份資源而不會執行num-- == 0這一句代碼。
                     return r;
       }
       return NULL;
}

unsigned int type決定資源的類型,unsigned int num決定type類型的第幾份資源(從0開始)。即使同類型資源在資源數組中不是連續排放也可以定位得到該資源。
函數進入for裏面,i=0,num_resources=7,拿出resource[0]資源。resource_type(r)提取出該份資源 的資源類型並與函數傳遞下來的資源類型進行比較,匹配。Num=0(這裏先判斷是否等於0再自減1)符合要求,從而返回該資源。

c、retval = alloc_chrdev_region(&devt, IRFPA_MINOR, IRFPA_DEVICES, DRIVER_NAME);//動態分配設備編號,該函數需要傳遞給它指定的第一個次設備號firstminor(一般爲0)和要分配的設備數count,以及設備名,調用該函數後自動分配得到的設備號保存在dev中。

d、drvdata = kzalloc(sizeof(struct irfpa_drvdata), GFP_KERNEL);//用kzalloc申請內存的時候, 效果等同於先是用 kmalloc() 申請空間 , 然後用 memset() 來初始化 ,所有申請的元素都被初始化爲 0.GFP_KERNEL 內核內存的正常分配. 可能睡眠.

e、dev_set_drvdata(&pdev->dev, (void *)drvdata);
將ndev保存成平臺總線設備的私有數據,提取用dev_get_drvdata

int dev_set_drvdata(struct device *dev, void *data)  
{  
         int error;  

         if (!dev->p) {  
                 error = device_private_init(dev);  
                 if (error)  
                         return error;  
         }  
         dev->p->driver_data = data;  
         return 0;  
 }

從代碼中,我們可以看到此函數主要是把drvdata賦給了device->p->driver_data指針。那麼,我們下面來看一下,Kernel中比較重要的Device結構體,它其實是對內核中所有設備的抽象表示。 所有的設備都有一個device實例與之對應,而且Device結構體的主要用法爲將其嵌入到其他的設備結構體中,如platform_device等。同時,Device結構體也負責作爲子系統之間交互的統一參數。

device結構體的構成。

include/linux/device.h
struct device {
     struct device *parent;
     struct device_private *p;  //負責保存driver核心部分的數據
     struct kobject kobj;
     const char *init_name;
     .......
     struct device_driver *driver;
#ifdef CONFIG_PINCTRL
     struct dev_pin_info *pins;
#endfi
     .......
     struct device_node *of_node;  //負責保存device_tree中相應的node地址
     .......
     const struct attribute_group **groups;
     ......
}

struct device_private {
     struct klist klist_children;
     struct klist_node knode_parent;
     struct klist_node knode_driver;
     struct klist_node knode_bus;
     struct list_head deferred_probe;
     void *driver_data;        //負責保存driver中相應的driver_data
     struct device *device;
}

那麼,driver_data是何時進行初始化的呢?我們通過追蹤代碼是可以發現,一般driver_data的初始化是發生在Driver文件中的probe函數中的。

在probe函數中,malloc完相應的driver data結構體,填充完相應的域後,就會將driver data的地址賦值給driver data。這樣,在實現與其他子系統交互的接口時,就能通過其他子系統傳遞過來的device指針來找到相應的driver data。

f、mutex_init(&drvdata->sem);
初始化這個sem爲互斥鎖

g、request_mem_region(irfpa_regs_res->start, irfpa_regs_res->end - irfpa_regs_res->start + 1, DRIVER_NAME)) (irfpa_regs_res->start, irfpa_regs_res->end - irfpa_regs_res->start + 1, DRIVER_NAME))

#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))
//__request_region檢查是否可以安全佔用起始物理地址S1D_PHYSICAL_REG_ADDR之後的連續S1D_PHYSICAL_REG_SIZE字節大小空間

一般來說,在系統運行時,外設的I/O內存資源的物理地址是已知的,由硬件的設計決定。但是CPU通常並沒有爲這些已知的外設I/O內存資源的物理地址預定義虛擬地址範圍,驅動程序並不能直接通過物理地址訪問I/O內存資源,而必須將它們映射到核心虛地址空間內(通過頁表),然後才能根據映射所得到的核心虛地址範圍,通過訪內指令訪問這些I/O內存資源。Linux在io.h頭文件中聲明瞭函數ioremap(),用來將I/O內存資源的物理地址映射到核心虛地址空間。
但要使用I/O內存首先要申請,然後才能映射,使用I/O端口首先要申請,或者叫請求,對於I/O端口的請求意思是讓內核知道你要訪問這個端口,這樣內核知道了以後它就不會再讓別人也訪問這個端口了.畢竟這個世界僧多粥少啊.申請I/O端口的函數是request_region, 申請I/O內存的函數是request_mem_region。request_mem_region函數並沒有做實際性的映射工作,只是告訴內核要使用一塊內存地址,聲明佔有,也方便內核管理這些資源。

h、drvdata->irfpa_base_address = ioremap(irfpa_regs_res->start, (irfpa_regs_res->end - irfpa_regs_res->start + 1));
ioremap主要是檢查傳入地址的合法性,建立頁表(包括訪問權限),完成物理地址到虛擬地址的轉換。如果出錯,iounmap函數用於取消ioremap()所做的映射。

void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
void *ioremap(unsigned long phys_addr, unsigned long size)
入口: phys_addr:要映射的起始的IO地址;
size:要映射的空間的大小;
flags:要映射的IO空間的和權限有關的標誌;
phys_addr:是要映射的物理地址
size:是要映射的長度,單位是字節
頭文件:io.h

功能:將一個IO地址空間映射到內核的虛擬地址空間上去,便於訪問;
實現:對要映射的IO地址空間進行判斷,低PCI/ISA地址不需要重新映射,也不允許用戶將IO地址空間映射到正在使用的RAM中,最後申請一個 vm_area_struct結構,調用remap_area_pages填寫頁表,若填寫過程不成功則釋放申請的vm_area_struct空間;
ioremap 依靠 __ioremap實現,它只是在__ioremap中以第三個參數爲0調用來實現.
ioremap是內核提供的用來映射外設寄存器到主存的函數,我們要映射的地址已經從pci_dev中讀了出來(上一步),這樣就水到渠成的成功映射了而不會和其他地址有衝突。映射完了有什麼效果呢,我舉個例子,比如某個網卡有100 個寄存器,他們都是連在一塊的,位置是固定的,假如每個寄存器佔4個字節,那麼一共400個字節的空間被映射到內存成功後,ioaddr就是這段地址的開頭(注意ioaddr是虛擬地址,而mmio_start是物理地址,它是BIOS得到的,肯定是物理地址,而保護模式下CPU不認物理地址,只認虛擬地址),ioaddr+0就是第一個寄存器的地址,ioaddr+4就是第二個寄存器地址(每個寄存器佔4個字節),以此類推,我們就能夠在內存中訪問到所有的寄存器進而操控他們了。

在將I/O內存資源的物理地址映射成核心虛地址後,理論上講我們就可以象讀寫RAM那樣直接讀寫I/O內存資源了。爲了保證驅動程序的跨平臺的可移植性,我們應該使用Linux中特定的函數來訪問I/O內存資源,而不應該通過指向核心虛地址的指針來訪問。

i、cdev_init(&drvdata->cdev, &irfpa_fops);

 struct cdev {
   struct kobject kobj;          // 每個 cdev 都是一個 kobject
   struct module *owner;       // 指向實現驅動的模塊
   const struct file_operations *ops;   // 操縱這個字符設備文件的方法
   struct list_head list;       // 與 cdev 對應的字符設備文件的 inode->i_devices 的鏈表頭
   dev_t dev;                   // 起始設備編號
   unsigned int count;       // 設備範圍號大小
};
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
   memset(cdev, 0, sizeof *cdev);
   INIT_LIST_HEAD(&cdev->list);
   kobject_init(&cdev->kobj, &ktype_cdev_default);
   cdev->ops = fops;
}

一個 cdev 一般它有兩種定義初始化方式:靜態的和動態的。

靜態內存定義初始化:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
動態內存定義初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;

這裏採用靜態初始化:

cdev_init(&drvdata->cdev, &irfpa_fops);
drvdata->cdev.owner = THIS_MODULE;
drvdata->cdev.ops = &irfpa_fops;

初始化 cdev 後,需要把它添加到系統中去。爲此可以調用 cdev_add() 函數。傳入 cdev 結構的指針,起始設備編號,以及設備編號範圍。
retval = cdev_add(&drvdata->cdev, devt, 1);

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
   p->dev = dev;
   p->count = count;
   return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}

設備驅動程序通過調用cdev_add把它所管理的設備對象的指針嵌入到一個類型爲struct probe的節點之中,然後再把該節點加入到cdev_map所實現的哈希鏈表中。
使用cdev_add註冊字符設備前應該先調用register_chrdev_region或alloc_chrdev_region分配設備號。alloc_chrdev_region申請一個動態主設備號,並申請一系列次設備號。baseminor爲起始次設備號,count爲次設備號的數量。註銷設備號(cdev_del)後使用unregister_chrdev_region。
內核中所有都字符設備都會記錄在一個 kobj_map 結構的 cdev_map 變量中。這個結構的變量中包含一個散列表用來快速存取所有的對象。kobj_map() 函數就是用來把字符設備編號和 cdev 結構變量一起保存到 cdev_map 這個散列表裏。當後續要打開一個字符設備文件時,通過調用 kobj_lookup() 函數,根據設備編號就可以找到 cdev 結構變量,從而取出其中的 ops 字段。
這裏寫圖片描述
對系統而言,當設備驅動程序成功調用了cdev_add之後,就意味着一個字符設備對象已經加入到了系統,在需要的時候,系統就可以找到它。對用戶態的程序而言,cdev_add調用之後,就已經可以通過文件系統的接口呼叫到我們的驅動程序。

static const struct file_operations irfpa_fops = {
    .owner = THIS_MODULE,
    .write = irfpa_write,
    .read = irfpa_read,
    .open = irfpa_open,
    .release = irfpa_release,
};

j、retval = sysfs_create_group(&(pdev->dev.kobj), &irfpa_attr_group);
sysfs是用於表現設備驅動模型的文件系統,它基於ramfs。

sysfs文件系統中提供了四類文件的創建與管理,分別是目錄、普通文件、軟鏈接文件、二進制文件。目錄層次往往代表着設備驅動模型的結構,軟鏈接文件則代表着不同部分間的關係。比如某個設備的目錄只出現在/sys/devices下,其它地方涉及到它時只好用軟鏈接文件鏈接過去,保持了設備唯一的實例。而普通文件和二進制文件往往代表了設備的屬性,讀寫這些文件需要調用相應的屬性讀寫。
sysfs是表現設備驅動模型的文件系統,它的目錄層次實際反映的是對象的層次。爲了配合這種目錄,linux專門提供了兩個結構作爲sysfs的骨架,它們就是struct kobject和struct kset。我們知道,sysfs是完全虛擬的,它的每個目錄其實都對應着一個kobject,要想知道這個目錄下有哪些子目錄,就要用到kset。從面向對象的角度來講,kset繼承了kobject的功能,既可以表示sysfs中的一個目錄,還可以包含下層目錄。

sysfs_create_group()在kobj目錄下創建一個屬性集合,並顯示集合中的屬性文件。如果文件已存在,會報錯。
sysfs_update_group()在kobj目錄下創建一個屬性集合,並顯示集合中的屬性文件。文件已存在也不會報錯。sysfs_update_group()也用於group改動影響到文件顯示時調用。
sysfs_remove_group()在kobj目錄下刪除一個屬性集合,並刪除集合中的屬性文件。
sysfs_add_file_to_group()將一個屬性attr加入kobj目錄下已存在的的屬性集合group。
sysfs_remove_file_from_group()將屬性attr從kobj目錄下的屬性集合group中刪除。
更詳細的參考我的另一篇博客[DEVICE_ATTR分析](http://blog.csdn.net/chuhang_zhqr/article/details/50174813)
可以使得可以在用戶空間直接對驅動的這些變量讀寫或調用驅動的某些函數。

通 過以上簡單的幾個步驟,就可以在終端查看到接口了。當我們將數據 echo 到接口中時,在上層實際上完成了一次 write 操 作,對應到 kernel ,調用了驅動中的 “wirte”。同理,當我們cat 一個 接口時則會調用 “show” 。到這裏,只是簡單的建立 了 應用 層到 kernel 的橋樑,真正實現對硬件操作的,還是在 “show” 和 “set” 中完成的。

至此,已經註冊設備驅動程序,並且對設備的資源進行申請,映射,並設置瞭如何操作四個存儲器接口。
irfpa_regs_res使用對sys目錄下的設備集進行echo和cat就可以對這個寄存器進行讀寫操作了。
vdma_regs_res和 vbuf_mem_res是被設置成字符型設備了irfpa,在/dev下面,可以根據irfpa_fops的設置對該設備接口進行操作。
xinfo_mem_res直接在probe程序中設置瞭如何操作:
drvdata->irfpa_xinfo_pinpon = irfpa_readreg(drvdata->irfpa_base_address + IRFPA_XINFO_PINPON);

在系統的目錄下,就可以找到一個字符型設備和一些寄存器設備集合。

k、irfpa_device_init(drvdata->irfpa_base_address, drvdata->vdma_base_address, vbuf_mem_res->start);
這是probe函數的最後一步,也是整個驅動程序的最後一步。初始化設備寄存器,這是對設備進行初始化操作,開機啓動時對整個設備的控制寄存器進行初始化,控制設備合理運行。

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