天天看點

裝置驅動Demo摘要背景介紹裝置驅動Demo

文章目錄

  • 摘要
  • 背景介紹
    • I/O結構
    • 裝置驅動模型
  • 裝置驅動Demo
    • 字元裝置驅動
    • 字元裝置驅動-misc使用
    • 塊裝置驅動

摘要

簡述計算機組成原理中的I/O裝置,了解裝置與驅動的關系。

沒有介紹裝置驅動模型的抽象(我不知道),給出了參考連結,可自行閱讀。

貼出了字元驅動和塊裝置驅動的示例代碼。

背景介紹

I/O結構

閱讀:計算機組成原理-03-系統總線| 南橋-wiki | Ch8-IO系統 --ppt | I/O控制方式

I/O裝置總的來說,挂載在總線上。(因為我不知道細節,比如說,兩者旁雜着控制器等。但總體看上去,I/O挂載在總線上)

CPU通過指令(軟體)來控制I/O硬體。這個軟體便是驅動程式。(非常粗糙)

I/O裝置有兩個重要的屬性:端口和記憶體。

  • 裝置驅動程式要直接通路外設或其接口卡上的實體電路,通常以寄存器的形式出現通路;外設寄存器也稱為I/O端口,通常包括控制寄存器、狀态寄存器和資料寄存器三類。(x通過端口控制I/O裝置)
  • 根據端口的訓示,讀寫裝置的記憶體。I/O記憶體可以編址或獨立編址。

Linux文檔中有一篇:Bus-Independent Device Accesses,我不是很明白它在說什麼。(下面三點,我不清楚)

  • 記錄I/O資源:linux下I/O資源管理
  • 管理I/O端口資源:request_region()、check_region()、release_region()
  • 管理I/O記憶體資源:request_mem_region()、check_mem_region()、release_mem_region()

裝置驅動模型

閱讀:統一裝置模型

關于裝置驅動模型的抽象,我不是很明白,可自行參考:Linux裝置模型(1)_基本概念

下面的三個示例,包含字元裝置驅動和塊裝置驅動。(填充一些資料結構)

裝置驅動Demo

代碼來源:動手實踐-編寫字元裝置驅動程式 、工程實踐-塊裝置驅動

由于沒有和實際硬體結合(即缺少驅動代碼控制端口,對I/O的記憶體進行操作),是以這三個示例驅動程式都比較簡單。

但通過這三個驅動demos,簡單能感受到“什麼是驅動”、“linux中一切皆檔案”。

字元裝置驅動

參考:Linux驅動篇(五)–字元裝置驅動(一)

代碼思路:申請裝置号-申請一個cdev結構體-向系統添加一個字元裝置-登出字元裝置

# include <linux/module.h>
# include <linux/fs.h>
# include <linux/uaccess.h>
# include <linux/init.h>
# include <linux/cdev.h>

# define DEMO_NAME "my_demo_dev"

static dev_t dev;
static struct cdev *demo_cdev;
static signed count = 1;


static int demodrv_open(struct inode *inode, struct file *file)
{
	int major = MAJOR(inode->i_rdev);
	int minor = MINOR(inode->i_rdev);

	printk("%s: major=%d, minor=%d\n",__func__,major,minor);

	return 0;
}


static ssize_t demodrv_read(struct file *file, char __user *buf,size_t lbuf,loff_t *ppos)
{
	printk("%s enter\n",__func__);
	
	return 0;
}


static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *f_pos)
{
	printk("%s enter\n",__func__);
	
	return 0;
}


static const struct file_operations demodrv_fops = {
	.owner = THIS_MODULE,
	.open = demodrv_open,
	.read = demodrv_read,
	.write = demodrv_write
};


static int __init simple_char_init(void)
{
	int ret;

	/**
	 * 向系統動态申請一個未被使用的裝置号;
	 * (主裝置号用來表示一個特定的驅動程式。次裝置号用來表示使用該驅動程式的各裝置)
	 * dev:存放傳回的裝置号
	 * 0:次裝置号的起始值
	 * count:次裝置号的個數
	 * DEMO_NAME:(裝置号标簽)
	*/ 
	ret = alloc_chrdev_region(&dev,0,count,DEMO_NAME); 
	if(ret)
	{
		printk("failed to allocate char device region\n");
		return ret;
	}

	/**
	 * 申請一個cdev結構體
	*/
	demo_cdev = cdev_alloc();
	if(!demo_cdev) 
	{
		printk("cdev_alloc failed\n");
		goto unregister_chrdev;
	}

	/**
	 * 初始化cdev:
	 * 	INIT_LIST_HEAD(&cdev->list);
	 *  kobject_init(&cdev->kobj, &ktype_cdev_default);
	 *  cdev->ops = fops; // 設定該驅動的檔案操作
	*/
	cdev_init(demo_cdev,&demodrv_fops);

	/**
	 * 向系統添加一個字元裝置
	*/
	ret = cdev_add(demo_cdev,dev,count);
	if(ret)
	{
		printk("cdev_add failed\n");
		goto cdev_fail;
	}

	printk("successed register char device: %s\n",DEMO_NAME);
	printk("Major number = %d,minor number = %d\n",MAJOR(dev),MINOR(dev));

	return 0;

cdev_fail:
	cdev_del(demo_cdev);

unregister_chrdev:
	unregister_chrdev_region(dev,count);

	return ret;
} 


