2014年8月11日 星期一

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

    這次就讓我們來實作一個字元驅動,可是沒有硬體,是要驅動什麼?沒關係,沒硬體有沒硬體的作法,就拿記憶體來當一個虛擬的裝置吧!雖然讀寫記憶體和讀寫暫存器的方法不一樣,還是可以讓我們學到很多東西的。

    首先必須要先決定這個虛擬的裝置應該長的怎麼樣,下面就是裝置的資料結構:
struct chrdev {
    int id;
    struct device *dev;
};

struct chrdev_inst {
    struct chrdev *c_dev;
    int size;
    char *buf;
};

struct chrdev c_dev = {
    .id = 1,
};
給了這個裝置一個ID,分配的記憶體大小(size),buf這個指標指的就是到時後要存取的記憶體。

    因為這個裝置是實驗性質,不會被編進kernel,是以module的方式存在,所以要建立當下insmodrmmod指令時相對應的載入和卸載函式,其中載入函式如下:

static struct file_operations chrdev_fops = {
    .read = chrdev_read,
    .write = chrdev_write,
    .unlocked_ioctl = chrdev_ioctl,
    .mmap = chrdev_mmap,
    .open = chrdev_open,
    .release = chrdev_release,
};


struct class *chrdev_class;

static struct chrdev_inst *alloc_chrdev_inst(void)
{
    struct chrdev_inst *inst;

    inst = kzalloc(sizeof(struct chrdev_inst), GFP_KERNEL);
    if (inst == NULL)
        goto out;

    inst->buf = kzalloc(BUFFER_SIZE, GFP_KERNEL);
    if (inst->buf == NULL)
        goto free_inst;

    inst->size = BUFFER_SIZE;

    return inst;

free_inst:
    kfree(inst);
out:
    return NULL;
}


int __init chrdev_init(void)
{
    int ret;
    dev_t devno;
    struct chrdev_inst *inst;

    inst = alloc_chrdev_inst();
    if (inst == NULL) {
        ret = -ENOMEM;
        goto out;
    }
    inst->c_dev = &c_dev;

    ret = register_chrdev(chrdev_major, "chrdev", &chrdev_fops);;
    if(ret < 0)
        goto free_inst;

    if (chrdev_major == 0)
        chrdev_major = ret;
    ret = 0;

    chrdev_class = class_create(THIS_MODULE, "chrdev_class");
    if (IS_ERR(chrdev_class)) {
        ret = PTR_ERR(chrdev_class);
        goto release_chrdev;
    }
   
    devno = MKDEV(chrdev_major, c_dev.id);
    c_dev.dev = device_create(chrdev_class, NULL, devno, inst, "%s-%d", "chrdev", c_dev.id);

    return ret;

release_chrdev:
    unregister_chrdev(chrdev_major, "chrdev");
free_inst:
    free_chrdev_inst(inst);
out:
    return ret;
}

module_init(chrdev_init);

這個函式的主要工作:
  1. 分配記憶體給struct chrdev_inst
  2. 為讀寫的區域也分配一個BUFFER_SIZE大小的記憶體
  3. 註冊字元裝置的裝置編號
  4. 建立class和device
    分配記憶體的部份這邊採用kernel提供的kzalloc()的方法,GFP_KERNEL是一個mask, 用來分配一般kernel的記憶體,其他還有GFP_ATOMIC(不會block,可用在interrupt處理函式裡) ,GFP_HIGHUSER(分配高端記憶體)...等等。

    註冊字元裝置的裝置編號用的是register_chrdev()方法,需要傳入主裝置編號,裝置名稱,檔案操作資料結構(struct file_operations)。如果主裝置編號為0,他會自動分配一個沒被使用的主編號,那次要編號咧?register_chrdev()會分配255個次要編號,我們也可以選擇分配一個range的次要編號,那就要改用alloc_chrdev_region()register_chrdev_region(),這兩個函式只單純分配裝置編號,所以字元裝置的結構(struct cdev)需要我們自己處理,不過kernel很好心的提供了cdev_init()cdev_add()...等API讓我們減少工作量。檔案操作資料結構裡面定義了一些callback functions,這就是字元裝置能被當成一般檔案處理的原因了,在驅動裡面這些callback functions需要我們自己實現 。

    為了要讓udev幫我們自動在/dev目錄下面建立一個裝置檔案,所以這邊需要建立class和device兩個資料結構,要建立class的原因是這個device必須屬於某個class或bus才能觸發hotplug,device_create()傳入裝置編號(MKDEV用來產生裝置編號),裝置私有資料(inst), 裝置在/dev目錄下的名稱。其中NULL那一個參數是這個device的parent device,如果是NULL,那他的parent就是virtual,所以我們的device會出現在/sys/devices/virtual/chrdev_class/chrdev-1。

    這邊看到很多地方用了goto,回想以前唸書的時候老師都說不用要用goto,為什麼這邊還用,而且用了一堆!其實整個kernel裡面用的才多咧,不過只要是用在錯誤的處理上,處理得當其實無傷大雅啦!

    接下來還看卸載函式吧!卸載函式如下:

static void free_chrdev_inst(struct chrdev_inst *inst)
{
    kfree(inst->buf);
    kfree(inst);
}


void __exit chrdev_exit(void)
{
    struct chrdev_inst *inst = dev_get_drvdata(c_dev.dev);

    device_destroy(chrdev_class, MKDEV(chrdev_major, c_dev.id));
    class_destroy(chrdev_class);
    unregister_chrdev(chrdev_major, "chrdev");
    free_chrdev_inst(inst);

}

module_exit(chrdev_exit);

卸載函式主要作的事跟載入函式相反,移除device和class,釋放裝置編號,釋放分配的記憶體空間。看完了我們的虛擬字元裝置載入和卸載函式,接下來就是檔案操作函式啦,不過這篇已經有點太長了,下集待續啦!!

沒有留言:

張貼留言