2014年8月20日 星期三

Linux典型裝置架構-Device & Driver

    延續之前的話題,這次來看看test_module的部份,包含在test_module.c這個檔案裡。可以把這個檔案再分為兩個部份,test_device和test_driver來看,首先先來看test_device。

struct test_operations test_inst_ops = {
    .transfer = test_dev_xfer,
    .erase = test_dev_erase,
};

struct test_device test_dev = {
    .name = DEV_NAME,
    .id = 1,
    .ops = &test_inst_ops,
};


struct test_inst {
    struct test_device *tdev;
    unsigned int    size;
    char        *buffer;
    unsigned long    last_wr;
    unsigned long    last_rd;
};


static __inline void test_set_drvdata(struct test_device *tdev, void *data)
{
    dev_set_drvdata(&tdev->dev, data);
}


static __inline void *test_get_drvdata(struct test_device *tdev)
{
    return dev_get_drvdata(&tdev->dev);
}


static int test_dev_xfer(struct test_device *tdev, struct test_msg *msg, int num_msg)
{
    int i;
    struct test_inst *inst = test_get_drvdata(tdev);

    for (i = 0; i < num_msg; i++) {
        int from = msg[i].from;
        int len = msg[i].len;

        if (from > inst->size)
            break;
        if ((from+len) > inst->size)
            len = inst->size - from;
        if (msg[i].flag == MSG_RD) {
            inst->last_wr = jiffies;
            memcpy(msg[i].buf, inst->buffer+from, len);
        } else {
            inst->last_rd = jiffies;
            memcpy((inst->buffer+from), msg[i].buf, len);
        }
    }

    return i;
}

static int test_dev_erase(struct test_device *tdev)
{
    struct test_inst *inst = test_get_drvdata(tdev);

    memset(inst->buffer, 0, inst->size);

    return 0;
}


在這邊給了test_device一個名稱,一個ID,也實現了要提供給abstraction layer(字元裝置)的一組callback function。test_get_drvdata()只是直接調用dev_get_drvdata(),從device結構中得到私有資料。test_inst的結構,他其實就是test_device的包裝,test_device提供一些基本的成員,而test_inst則是依需求或功能在test_device上擴充,有點像是物件導向繼承的概念,test_inst的功能還是分配一塊可讀寫的記憶體空間,另外也紀錄最後讀寫的時間,這個時間是以jiffies為單位,他是電腦一開機後就隨著timer跳動的計數器。

    跟"一個簡單的字元裝置(2)"中的chrdev_ioctl_rdwr()函數比較一下,可以發現test_dev_xfer()做的事情是流程中比較後面有關處理記憶體複製的動作,也就是屬於直接控制硬體的部份,而在其他裝置這類的callback function做的事情就可能是讀寫暫存器,在"一個簡單的字元裝置(3)"結尾的部份也有提過這是屬於platform dependent的部分。那其他跟硬體無關的部份久則還是留在字元裝置裡。這樣很明顯的形成兩個分層,系統需要移植的時候只要重新開發底層跟硬體有關的部份,因此減少了很多開發的時間。

    再來看看test_driver的部份

struct test_driver test_drv = {
    .driver = {
        .owner = THIS_MODULE,
        .name = DEV_NAME,
    },
    .probe = test_inst_probe,
    .remove = test_inst_remove,
};


static ssize_t inst_show_info(struct device *dev, struct device_attribute *attr, char *buf)
{
    struct test_device *tdev = container_of(dev, struct test_device, dev);
    struct test_inst *inst = test_get_drvdata(tdev);

    return snprintf(buf, PAGE_SIZE, "ID: %d\nBuffer size: %d\n", tdev->id, inst->size);
}

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



static int test_inst_probe(struct test_device *tdev)
{
    int ret = 0;
    struct test_inst *inst;

    inst = kzalloc(sizeof(struct test_inst), GFP_KERNEL);
    if (inst == NULL) {
        ret = -ENOMEM;
        goto out;
    }

    inst->tdev = tdev;
    inst->size = BUFFER_SIZE;
    inst->buffer = kzalloc(inst->size, GFP_KERNEL);
    if (inst->buffer == NULL) {
        ret = -ENOMEM;
        goto free_inst;
    } 

    ret = device_create_file(&tdev->dev, &dev_attr_info);
    if (ret)
        goto release_buffer;

    test_set_drvdata(tdev, inst);

    return ret;

release_buffer:
    kfree(inst->buffer);
free_inst:
    kfree(inst);
out:
    return ret;
}

static int test_inst_remove(struct test_device *tdev)
{   
    struct test_inst *inst = test_get_drvdata(tdev);
   
    device_remove_file(&tdev->dev, &dev_attr_info);
    kfree(inst->buffer);
    kfree(inst);
    test_set_drvdata(tdev, NULL);

    return 0;
}


    因為要能夠跟test_device match,所以test_driver的名稱必須和test_device一樣,另外重要的則是實現了probe()remove()方法,實際的裝置結構是在probe()方法理時現的,主要工作就是分配記憶體並對test_inst結構初始化,之後再把他放到device結構的私有資料裡,如果曾經有開發驅動的經驗,會發現在probe()方法裡還會註冊實際的裝置。當然這裡也提供一個屬性檔(info)可以獲取裝置的ID和記憶體區塊的大小。

    test_module這部份也編寫成module的形式,所以載入和卸載函式也是不可或缺的,其實也只有註冊test_device和test_driver。

static int __init test_inst_init(void)
{
    int ret = 0;

    ret = test_device_register(&test_dev);
    if (ret)
        goto out;
   
    ret = test_driver_register(&test_drv);
    if (ret)
        goto release_dev;

    return ret;

release_dev:
    test_device_unregister(&test_dev);
out:
    return ret;
}

static void __exit test_inst_exit(void)
{
    test_driver_unregister(&test_drv);
    test_device_unregister(&test_dev);
}


    這個Linux典型的裝置架構幾乎快要完成了,剩下的就是要提供一個入口給user space的process,test_chrdev就是完成這個工作的字元裝置,其實他跟之前"一個簡單的字元裝置"是差不多的,只是每個動作最後改由調用test_device提供的callback function。下次我們在來看這部份吧!

沒有留言:

張貼留言