static void __exit simple_char_exit(void)
{
	printk("removing device\n");

	if(demo_cdev)
		cdev_del(demo_cdev);

	unregister_chrdev_region(dev,count); // 登出字元裝置時,需要釋放裝置号
}

module_init(simple_char_init);
module_exit(simple_char_exit);

MODULE_LICENSE("GPL");
           

字元裝置驅動-misc使用

參考: 跟着核心學架構-misc子系統 | Linux核心源碼學習之kfifo

Linux 中有三大類裝置:字元,網絡,塊裝置,每一種裝置又細分為很多類,比如字元裝置就被預先分為很多種類,并在檔案中标記了這些種類都使用了哪個主裝置号,但即便如此,硬體千千萬,總還是有漏網之魚,對于這些難以劃分類别的字元裝置,Linux中使用"混雜",裝置來統一描述,并配置設定給他們一個共同的主裝置号10,隻用此裝置号進行區分裝置,,這些裝置主要包括随機數發生器,LCD,時鐘發生器等。此外,和很多同樣是對cdev進行再次封裝的子系統一樣,misc也會自動建立裝置檔案,免得每次寫cdev接口都要使用class_create()和device_create()等。
# include <linux/module.h>
# include <linux/fs.h>
# include <linux/uaccess.h>
# include <linux/init.h>
# include <linux/cdev.h>
//加入misc機制
# include <linux/miscdevice.h>
# include <linux/kfifo.h>

DEFINE_KFIFO(mydemo_fifo,char,64);

//裝置名
# define DEMO_NAME "my_demo_dev"

static struct device *mydemodrv_device;

static int demodrv_open(struct inode *inode, struct file *file)
{
	int major = MAJOR(inode->i_rdev);
	int minor = MINOR(inode->i_rdev);

	printk("%s: major=%d, minor=%d\n",__func__,major,minor);

	return 0;
}


static ssize_t demodrv_read(struct file *file, char __user *buf,size_t count,loff_t *ppos)
{
	int actual_readed;
	int ret;

	ret = kfifo_to_user(&mydemo_fifo,buf, count, &actual_readed); // 摘取隊列資料至使用者空間的函數
	if(ret)
		return -EIO;

	printk("%s,actual_readed=%d,pos=%lld\n",__func__,actual_readed,*ppos);

	return actual_readed;
}


static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *ppos)
{
	unsigned int actual_write;
	int ret;

	ret = kfifo_from_user(&mydemo_fifo,buf, count, &actual_write);
	if(ret)
		return -EIO;

	printk("%s: actual_write=%d,ppos=%lld\n",__func__,actual_write,*ppos);

	return actual_write;
}


static const struct file_operations demodrv_fops = {
	.owner = THIS_MODULE,
	.open = demodrv_open,
	.read = demodrv_read,
	.write = demodrv_write,
};

static struct miscdevice mydemodrv_misc_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEMO_NAME,
	.fops = &demodrv_fops,
};

static int __init simple_char_init(void)
{
	int ret;

	/**
	 * 主裝置号為10.
	 * 提供次裝置号,和檔案操作
	*/
	ret = misc_register(&mydemodrv_misc_device);
	if(ret)
	{
		printk("failed register misc device\n");
		return ret;
	}

	mydemodrv_device = mydemodrv_misc_device.this_device;

	printk("successed register char device: %s\n",DEMO_NAME);

	return 0;
} 


static void __exit simple_char_exit(void)
{
	printk("removing device\n");

	misc_deregister(&mydemodrv_misc_device);
}

module_init(simple_char_init);
module_exit(simple_char_exit);

MODULE_LICENSE("GPL");
           

塊裝置驅動

參考: Linux驅動 | 解讀塊裝置驅動的重要概念

建立一個RAM disk。RAM disk是通過使用軟體将RAM模拟當做硬碟來使用的一種技術。

使用gendisk結構體來描述通用磁盤。該結構體中包含了對磁盤的操作方式,請求隊列(處理),裝置号。

代碼思路:填充gendisk結構-将初始化完成的gendisk結構添加到核心中-釋放gendisk結構。

#include <linux/fs.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <linux/errno.h>
#include <linux/hdreg.h>
#include <linux/version.h>

#define MY_DEVICE_NAME "myramdisk"

/**
 * RAM盤是通過使用軟體将RAM模拟當做硬碟來使用的一種技術
*/

static int mybdrv_ma_no, diskmb = 256, disk_size;
static char *ramdisk;
static struct gendisk *my_gd;
static spinlock_t lock;
static unsigned short sector_size = 512;
static struct request_queue *my_request_queue;

