背景
最近學習字元裝置驅動,其大緻的架構與流程都基本搞懂了,為了友善以後代碼重用,寫了一個較為完善的模闆,
功能包括自動建立字元裝置,/dev下自動建立裝置檔案,實作了open、read、write、release、ioctl等函數功能,
以後如果需要寫如:led、key、lcd等的字元裝置驅動,就不需要從O開始,可以直接用來修改調試,
代碼中有比較清晰的注釋,以及錯誤處理與回收機制,并且經過了初步測試,是通過的
該模闆的作用:編譯生成.ko檔案後,通過insmod *.ko加載子產品,會自動在/dev目錄下生成相應的裝置檔案,模闆中裝置檔案名稱為firdev,
同樣,解除安裝子產品時會自動将其删除,這主要是device與class這兩個結構體的功勞,代碼中都有注釋…
驅動模闆
#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/cdev.h>
#include <linux/ioctl.h>
/* 字元裝置數量、名稱、以及緩存大小 */
#define CHRDEV_CNT 1
#define CHRDEV_NAME "chrdev"
#define BUF_SIZE 256
#define CHRDEV_MAGIC 'k' //指令類型
#define CHRDEV_MAXNR 3 //最大指令序号
/* ioctl 自定義指令 */
#define CMD_OPEN (_IO(CHRDEV_MAGIC, 1))
#define CMD_CLOSE (_IO(CHRDEV_MAGIC, 2))
#define CMD_SET (_IO(CHRDEV_MAGIC, 3))
/* 自定義字元裝置結構體 */
struct chr_dev {
dev_t devnum; //裝置号
struct cdev *pcdev; //cdev
struct class *class; //類
struct device *device; //裝置
char kbuf[BUF_SIZE]; //資料存儲區
};
struct chr_dev firdev = {
.devnum = 0,
};
/*
* @brief 檔案打開函數
* @param inode : 傳遞給驅動的inode
file : 要打開的裝置檔案
* @retval 0 成功, 其他 失敗
*/
static int chrdev_open(struct inode *inode, struct file *file)
{
file->private_data = &firdev; //設定私有資料
firdev.devnum = inode->i_rdev; //擷取裝置号
printk(KERN_INFO "chrdev_open,devnum:%x\n", firdev.devnum);
return 0;
}
/*
* @brief 檔案關閉函數
* @param inode : 傳遞給驅動的inode
file : 要關閉的裝置檔案
* @retval 0 成功, 其他 失敗
*/
static int chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "chrdev_release\n");
return 0;
}
/*
* @brief 讀函數, 将核心中的資料拷貝到應用層
* @param file: 要打開的裝置檔案
buf : 傳回給使用者空間的資料緩沖區
cnt : 要讀取的資料長度
offt: 相對于檔案首位址的偏移
* @retval 讀取的位元組數, 負值表示讀取失敗
*/
ssize_t chrdev_read(struct file *filp, char __user *ubuf, size_t size, loff_t *ppos)
{
int ret = -1;
unsigned long p = *ppos;
unsigned int count = size;
struct chr_dev *dev = filp->private_data; //擷取私有資料
//有效長度判斷
if (p >= BUF_SIZE)
return 0;
if (count > BUF_SIZE - p)
count = BUF_SIZE - p;
//一定要用如下拷貝函數, 從核心空間拷貝至使用者
if(copy_to_user(ubuf, (void*)(dev->kbuf + p), count)) {
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
else {
*ppos += count;
ret = count;
printk(KERN_INFO "to user success...\n");
}
return ret;
}
/*
* @brief 寫函數, 将應用層傳遞過來的資料複制到核心中
* @param filp: 打開的檔案描述符
buf : 要寫給裝置寫入的資料
cnt : 要寫入的資料長度
offt: 相對于檔案首位址的偏移
* @retval 寫入的位元組數, 負值表示寫入失敗
*/
static ssize_t chrdev_write(struct file *filp, const char __user *ubuf, size_t size, loff_t *ppos)
{
int ret = -1;
unsigned long p = *ppos;
unsigned int count = size;
struct chr_dev *dev = filp->private_data;
//有效長度判斷
if(p >= BUF_SIZE)
return 0;
if (count > BUF_SIZE - p)
count = BUF_SIZE - p;
//一定要用如下拷貝函數,從使用者空間拷貝至核心
if (copy_from_user(dev->kbuf + p, ubuf, count)) {
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
else {
*ppos += count;
ret = count;
printk(KERN_INFO "from user success...\n");
}
return ret;
}
/*
* @brief 檔案定位函數
* @param filp: 打開的檔案描述符
whence: 偏移起始位置
offset: 偏移的步數
* @retval 偏移的位元組數, 負值表示偏移失敗
*/
static loff_t chrdev_llseek(struct file *filp, loff_t whence, int offset)
{
unsigned int newpos = 0;
switch(whence) {
case SEEK_SET: newpos = offset; break;
case SEEK_CUR: newpos = filp->f_pos + offset; break;
case SEEK_END: newpos = BUF_SIZE -1 + offset; break;
default: return -EINVAL;
}
if ((newpos < 0) || (newpos > BUF_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
/*
* @brief IO控制函數
* @param filp: 打開的檔案描述符
cmd: 指令
arg: 參數
* @retval 0表示執行成功, 負值表示失敗
*/
long chrdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
if (_IOC_TYPE(cmd) != CHRDEV_MAGIC)
return -EINVAL;
if (_IOC_NR(cmd) > CHRDEV_MAXNR)
return -EINVAL;
switch(cmd) {
case CMD_OPEN:
printk("IO open device!\n");
return 0;
case CMD_CLOSE:
printk("IO close device!\n");
return 0;
case CMD_SET:
printk("IO setup device, arg:%d\n", arg);
return 0;
default:
return -EINVAL;
}
return 0;
}
//檔案操作結構體, 外部操作此子產品的接口, 需要我們填充
//.owner:指向擁有這個結構的子產品的指針,用來在它的操作還在被使用時阻止子產品被解除安裝
static struct file_operations chrdev_fops = {
.owner = THIS_MODULE,
//應用層間接調用的就是如下接口
.open = chrdev_open, //打開裝置時調用
.release = chrdev_release,
.write = chrdev_write,
.read = chrdev_read,
.llseek = chrdev_llseek,
.unlocked_ioctl = chrdev_ioctl,
};
//子產品加載函數
static int __init chrdev_init(void)
{
int ret;
// 1.配置設定裝置号
ret = alloc_chrdev_region(&firdev.devnum, 0, CHRDEV_CNT, CHRDEV_NAME);
if(ret < 0) {
printk(KERN_ERR "alloc_chrdev_region fail\n");
goto chrdev_fail; //如果執行到這裡, 可以直接傳回
}
printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(firdev.devnum), MINOR(firdev.devnum));
// 2.初始化cdev, 并添加到系統
// cdev綁定file_operations與dev_t, 添加進系統進而産生了聯系
firdev.pcdev = cdev_alloc();
//firdev.pcdev->owner = THIS_MODULE;
//firdev.pcdev->ops = &chrdev_fops; //将cdev和file_operations進行綁定
cdev_init(firdev.pcdev, &chrdev_fops);//這條語句可代替上面兩條語句
ret = cdev_add(firdev.pcdev, firdev.devnum, CHRDEV_CNT);//将cdev結構體加入到系統中去
if (ret) {
printk(KERN_ERR "Unable to cdev_add\n");
goto cdev_fail;//如果執行到這裡, 說明前面裝置号配置設定成功了, 需要釋放掉
}
printk(KERN_INFO "cdev_add success\n");
// 3.建立類
// 注冊字元裝置驅動完成後, 添加裝置類的操作, 讓核心幫我們發資訊
// 給udev,讓udev自動建立和删除裝置檔案
firdev.class = class_create(THIS_MODULE, CHRDEV_NAME);
if (IS_ERR(firdev.class)) {
goto class_fail;//如果執行到這裡, 說明裝置号與cdev都配置設定成功了, 需要釋放掉
}
// 4.建立裝置
// 最後1個參數字元串,就是我們将來要在/dev目錄下建立的裝置檔案的名字
// 是以我們這裡要的檔案名是/dev/firdev
firdev.device = device_create(firdev.class, NULL, firdev.devnum, NULL, CHRDEV_NAME);
if (IS_ERR(firdev.device)) {
goto device_fail;
}
return 0;
device_fail:
device_destroy(firdev.class, firdev.devnum);
class_fail:
cdev_del(firdev.pcdev);
cdev_fail:
unregister_chrdev_region(firdev.devnum, CHRDEV_CNT);
chrdev_fail:
return -EINVAL;
}
//子產品解除安裝函數, 登出要跟建立時倒着來
static void __exit chrdev_exit(void)
{
// 銷毀裝置,即把建立的裝置檔案删掉
device_destroy(firdev.class, firdev.devnum);
// 銷毀類,釋放資源
class_destroy(firdev.class);
// 登出字元裝置驅動結構
cdev_del(firdev.pcdev);
// 然後登出申請到的裝置号
unregister_chrdev_region(firdev.devnum, CHRDEV_CNT);
}
//子產品加載與解除安裝時會調用如下接口
module_init(chrdev_init);
module_exit(chrdev_exit);
//下面這些都是跟子產品相關, 需要加上才能編譯
MODULE_LICENSE("GPL"); // 子產品許可證
MODULE_AUTHOR("author"); // 子產品作者
MODULE_DESCRIPTION("description"); // 子產品資訊
MODULE_ALIAS("alias"); // 子產品别名
測試代碼
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "streng.h"
int main()
{
int fd;
char Buf[128] = "This is char dev!";/*初始化Buf*/
printf("BUF: %s\n",Buf);
fd = open("/dev/chrdev", O_RDWR);/*打開裝置檔案*/
if (fd < 0) {
printf("Open chrdev Error!\n");
return -1;
}
write(fd, Buf, sizeof(Buf)); /*寫入裝置*/
lseek(fd, 0, SEEK_SET); /*需要重新定位檔案位置*/
strcpy(Buf,"Buf is NULL!"); /*清除Buf*/
printf("BUF: %s\n",Buf);
read(fd, Buf, sizeof(Buf)); /*讀出裝置*/
printf("BUF: %s\n",Buf); /*檢測結果*/
while(1) {
int cmd, arg;
printf("Input cmd:");
scanf("%d", &cmd);
if(cmd == 1) {
ioctl(fd, CMD_CLOSE);
printf("cmd:%d\n", cmd);
}
else if(cmd == 2) {
ioctl(fd, CMD_OPEN);
printf("cmd:%d\n", cmd);
}
else if(cmd == 3) {
printf("Input data:");
scanf("%d", &arg);
ioctl(fd, CMD_SET, arg);
}
else {
close(fd);
break;
}
}
return 0;
}
測試結果
将驅動模闆編譯生成.ko檔案,我的是first_drv.ko,拷貝至開發闆
然後執行:insmod first_drv.ko
[email protected]-IMX6U:/mnt/ttt# insmod first_drv.ko
major = 249, minor = 0.
cdev_add success
子產品加載成功,cat /proc/devices可看到firdev驅動,并且ls /dev可看到firdev目錄,這就是自動生成的裝置檔案
然後測試代碼測試,如下:

至此,字元裝置驅動學習告一段落!