2014年8月21日 星期四

Linux典型裝置架構-Character Device

    到目前為止,也來到典型裝置的最後一部分test_chrdev,雖然說他跟之前的"一個簡單的字元裝置"是差不多的,可是還是有不一樣的地方,我們還是從載入和卸載函式看起吧!

static struct file_operations test_chrdev_fops = {
    .owner = THIS_MODULE,
    .read = test_chrdev_read,
    .write = test_chrdev_write,
    .unlocked_ioctl = test_chrdev_ioctl,
    .open = test_chrdev_open,
    .release = test_chrdev_release,
};


static int create_dev(struct device *dev, void *data)
{
    int ret = 0;
    dev_t devno;
    struct device *chrdev;
    struct test_device *tdev = container_of(dev, struct test_device, dev);

    devno = MKDEV(chrdev_major, tdev->id);

    chrdev = device_create(test_inst_class, &tdev->dev, devno, NULL, "%s-%d", tdev->name, tdev->id);
    if (IS_ERR(chrdev))
        ret = PTR_ERR(chrdev);

    return ret;
}

static int destroy_dev(struct device *dev, void *data)
{
    dev_t devno;
    struct test_device *tdev = container_of(dev, struct test_device, dev);

    devno = MKDEV(chrdev_major, tdev->id);

    device_destroy(test_inst_class, devno);
   
    return 0;
}

static int __init test_chrdev_init(void)
{
    int ret;

    ret = register_chrdev(chrdev_major, "test_chrdev", &test_chrdev_fops);
    if (ret < 0)
        goto out;

    if (!chrdev_major)
        chrdev_major = ret;
    ret = 0;
   
    test_inst_class = class_create(THIS_MODULE, "test_chrdev");
    if (IS_ERR(test_inst_class)) {
        ret = PTR_ERR(test_inst_class);
        goto release_chrdev;
    }

    bus_for_each_dev(&test_bus_type, NULL, NULL, create_dev);

    return ret;

destroy_class:
    class_destroy(test_inst_class);
release_chrdev:
    unregister_chrdev(chrdev_major, "test_chrdev");
out:
    return ret;
}

static void test_chrdev_exit(void)

{
    bus_for_each_dev(&test_bus_type, NULL, NULL, destroy_dev);
    class_destroy(test_inst_class);
    unregister_chrdev(chrdev_major, "test_chrdev");
}


    載入函式chrdev_init()中,看到了熟悉的字元裝置的註冊和class的建立,不過這邊用了kernel提供的bus_for_each_dev()走訪所有已經掛在test_bus_type下的device,並對每個device調用create_dev(),這個函式會先找到device所屬的test_device,在根據每個test_device的id欄位在/dev目錄下建立相對應的裝置檔案,跟"一個簡單的字元裝置"不同的是parent欄位是test_device,所以test_chrdev最後在/sys目錄下的位置會是/sys/devices/test_bus/test-1/test_chrdev。而卸載函式test_chardev_exit()則是移除裝置檔案,class和字元裝置。跟之前的file_operations比較可以發現少了mmap()的操作函式,因為沒有可以直接映射的記憶體位址,所以mmap()在這邊沒有一展所長的地方。

static int find_test_dev(struct device *dev, void *data)
{
    int id, ret = 0;
    struct dev_info *info = (struct dev_info*)data;
    struct test_device *tdev = container_of(dev, struct test_device, dev);

    id = iminor(info->inode);

    if (id == tdev->id) {
        info->filp->private_data = tdev;
        ret = 1;
    }

    return ret;
}

static int test_chrdev_open(struct inode *inode, struct file *filp)
{
    int ret;
    struct dev_info info = {inode, filp};

    ret = bus_for_each_dev(&test_bus_type, NULL, &info, find_test_dev);
    if (!ret)
        ret = -ENODEV;
    else       
        ret = 0;

    return ret;
}

static int test_chrdev_release(struct inode *inode, struct file *filp)

{
    return 0;
}


在open()函式裡也同樣調用bus_for_each_dev()來搜尋目前開啟的test_device是否已經被註冊,如果找到了就放到檔案的私有資料攔,方便之後的使用。release()在這邊同樣沒什工作要作。

static ssize_t test_chrdev_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    int ret;
    char *buffer;
    struct test_msg *msg;
    struct test_device *tdev = filp->private_data;

    buffer = kzalloc(size, GFP_KERNEL);
    if (buffer == NULL) {
        ret = -ENOMEM;
        goto out;
    }

    msg = kzalloc(sizeof(struct test_msg), GFP_KERNEL);
    if (msg == NULL) {
        ret = -ENOMEM;
        goto free_buffer;
    }

    msg->flag = MSG_RD;
    msg->from = 0;
    msg->len = size;
    msg->buf = buffer;

    ret = tdev->ops->transfer(tdev, msg, 1);
    if (ret < 0)
        goto free_msg;

    if (copy_to_user(buf, msg->buf, msg->len))
        ret = -EFAULT;