module_param_named(size, diskmb, int, 0);
static void my_request(struct request_queue *q)
{
	struct request *rq;
	int size, res = 0;
	char *ptr;
	unsigned nr_sectors, sector;
	pr_info("start handle request\n");

	/**
	 * 從請求隊列中擷取一個請求。
	 * 在v5中,這個函數同樣被删除
	*/
	rq = blk_fetch_request(q);
	while (rq) {
		nr_sectors = blk_rq_cur_sectors(rq); // 目前要傳遞的sector個數
		sector = blk_rq_pos(rq); // blk_rq_pos():the current sector

		ptr = ramdisk + sector * sector_size;
		size = nr_sectors * sector_size;

		if ((ptr + size) > (ramdisk + disk_size)) {
			pr_err("end of device\n");
			goto done;
		}

		if (rq_data_dir(rq)) { // 處理寫請求
			pr_info("writing at sector %d, %u sectors\n",
				sector, nr_sectors);
			memcpy(ptr, bio_data(rq->bio), size);
		} else { // 處理讀請求
			pr_info("reading at sector %d, %u sectors\n",
				sector, nr_sectors);
			memcpy(bio_data(rq->bio), ptr, size);
		}
done:
		if (!__blk_end_request_cur(rq, res))
			rq = blk_fetch_request(q);
	}
	pr_info("handle request done\n");
}

static int my_ioctl(struct block_device *bdev, fmode_t mode,
		    unsigned int cmd, unsigned long arg)
{
	long size;
	struct hd_geometry geo;

	pr_info("cmd=%d\n", cmd);

	// 定義了一個io請求:HDIO_GETGEO-get device geometry
	switch (cmd) { 
	case HDIO_GETGEO:
		pr_info("HIT HDIO_GETGEO\n");
		/*
		 * get geometry: we have to fake one...
		 */
		size = disk_size;
		size &= ~0x3f;
		geo.cylinders = size>>6;
		geo.heads = 2;
		geo.sectors = 16;
		geo.start = 4;

		if (copy_to_user((void __user *)arg, &geo, sizeof(geo)))
			return -EFAULT;

		return 0;
	}
	pr_warn("return -ENOTTY\n");

	return -ENOTTY;
}

static const struct block_device_operations mybdrv_fops = {
	.owner = THIS_MODULE,
	.ioctl = my_ioctl,
};

static int __init my_init(void)
{
	disk_size = diskmb * 1024 * 1024;
	spin_lock_init(&lock);

	ramdisk = vmalloc(disk_size);
	if (!ramdisk)
		return -ENOMEM;

	/**
	 * 塊裝置初始化請求隊列,該函數已被删除
	 * https://lore.kernel.org/lkml/[email protected]/T/
	 * 每一塊裝置都會有一個隊列,當需要對裝置操作時,把請求放在隊列中
	*/
	my_request_queue = blk_init_queue(my_request, &lock);
	if (!my_request_queue) {
		vfree(ramdisk);
		return -ENOMEM;
	}

	/**
	 * 設定邏輯塊的大小:這應該設定為儲存設備可以尋址的盡可能低的塊大小;預設值512涵蓋了大多數硬體。
	*/
	blk_queue_logical_block_size(my_request_queue, sector_size);

	/**
	 * 注冊一個新的塊裝置(号)。
	 * 第一個參數:主裝置号。等于0,表示嘗試主動申請一個未使用的主裝置号。
	 * 第二個參數:裝置名
	*/
	mybdrv_ma_no = register_blkdev(0, MY_DEVICE_NAME);
	if (mybdrv_ma_no < 0) {
		pr_err("Failed registering mybdrv, returned %d\n",
		       mybdrv_ma_no);
		vfree(ramdisk);
		return mybdrv_ma_no;
	}

	/**
	 * 配置設定一個gendisk(通用磁盤的資料結構)
	*/
	my_gd = alloc_disk(16);
	if (!my_gd) {
		unregister_blkdev(mybdrv_ma_no, MY_DEVICE_NAME);
		vfree(ramdisk);
		return -ENOMEM;
	}

	my_gd->major = mybdrv_ma_no; // 填充主裝置号
	my_gd->first_minor = 0;      // 次裝置号,從0開始
	my_gd->fops = &mybdrv_fops;  // 該裝置的操作
	strcpy(my_gd->disk_name, MY_DEVICE_NAME); // 裝置名
	my_gd->queue = my_request_queue;   // 裝置操作的請求隊列
	set_capacity(my_gd, disk_size / sector_size); // 設定容量,以sector_size為機關
	add_disk(my_gd); // 将初始化完成的gendisk結構添加到核心中

	pr_info("device successfully   registered, Major No. = %d\n",
		mybdrv_ma_no);
	pr_info("Capacity of ram disk is: %d MB\n", diskmb);

	return 0;
}

static void __exit my_exit(void)
{
	del_gendisk(my_gd);
	put_disk(my_gd);
	unregister_blkdev(mybdrv_ma_no, MY_DEVICE_NAME);
	pr_info("module successfully unloaded, Major No. = %d\n", mybdrv_ma_no);
	blk_cleanup_queue(my_request_queue);
	vfree(ramdisk); // ramdisk釋放磁盤空間
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("Benshushu");
MODULE_LICENSE("GPL v2");
           

繼續閱讀