目錄
MISC 設備驅動
miscdevice結構體
misc_register 函數
misc_deregister 函數
實驗程序編寫
修改設備樹
驅動程序編寫
miscbeep.c
miscbeepApp.c
Makefile 文件
運行測試
MISC 驅動也叫做雜項驅動,也就是當某些外設無法進行分類的時候就可以使用 MISC 驅動。
MISC 設備驅動
?所有的 MISC 設備驅動的主設備號都為 10,不同的設備使用不同的從設備號。MISC 設備會自動創建 cdev,因此采用 MISC 設備驅動可以簡化字符設備驅動的編寫。
miscdevice結構體
我們需要向 Linux 注冊一個 miscdevice 設備, miscdevice是一個結構體,定義在文件 include/linux/miscdevice.h 中。
miscdevice結構體內容如下:
/*** struct miscdevice - 混雜設備(miscdevice)結構體* * 用于表示Linux內核中的混雜設備(次設備號動態分配的字符設備)* 通常用于簡單的字符設備驅動,避免手動申請主設備號*/
struct miscdevice {int minor; /* 次設備號(MISC_DYNAMIC_MINOR表示動態分配) */const char *name; /* 設備名稱(出現在/dev和sysfs中) */const struct file_operations *fops; /* 文件操作集合(實現read/write/ioctl等) *//* 以下字段通常由內核內部管理,驅動無需初始化 */struct list_head list; /* 內核使用的鏈表頭 */struct device *parent; /* 父設備指針(可選) */struct device *this_device; /* 內核創建的設備實例 *//* 可選擴展字段 */const struct attribute_group **groups; /* 屬性組(sysfs接口) */const char *nodename; /* 設備節點名稱(覆蓋默認的命名規則) */umode_t mode; /* 設備節點權限(如0644) */
};
定義一個 MISC 設備(miscdevice 類型)以后,我們需要設置 minor、 name 和 fops 這三個成員變量。
minor 表示子設備號, 用戶指定子設備號, Linux 系統已經預定義了一些 MISC 設備的子設備號,這些預定義的子設備號定義在include/linux/miscdevice.h 文件中,如下所示:
/* 輸入設備類 */
#define PSMOUSE_MINOR 1 /* PS/2鼠標設備 */
#define MS_BUSMOUSE_MINOR 2 /* 微軟總線鼠標(已廢棄) */
#define ATIXL_BUSMOUSE_MINOR 3 /* ATI XL總線鼠標(已廢棄) */
#define ATARIMOUSE_MINOR 5 /* Atari總線鼠標(已廢棄) */
#define SUN_MOUSE_MINOR 6 /* Sun鼠標(已廢棄) */
#define APOLLO_MOUSE_MINOR 7 /* Apollo鼠標(已廢棄) */
#define PC110PAD_MINOR 9 /* IBM PC110觸控板 */
#define ADB_MOUSE_MINOR 10 /* ADB總線鼠標(Mac) *//* 存儲/設備控制類 */
#define WATCHDOG_MINOR 130 /* 看門狗定時器 */
#define TEMP_MINOR 131 /* 溫度傳感器 */
#define RTC_MINOR 135 /* 實時時鐘 */
#define EFI_RTC_MINOR 136 /* EFI實時時鐘 */
#define SUN_OPENPROM_MINOR 139 /* Sun Open PROM *//* 網絡/通信類 */
#define NVRAM_MINOR 144 /* 非易失性RAM */
#define I2O_MINOR 166 /* I2O設備 */
#define MICROCODE_MINOR 184 /* CPU微代碼更新 *//* 特殊功能設備 */
#define VHCI_MINOR 255 /* USB虛擬主機控制器 */
#define MISC_DYNAMIC_MINOR 255 /* 動態分配標志(與VHCI共享) */
我們在使用的時候可以從這些預定義的子設備號中挑選一個,當然也可以自己定義一個沒有被其他設備使用地子設備號。
name : MISC 設備名字,當此設備注冊成功以后就會在/dev 目錄下生成一個名為 name的設備文件。
fops 就是字符設備的操作集合, MISC 設備驅動最終是需要使用用戶提供的 fops操作集合。
misc_register 函數
當設置好 miscdevice 以后,就需要使用 misc_register 函數向系統中注冊一個 MISC 設備。
misc_register 函數原型如下:
int misc_register(struct miscdevice * misc)
- misc:要注冊的 MISC 設備。
- 返回值: 負數,失敗; 0,成功。
在以前的字符設備驅動中,我們會使用如下幾個函數完成設備創建過程:
alloc_chrdev_region(); /* 申請設備號 */
cdev_init(); /* 初始化 cdev */
cdev_add(); /* 添加 cdev */
class_create(); /* 創建類 */
device_create(); /* 創建設備 */
現在只需要使用 misc_register 函數來完成這些功能。
misc_deregister 函數
當我們卸載設備驅動模塊的時候。需要調用 misc_deregister 函數來注銷掉 MISC 設備,
misc_deregister 函數原型如下:
int misc_deregister(struct miscdevice *misc)
- misc:要注銷的 MISC 設備。
- 返回值: 負數,失敗; 0,成功。
以前注銷設備驅動的時候,我們需要刪除此前創建的 cdev、設備等等:
cdev_del(); /* 刪除 cdev */
unregister_chrdev_region(); /* 注銷設備號 */
device_destroy(); /* 刪除設備 */
class_destroy(); /* 刪除類 */
現在也只需要一個 misc_deregister 函數來實現。
實驗程序編寫
實驗程序,我們需要采用 platform 加 misc 的方式編寫 beep 驅動,這也是實際的 Linux 驅動中很常用的方法。采用 platform 來實現總線、設備和驅動, misc 主要負責完成字符設備的創建。
beep硬件原理圖和芯片資料,可以參考:裸機學習實驗5——蜂鳴器實驗
修改設備樹
正點原子I.MX6U-ALPHA開發板上的BEEP,使用了SNVS_TAMPER1這個PIN。
打開imx6ull-alientek-emmc.dts,在 iomuxc 節點的 imx6ul-evk 子節點下創建一個名為“pinctrl_beep”的子節點。
“pinctrl_beep”節點內容如下所示:
pinctrl_beep: beepgrp {fsl,pins = <MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0 /* beep */>;
};
在根節點“/”下創建 BEEP 節點,節點名為“beep”。
“beep”節點內容如下:
beep {#address-cells = <1>; // 子節點地址空間占1個32位字#size-cells = <1>; // 子節點大小空間占1個32位字compatible = "atkalpha-beep"; // 匹配驅動的唯一標識符pinctrl-names = "default"; // 引腳控制狀態名稱pinctrl-0 = <&pinctrl_beep>; // 關聯的引腳配置組beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>; // GPIO控制定義:// - gpio5組第1個引腳// - 高電平有效status = "okay"; // 設備啟用狀態
};
最后檢查imx6ull-alientek-emmc.dts文件里,PIN是否被其它外設使用,若有則屏蔽。
驅動程序編寫
miscbeep.c
蜂鳴器的驅動文件,代碼如下:
?
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define MISCBEEP_NAME "miscbeep" /* 名字 */
#define MISCBEEP_MINOR 144 /* 子設備號 */
#define BEEPOFF 0 /* 關蜂鳴器 */
#define BEEPON 1 /* 開蜂鳴器 *//* miscbeep設備結構體 */
struct miscbeep_dev{dev_t devid; /* 設備號 */struct cdev cdev; /* cdev */struct class *class; /* 類 */struct device *device; /* 設備 */struct device_node *nd; /* 設備節點 */int beep_gpio; /* beep所使用的GPIO編號 */
};struct miscbeep_dev miscbeep; /* beep設備 *//** @description : 打開設備* @param - inode : 傳遞給驅動的inode* @param - filp : 設備文件,file結構體有個叫做private_data的成員變量* 一般在open的時候將private_data指向設備結構體。* @return : 0 成功;其他 失敗*/
static int miscbeep_open(struct inode *inode, struct file *filp)
{filp->private_data = &miscbeep; /* 設置私有數據 */return 0;
}/** @description : 向設備寫數據 * @param - filp : 設備文件,表示打開的文件描述符* @param - buf : 要寫給設備寫入的數據* @param - cnt : 要寫入的數據長度* @param - offt : 相對于文件首地址的偏移* @return : 寫入的字節數,如果為負值,表示寫入失敗*/
static ssize_t miscbeep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char beepstat;struct miscbeep_dev *dev = filp->private_data;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}beepstat = databuf[0]; /* 獲取狀態值 */if(beepstat == BEEPON) { gpio_set_value(dev->beep_gpio, 0); /* 打開蜂鳴器 */} else if(beepstat == BEEPOFF) {gpio_set_value(dev->beep_gpio, 1); /* 關閉蜂鳴器 */}return 0;
}/* 設備操作函數 */
static struct file_operations miscbeep_fops = {.owner = THIS_MODULE,.open = miscbeep_open,.write = miscbeep_write,
};/* MISC設備結構體 */
static struct miscdevice beep_miscdev = {.minor = MISCBEEP_MINOR,.name = MISCBEEP_NAME,.fops = &miscbeep_fops,
};/** @description : flatform驅動的probe函數,當驅動與* 設備匹配以后此函數就會執行* @param - dev : platform設備* @return : 0,成功;其他負值,失敗*/
static int miscbeep_probe(struct platform_device *dev)
{int ret = 0;printk("beep driver and device was matched!\r\n");/* 設置BEEP所使用的GPIO *//* 1、獲取設備節點:beep */miscbeep.nd = of_find_node_by_path("/beep");if(miscbeep.nd == NULL) {printk("beep node not find!\r\n");return -EINVAL;} /* 2、 獲取設備樹中的gpio屬性,得到BEEP所使用的BEEP編號 */miscbeep.beep_gpio = of_get_named_gpio(miscbeep.nd, "beep-gpio", 0);if(miscbeep.beep_gpio < 0) {printk("can't get beep-gpio");return -EINVAL;}/* 3、設置GPIO5_IO01為輸出,并且輸出高電平,默認關閉BEEP */ret = gpio_direction_output(miscbeep.beep_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}/* 一般情況下會注冊對應的字符設備,但是這里我們使用MISC設備* 所以我們不需要自己注冊字符設備驅動,只需要注冊misc設備驅動即可*/ret = misc_register(&beep_miscdev);if(ret < 0){printk("misc device register failed!\r\n");return -EFAULT;}return 0;
}/** @description : platform驅動的remove函數,移除platform驅動的時候此函數會執行* @param - dev : platform設備* @return : 0,成功;其他負值,失敗*/
static int miscbeep_remove(struct platform_device *dev)
{/* 注銷設備的時候關閉LED燈 */gpio_set_value(miscbeep.beep_gpio, 1);/* 注銷misc設備 */misc_deregister(&beep_miscdev);return 0;
}/* 匹配列表 */static const struct of_device_id beep_of_match[] = {{ .compatible = "atkalpha-beep" },{ /* Sentinel */ }};/* platform驅動結構體 */
static struct platform_driver beep_driver = {.driver = {.name = "imx6ul-beep", /* 驅動名字,用于和設備匹配 */.of_match_table = beep_of_match, /* 設備樹匹配表 */},.probe = miscbeep_probe,.remove = miscbeep_remove,
};/** @description : 驅動出口函數* @param : 無* @return : 無*/
static int __init miscbeep_init(void)
{return platform_driver_register(&beep_driver);
}/** @description : 驅動出口函數* @param : 無* @return : 無*/
static void __exit miscbeep_exit(void)
{platform_driver_unregister(&beep_driver);
}module_init(miscbeep_init);
module_exit(miscbeep_exit);
MODULE_LICENSE("GPL");
關鍵代碼分析如下:
MISC 設備 beep_miscdev結構體,當系統啟動以后就會在/dev/目錄下存在一個名為“miscbeep”的設備文件。
static struct miscdevice beep_miscdev = {.minor = MISCBEEP_MINOR,.name = MISCBEEP_NAME,.fops = &miscbeep_fops,
};
miscbeep_probe函數,platform 框架的 probe 函數,當驅動與設備匹配以后此函數就會執行,首先在此函數中初始化 BEEP 所使用的 IO,然后通過 misc_register 函數向 Linux 內核注冊MISC 設備 beep_miscdev。
ret = misc_register(&beep_miscdev);
miscbeep_remove函數,platform 框架的 remove 函數,調用 misc_deregister 函數來注銷MISC 設備。
misc_deregister(&beep_miscdev);
miscbeepApp.c
測試app文件miscbeepApp.c,代碼如下:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"#define BEEPOFF 0
#define BEEPON 1/** @description : main主程序* @param - argc : argv數組元素個數* @param - argv : 具體參數* @return : 0 成功;其他 失敗*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;unsigned char databuf[1];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR); /* 打開beep驅動 */if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}databuf[0] = atoi(argv[2]); /* 要執行的操作:打開或關閉 */retvalue = write(fd, databuf, sizeof(databuf));if(retvalue < 0){printf("BEEP Control Failed!\r\n");close(fd);return -1;}retvalue = close(fd); /* 關閉文件 */if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;
}
Makefile 文件
makefile文件只需要修改?obj-m 變量的值,改為miscbeep.o。
KERNELDIR := /home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)
obj-m := miscbeep.obuild: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
運行測試
編譯代碼:
make -j32 //編譯makefile文件
arm-linux-gnueabihf-gcc miscbeepApp.c -o miscbeepApp //編譯測試app
編譯成功以后,就會生成一個名為“miscbeep.ko”的驅動模塊文件,和miscbeepApp 應用程序。
將編譯出來的miscbeep.ko 和 miscbeepApp 這兩個文件拷貝到rootfs/lib/modules/4.1.15 目錄中,重啟開發板。
進入到目錄 lib/modules/4.1.15 中,輸入如下命令加載 miscbeep.ko 這個驅動模塊:
depmod //第一次加載驅動的時候需要運行此命令
modprobe miscbeep.ko //加載設備模塊
當驅動模塊加載成功以后,我們可以在/sys/class/misc 這個目錄下看到一個名為“miscbeep”的子目錄,如圖:
所有的 misc 設備都屬于同一個類, /sys/class/misc 目錄下就是 misc 這個類的所有設備,每個設備對應一個子目錄。
驅動與設備匹配成功以后就會生成/dev/miscbeep 這個設備驅動文件,要查看這個文件的主次設備號,可以輸入如下命令:
ls /dev/miscbeep -l
輸入如下命令測試 BEEP:
./miscbeepApp /dev/miscbeep 1 //打開 BEEP
./miscbeepApp /dev/miscbeep 0 //關閉 BEEP
觀察一下 BEEP 能否打開和關閉,如果可以的話就說明驅動工作正常。
卸載驅動,輸入如下命令:
rmmod miscbeep.ko