free_msg:
    kfree(msg);
free_buffer:
    kfree(buffer);
out:
    return ret;
}


static ssize_t test_chrdev_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
    int ret;
    char *buffer;
    struct test_msg *msg;   
    struct test_device *tdev = filp->private_data;

    buffer = kzalloc(size, GFP_KERNEL);
    if (buffer == NULL) {
        ret = -ENOMEM;
        goto out;
    }

    if (copy_from_user(buffer, buf, size)) {
        ret = -EFAULT;
        goto free_buffer;
    }

    msg = kzalloc(sizeof(struct test_msg), GFP_KERNEL);
    if (msg == NULL) {
        ret = -ENOMEM;
        goto free_buffer;
    }

    msg->flag = MSG_WR;
    msg->from = 0;
    msg->len = size;
    msg->buf = buffer;

    ret = tdev->ops->transfer(tdev, msg, 1);
   
    kfree(msg);
free_buffer:
    kfree(buffer);
out:
    return ret;
}


    讀寫函式一樣是從user space接收一個size的資料(buf),一樣是從位置0地方開始讀寫,不過這邊需要自己建構一個test_msg的結構,之後調用test_device所提供的transfer() callback function把建構好的test_msg往下傳,一次只會傳一個msg。因為分層的架構,讀寫函式裡已經看不到任何跟硬體相關的操作,所有有關硬體的操作都在transfer()函式裡。

static int test_ioctl_rdwr(struct file *filp, unsigned long arg)
{
    int i, j, ret;
    char **buffer;
    struct test_msg *msg;
    struct test_ioctl_data test_data;
    struct test_device *tdev = filp->private_data;

    if (copy_from_user(&test_data, (struct test_ioctl_data*)arg, sizeof(test_data)))
        return -EFAULT;

    msg = kzalloc(test_data.num_msg*sizeof(struct test_msg), GFP_KERNEL);
    if (msg == NULL)
        return -ENOMEM;
    if (copy_from_user(msg, test_data.msg, test_data.num_msg*sizeof(struct test_msg))) {
        kfree(msg);
        return -EFAULT;
    }

    buffer = kzalloc(test_data.num_msg*sizeof(char), GFP_KERNEL);
    if (buffer == NULL) {
        kfree(msg);
        return -ENOMEM;
    }

    for (i = 0; i < test_data.num_msg; i++) {
        buffer[i] = msg[i].buf;

        msg[i].buf = kzalloc(msg[i].len, GFP_KERNEL);
        if (msg[i].buf == NULL)
            break;

        if (msg[i].flag & MSG_WR) {
            if (copy_from_user(msg[i].buf, buffer[i], msg[i].len)) {
                kfree(msg[i].buf);
                return -EFAULT;
            }
        }
    }

    ret = tdev->ops->transfer(tdev, msg, test_data.num_msg);

    for (j= 0; j < ret; j++) {
        if (msg[j].flag & MSG_RD) {
            if (copy_to_user(buffer[j], msg[j].buf, msg[j].len))
                break;
        }
    }       
   
    ret = j;

    for (j = 0; j < i; j++)
        kfree(msg[j].buf);
    kfree(buffer);
    kfree(msg);
    return ret;
}

static int test_ioctl_erase(struct file *filp)
{
    int ret;
    struct test_device *tdev = filp->private_data;

    ret = tdev->ops->erase(tdev);

    return ret;
}

static long test_chrdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

{
    int ret;

    switch(cmd) {
    case TEST_RDWR:
        ret = test_ioctl_rdwr(filp, arg);
        break;
    case TEST_ERASE:
        ret = test_ioctl_erase(filp);
        break;
    default:
        ret = -EINVAL;
    }

    return ret;
}


    ioctl()一樣有讀寫和擦除的兩個工作,對照"一個簡單的字元裝置",這邊的讀寫動作只是之前前半部的動作,把user space傳過來的所有messages分別複製一份到kernel space,最後也是調用test_device提供的transfer() callback function。擦除的動作則直接調用erase() callback function。

    好啦!所有的操作函式都介紹完了,以為一切都結束了嗎?我們還有一些後續的事情要做阿。test_chrdev當然也是編寫成一個module,所以目前我們有三個modules,那每個module載入的順序呢?一般認為當然是test_bus.ko -> test_module.ko -> test_chrdev.ko了,那有沒有想過,如果有一個新的裝置註冊,有沒有辦法同時在/dev目錄下也建立相對應的裝置檔案呢?這屬於kernel的通知機制,在下一篇再做介紹吧!

沒有留言:

張貼留言