到目前為止,也來到典型裝置的最後一部分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的通知機制,在下一篇再做介紹吧!
沒有留言:
張貼留言