天天看點

linux驅動: 字元裝置驅動模闆背景驅動模闆測試代碼測試結果

背景

最近學習字元裝置驅動,其大緻的架構與流程都基本搞懂了,為了友善以後代碼重用,寫了一個較為完善的模闆,

功能包括自動建立字元裝置,/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目錄,這就是自動生成的裝置檔案

然後測試代碼測試,如下:

linux驅動: 字元裝置驅動模闆背景驅動模闆測試代碼測試結果

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

繼續閱讀