首先必須要先決定這個虛擬的裝置應該長的怎麼樣,下面就是裝置的資料結構:
struct chrdev {
int id;
struct device *dev;
};
struct chrdev_inst {
struct chrdev *c_dev;
int size;
char *buf;
};
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這個指標指的就是到時後要存取的記憶體。.id = 1,
};
因為這個裝置是實驗性質,不會被編進kernel,是以module的方式存在,所以要建立當下insmod和rmmod指令時相對應的載入和卸載函式,其中載入函式如下:
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);
這個函式的主要工作:
- 分配記憶體給struct chrdev_inst
- 為讀寫的區域也分配一個BUFFER_SIZE大小的記憶體
- 註冊字元裝置的裝置編號
- 建立class和device
註冊字元裝置的裝置編號用的是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,釋放裝置編號,釋放分配的記憶體空間。看完了我們的虛擬字元裝置載入和卸載函式,接下來就是檔案操作函式啦,不過這篇已經有點太長了,下集待續啦!!
沒有留言:
張貼留言