2014年8月13日 星期三

一個簡單的字元裝置(3)

    上次留了一個問題還沒回答,user space的process要如何知道每個驅動的ioctl的命令號是多少?回想一下命令號是怎麼被建立出來的

_IO(type, nr)
_IOR(typr, nr, size)
_IOW(type, nr, size)
_IORW(type, nr, size)

    可以看到需要傳入幾個參數,type(也有人說他是一個magic number), nr(一個8 bits的數字), size(傳給ioctl()第三個參數arg的大小),另外不要忘了,還有dir(read和write的動作),這四個巨集會把這四個參數照一個順序排列形成一個32 bits的命令號,排列的順序如下圖:


知道了這個規則後,我們就能直接建立自己的命令號了,在記憶體字元裝置驅動裡面用的是

#define CHRDEV_TYPE 'k'
#define CHRDEV_ERASE _IO(CHRDEV_TYPE, 0x50)
#define CHRDEV_RDWR _IO(CHRDEV_TYPE, 0x51)

得到兩個命令號,現在改用上述的排序規則把他們建立出來,type在這邊是'k'這個字母的ASCII code,也就是0x6b, nr分別是0x50和0x51,沒有size和dir,所以兩個命令號會是

CHRDEV_ERASE: 0x6b50
CHRDEV_RDWR: 0x6b51

    因此不管在驅動中或是在process都可套用相同規則,建立出來命令號彼此都可以認得,而且不會跟系統中已經有的命令號衝突。如果想驗證一下自己建立的命令號跟原來的是不是一樣,可以用printk()輸出用_IO()建立的命令號到klog,再用dmesg指令查看。

    剛好這邊提到獲得ioctl命令的資訊,那就順便來講講獲得裝置資訊的方法吧!還記得在/sys每個裝置的目錄下有一些屬性可以獲取裝置的資訊,這邊也用這個方法show出我們記憶體裝置的ID, 記憶體大小和ioctl cmd的資訊。kernel提供了一個方法讓我們可以建立自己的屬性

DEVICE_ATTR(_name, _mode, _show, _store)

這個巨集會建立屬於裝置的屬性結構(struct dev_attr_##_name),_name為屬性名稱,_mode為存取權限(S_IRUSR,S_IWUSR...),一般當用cat指令讀這個屬性時會用到_show方法,寫的時候會用到_store方法。因為我們目前只提供裝置資訊,所以只需要實現_show方法,_show方法是將資訊寫到一個buffer裡面後回傳總共寫的多少bytes。

static ssize_t chrdev_show_info(struct device *dev, struct device_attribute *attr, char *buf)
{
    struct chrdev_inst *inst = dev_get_drvdata(dev);

    return snprintf(buf, PAGE_SIZE, "ID: %d\nMemory size: %d bytes\nioctl command:\n\tCHRDEV_ERASE: %#x\n\tCHRDEV_RDWR :%#x\n",c_dev.id, inst->size,CHRDEV_ERASE, CHRDEV_RDWR);
}


static DEVICE_ATTR(info, S_IRUSR, chrdev_show_cmd, NULL);



    除此之外,在裝置驅動的載入和卸載函式也要建立和移除這個屬性,對應的API原型為

device_create_file(struct device *dev, const struct device_attribute *attr);
device_remove_file(struct device *dev, const struct device_attribute *attr);

所以在chrdev_init()裡要增加

device_create_file(c_dev.dev, &dev_attr_info);

chrdev_exit()裡面也增加

device_remove_file(c_dev.dev, &dev_attr_info);

在載入函式那時候也有提過最後我們的裝置目錄會被建立成/sys/devices/virtual/chrdev_class/chrdev-1的一個目錄,info屬性檔也理所當然的會出現在這個目錄下,
最後在用cat指令讀取info可以得知這個記憶體裝置的一些資訊了。

    到目前為止,我們的字元裝置也大概看完了,對字元裝置驅動的結構也有更進一步的認識,當然這只是一部分,有很多檔案操作函式這邊並沒有提到,不過對這個裝置已經夠足夠提供需要的功能了。在kernel裡字元驅動通常是以一個中繼的角色存在,或者可以稱為abstraction layer,對user space的process提供操作其他沒有出現在/dev目錄下裝置的方法, 而這些裝置通常都是platform dependent的裝置,這個好處是移植到不同嵌入式系統上時可以幾少開發的時間。下次我們再來看看linux典型的裝置架構,也就是掛在bus下的那些裝置,並且改變目前這個字元驅動,讓他符合一個典型的架構。


沒有留言:

張貼留言