【迅為iTop4412學習筆記】8.註冊雜項裝置,以及生成裝置節點
宣告
以下都是我剛開始看驅動視訊的個人強行解讀,如果有誤請指出,共同進步。
本節目標
註冊雜項裝置,並生成裝置節點
首先講一下之前
我們說過linux的關鍵是:驅動和裝置掛載在總線上
比如微控制器EEPROM是IIC協議的,那麼裝置EEPROM掛載在IIC總線上,編寫驅動即可。
對於匯流排
那麼對於LED這種沒協議的咋整?他就是個GPIO,所以Linux弄了一個platform虛擬匯流排,讓這種沒有匯流排的都掛載在這個虛擬總線上,好了 匯流排的事情完成了。
對於裝置
比如我們有1個LED,就註冊一個LED的裝置,LINUX為他分配一個裝置號,那麼假如我有1000個不同的裝置,是不是可以註冊1000個裝置呢?這裡面就有學問了。
Linux為裝置分配裝置號,而每個裝置號又分為主裝置號和次裝置號。主裝置號用來區分不同種類的裝置(0-255一共256個),而次裝置號用來區分同一型別的多個裝置。而對於常用裝置,Linux定死了他的裝置號。我們用命令檢視
cat /proc/devices
返回內容(只截取了字元裝置部分)
Character devices: 1 mem 4 /dev/vc/0 4 tty 4 ttyS 5 /dev/tty 5 /dev/console 5 /dev/ptmx 5 ttyprintk 6 lp 7 vcs 10 misc 13 input 14 sound 21 sg 29 fb 99 ppdev 108 ppp 116 alsa 128 ptm 136 pts 180 usb 189 usb_device 216 rfcomm 226 drm 251 hidraw 252 bsg 253 watchdog 254 rtc
這些主裝置號其實並不是完全任由我們設定,linux會把常用的裝置固定裝置號,比如misc雜項裝置的主裝置號就是10,然後給歸類misc的裝置分配次裝置號來區分。
正文
對於迅為的教程,是本身核心已經有一個裝置的情況下 載入模組 -> 載入編譯進核心的裝置的驅動-> 呼叫probe函式。
這是一個標準的註冊裝置,註冊驅動,呼叫probe()的過程。
然後他在probe()裡又註冊了一個雜項裝置,這樣程式碼很冗長,所以我在此做了一些修改,直接像註冊普通裝置一樣註冊雜項裝置。(程式碼相比,區別就是函式不一樣)。
以模組module來當模板
#include <linux/init.h>
#include <linux/module.h>
//#include <linux/platform_device.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");
static int mryang_init(void)
{
printk(KERN_EMERG "HELLO MrYang\n");
// platform_device_register(&mryang_device);
return 0;
}
static void mryang_exit(void)
{
printk(KERN_EMERG "Bye MrYang\n");
// platform_device_unregister(&mryang_device);
}
module_init(mryang_init);
module_exit(mryang_exit);
無論註冊裝置還是驅動,我們都要用API函式以及定義結構體,這次也不例外,我們首先開啟雜項裝置的標頭檔案看看
vim include/linux/miscdevice.h
搜尋結構體miscdevice,以下就是我們要用到的部分
#define MISC_DYNAMIC_MINOR 255
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
extern int misc_register(struct miscdevice * misc);
extern int misc_deregister(struct miscdevice *misc);
不知道有沒有注意到,以往我們程式碼只看了結構體和註冊解除安裝函式,為什麼會把巨集定義也放上來呢?因為MISC_DYNAMIC_MINOR是動態次裝置號,結構體內的minor需要我們指定次裝置號是多少。
首先我們要包含標頭檔案(註冊裝置節點的標頭檔案後面再說)
#include <linux/miscdevice.h> // 註冊雜項裝置的標頭檔案
#include <linux/fs.h> // 註冊裝置節點的結構體的標頭檔案
我們要定義結構體,然後在probe函式裡,把結構體傳給註冊函式進行註冊(解除安裝同理)
static struct miscdevice mryang_dev= {
// 次裝置號,可以指定次裝置號是多少,若是想讓linux自己去分配,則定義為巨集定義
.minor = MISC_DYNAMIC_MINOR,
// 次裝置的名字
.name = "mryang_misc_ctl",
.fops = &mryang_ops,
};
結構體為什麼這麼定義,我們舉個例子
我們之前註冊裝置,有name叫mryang_ctl,其id為-1。怎麼雜項裝置就沒有了呢?其實不然,此雜項裝置非彼雜項裝置,雜項裝置misc是一個統稱,misc才是一個有name有id的裝置。而我們現在說的雜項裝置,misc旗下的亂七八糟的裝置,是這種雜項裝置!
假如說misc有五個兒子,我們喊:misc的兒子出來一下,結果五個孩子根本不知道叫的誰。所以我們要為五個孩子分配一下次裝置號minor,以及次裝置的名字name,這下就對了。喊misc的1號兒子xxx出來,那麼叫誰清清楚楚。
那麼fops是什麼呢?結構體內是這麼定義的
const struct file_operations *fops;
他是一個檔案操作的結構體,對於linux來說,一切皆檔案,所以如果我們的應用要操控LED,自然是開啟LED的檔案描述符,寫入1開,寫入0關(這只是隨便舉的例子!)。這也是為什麼定義標頭檔案fs.h的原因。
沒錯我們又要定義一個結構體,叫mryang_ops,裡面存放這操控這個雜項裝置的各種檔案操作函式,當然,這些操作函式我們也要寫。。。函式該怎麼定義需要去檢視標頭檔案看函式定義是如何定義的。
// 3個檔案操作函式
static int mryang_open(struct inode *inode, struct file *file){
printk(KERN_EMERG "mryang_open!\n");
return 0;
}
static int mryang_release(struct inode *inode, struct file *file){
printk(KERN_EMERG "mryang_release\n");
return 0;
}
static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{
printk("cmd is %u\n",cmd);
printk("arg is %lu\n",arg);
return 0;
}
// file_operations 結構體 mryang_ops
static struct file_operations mryang_ops = {
.owner = THIS_MODULE,
.open = mryang_open,
.release = mryang_release,
.unlocked_ioctl = mryang_unlocked_ioctl,
};
然後我們把這個結構體mryang_ops傳給我們註冊雜項裝置的結構體mryang_dev的成員fops。
這個流程感覺像一個套娃,你開啟一個套娃,發現還要開啟一個,你再開啟,發現還要再開啟三個…不過好在全部編寫完成了。
此處挖坑…
結構體定義好了,我們在載入模組的時候註冊,解除安裝模組的時候解除安裝就行,分別寫入註冊、解除安裝雜項裝置的函式
// 寫入載入模組的函式內
misc_register(&mryang_dev);
// 寫入解除安裝模組的函式內
misc_deregister(&mryang_dev);
整個程式
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h> // 註冊雜項裝置的標頭檔案
#include <linux/fs.h> // 註冊裝置節點的結構體的標頭檔案
#define DEVICE_NAME "mryang_misc_ctl"
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");
/*
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
*/
static int mryang_open(struct inode *inode, struct file *file){
printk(KERN_EMERG "mryang_open!\n");
return 0;
}
static int mryang_release(struct inode *inode, struct file *file){
printk(KERN_EMERG "mryang_release\n");
return 0;
}
static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{
printk("cmd is %u\n",cmd);
printk("arg is %lu\n",arg);
return 0;
}
static struct file_operations mryang_ops = {
.owner = THIS_MODULE,
.open = mryang_open,
.release = mryang_release,
.unlocked_ioctl = mryang_unlocked_ioctl,
};
static struct miscdevice mryang_dev= {
// 次裝置號,可以指定次裝置號是多少,若是想讓linux自己去分配,則定義為巨集定義
.minor = MISC_DYNAMIC_MINOR,
// 次裝置的名字
.name = "mryang_misc_ctl",
.fops = &mryang_ops,
};
static int mryang_init(void)
{
printk(KERN_EMERG "HELLO MrYang\n");
printk(KERN_EMERG "return %d\n", misc_register(&mryang_dev) );
return 0;
}
static void mryang_exit(void)
{
printk(KERN_EMERG "Bye MrYang\n");
misc_deregister(&mryang_dev);
}
module_init(mryang_init);
module_exit(mryang_exit);
載入和解除安裝只會輸出模組資訊,那麼我們怎麼判斷是否成功註冊了雜項裝置呢?
- misc_register()和之前的註冊的函式都有返回值,我們可以在程式裡列印他們的返回值來檢視
- 使用命令檢視
此命令檢視裝置節點
ls /dev
mryang_misc_ctl 確實出現在了返回的列表裡
檢視雜項裝置號
cat /proc/misc
返回 46 mryang_misc_ctl,說明雜項裝置的次裝置有mryang_misc_ctl,其次裝置號為46
結束
註冊雜項裝置雖然步驟跟註冊裝置類似,但是我們要注意到,我們現在說的註冊雜項裝置,其實只是為了在雜項裝置misc下注冊的,目的是為了給他分配一個次裝置號(兒子就是兒子,地位和爸爸和叔叔不是同一輩啊…)
除了註冊了雜項裝置,我們還為裝置生成了裝置節點,以便上層呼叫。比如上層發出命令LED0開,LED1關。裝置節點裡的檔案操作函式就會發揮作用。比如開啟就呼叫mryang_open,關閉呼叫mryang_release,操作裝置就呼叫mryang_unlocked_ioctl()傳入命令和引數。
對於本節我們雖然寫了 但是沒有用上,而後面,我們就會來使用這些。