【linux系列】新增Linux块设备驱动

编译环境

  • 虚拟机操作系统: ubuntu-20.04.3-desktop-amd64
  • 原系统内核版本:5.11.0-46-generic
  • 内核源码版本:5.11.1

修改任务

普通任务
  • 写一个块设备驱动,可以动态加载和卸载该设备驱动,进行读写操作,使用模块编译方式。
  • 向系统注册、注销块设备,可以通过程序或命令行使用该块设备。
  • 使用内存分配的函数,设置块设备的最小容量为128MB ,可以把数据写入块设备,并读取出来。
进阶任务
  • 重新编译Linux内核,将驱动以模块的形式编译入内核。

编写驱动

  • 这里提供一段编写好的块设备驱动程序(代码来源 在此致以感谢)
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/blk_types.h>
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#include <uapi/linux/hdreg.h> //for struct hd_geometry
#include <uapi/linux/cdrom.h> //for CDROM_GET_CAPABILITY

#ifndef SUCCESS
#define SUCCESS 0
#endif

//This defines are available in blkdev.h from kernel 4.17 (vanilla).
#ifndef SECTOR_SHIFT 
#define SECTOR_SHIFT 9
#endif
#ifndef SECTOR_SIZE
#define SECTOR_SIZE (1 << SECTOR_SHIFT)
#endif

// constants - instead defines
static const char* _hwhdev_name = "hwhdev";
static const size_t _hwhdev_buffer_size = 32 *1024 * PAGE_SIZE;

// types
typedef struct hwhdev_cmd_s
{
    //nothing
} hwhdev_cmd_t;

// The internal representation of our device
typedef struct hwhdev_device_s
{
    sector_t capacity;			    // Device size in bytes
    u8* data;			    // The data aray. u8 - 8 bytes
    atomic_t open_counter;			// How many openers

    struct blk_mq_tag_set tag_set;
    struct request_queue *queue;	// For mutual exclusion

    struct gendisk *disk;		// The gendisk structure
} hwhdev_device_t;

// global variables 

static int _hwhdev_major = 0;
static hwhdev_device_t* _hwhdev_device = NULL;


// functions
static int hwhdev_allocate_buffer(hwhdev_device_t* dev)
{
    dev->capacity = _hwhdev_buffer_size >> SECTOR_SHIFT;
    //dev->data = kmalloc(dev->capacity << SECTOR_SHIFT, GFP_KERNEL); //
    dev->data = vmalloc(dev->capacity << SECTOR_SHIFT);
    if (dev->data == NULL) {
        printk(KERN_WARNING "hwhdev: vmalloc failure.\n");
        return -ENOMEM;
    }

    return SUCCESS;
}

static void hwhdev_free_buffer(hwhdev_device_t* dev)
{
    if (dev->data) {
        vfree(dev->data);

        dev->data = NULL;
        dev->capacity = 0;
    }
}

static void hwhdev_remove_device(void)
{
    hwhdev_device_t* dev = _hwhdev_device;
    if (dev == NULL)
        return;

    if (dev->disk)
        del_gendisk(dev->disk);

    if (dev->queue) {
        blk_cleanup_queue(dev->queue);
        dev->queue = NULL;
    }

    if (dev->tag_set.tags)
        blk_mq_free_tag_set(&dev->tag_set);

    if (dev->disk) {
        put_disk(dev->disk);
        dev->disk = NULL;
    }

    hwhdev_free_buffer(dev);

    vfree(dev);
    _hwhdev_device = NULL;

    printk(KERN_WARNING "hwhdev: simple block device was removed\n");
}

static int do_simple_request(struct request *rq, unsigned int *nr_bytes)
{
    int ret = SUCCESS;
    struct bio_vec bvec;
    struct req_iterator iter;
    hwhdev_device_t *dev = rq->q->queuedata;
    loff_t pos = blk_rq_pos(rq) << SECTOR_SHIFT;
    loff_t dev_size = (loff_t)(dev->capacity << SECTOR_SHIFT);

    printk(KERN_WARNING "hwhdev: request start from sector %lld \n", blk_rq_pos(rq));
    
    rq_for_each_segment(bvec, rq, iter)
    {
        unsigned long b_len = bvec.bv_len;

        void* b_buf = page_address(bvec.bv_page) + bvec.bv_offset;

        if ((pos + b_len) > dev_size)
            b_len = (unsigned long)(dev_size - pos);

        if (rq_data_dir(rq))//WRITE
            memcpy(dev->data + pos, b_buf, b_len);
        else//READ
            memcpy(b_buf, dev->data + pos, b_len);

        pos += b_len;
        *nr_bytes += b_len;
    }

    return ret;
}

static blk_status_t _queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data* bd)
{
    unsigned int nr_bytes = 0;
    blk_status_t status = BLK_STS_OK;
    struct request *rq = bd->rq;

    //we cannot use any locks that make the thread sleep
    blk_mq_start_request(rq);

    if (do_simple_request(rq, &nr_bytes) != SUCCESS)
        status = BLK_STS_IOERR;

    printk(KERN_WARNING "hwhdev: request process %d bytes\n", nr_bytes);

#if 0 //simply and can be called from proprietary module 
    blk_mq_end_request(rq, status);
#else //can set real processed bytes count 
    if (blk_update_request(rq, status, nr_bytes)) //GPL-only symbol
        BUG();
    __blk_mq_end_request(rq, status);
#endif

    return BLK_STS_OK;//always return ok
}

static struct blk_mq_ops _mq_ops = {
    .queue_rq = _queue_rq,
};

static int _open(struct block_device *bdev, fmode_t mode)
{
    hwhdev_device_t* dev = bdev->bd_disk->private_data;
    if (dev == NULL) {
        printk(KERN_WARNING "hwhdev: invalid disk private_data\n");
        return -ENXIO;
    }

    atomic_inc(&dev->open_counter);

    printk(KERN_WARNING "hwhdev: device was opened\n");

    return SUCCESS;
}
static void _release(struct gendisk *disk, fmode_t mode)
{
    hwhdev_device_t* dev = disk->private_data;
    if (dev) {
        atomic_dec(&dev->open_counter);

        printk(KERN_WARNING "hwhdev: device was closed\n");
    }
    else
        printk(KERN_WARNING "hwhdev: invalid disk private_data\n");
}

static int _getgeo(hwhdev_device_t* dev, struct hd_geometry* geo)
{
    sector_t quotient;

    geo->start = 0;
    if (dev->capacity > 63) {

        geo->sectors = 63;
        quotient = (dev->capacity + (63 - 1)) / 63;

        if (quotient > 255) {
            geo->heads = 255;
            geo->cylinders = (unsigned short)((quotient + (255 - 1)) / 255);
        }
        else {
            geo->heads = (unsigned char)quotient;
            geo->cylinders = 1;
        }
    }
    else {
        geo->sectors = (unsigned char)dev->capacity;
        geo->cylinders = 1;
        geo->heads = 1;
    }
    return SUCCESS;
}

static int _ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg)
{
    int ret = -ENOTTY;
    hwhdev_device_t* dev = bdev->bd_disk->private_data;

    printk(KERN_WARNING "hwhdev: ioctl %x received\n", cmd);

    switch (cmd) {
        case HDIO_GETGEO:
        {
            struct hd_geometry geo;

            ret = _getgeo(dev, &geo );
            if (copy_to_user((void *)arg, &geo, sizeof(struct hd_geometry)))
                ret = -EFAULT;
            else
                ret = SUCCESS;
            break;
        }
        case CDROM_GET_CAPABILITY: //0x5331  / * get capabilities * / 
        {
            struct gendisk *disk = bdev->bd_disk;

            if (bdev->bd_disk && (disk->flags & GENHD_FL_CD))
                ret = SUCCESS;
            else
                ret = -EINVAL;
            break;
        }
    }

    return ret;
}
#ifdef CONFIG_COMPAT
static int _compat_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg)
{
    // CONFIG_COMPAT is to allow running 32-bit userspace code on a 64-bit kernel
    return -ENOTTY; // not supported
}
#endif

static const struct block_device_operations _fops = {
    .owner = THIS_MODULE,
    .open = _open,
    .release = _release,
    .ioctl = _ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = _compat_ioctl,
#endif
};

//添加设备
static int hwhdev_add_device(void)
{
    int ret = SUCCESS;

    hwhdev_device_t* dev = kzalloc(sizeof(hwhdev_device_t), GFP_KERNEL);
    if (dev == NULL) {
        printk(KERN_WARNING "hwhdev: unable to allocate %ld bytes\n", sizeof(hwhdev_device_t));
        return -ENOMEM;
    }
    _hwhdev_device = dev;

    do{
        ret = hwhdev_allocate_buffer(dev);
        if(ret)
            break;

#if 0 //simply variant with helper function blk_mq_init_sq_queue. It`s available from kernel 4.20 (vanilla).
        {//configure tag_set
            struct request_queue *queue;

            dev->tag_set.cmd_size = sizeof(hwhdev_cmd_t);
            dev->tag_set.driver_data = dev;

            queue = blk_mq_init_sq_queue(&dev->tag_set, &_mq_ops, 128, BLK_MQ_F_SHOULD_MERGE | BLK_MQ_F_SG_MERGE);
            if (IS_ERR(queue)) {
                ret = PTR_ERR(queue);
                printk(KERN_WARNING "hwhdev: unable to allocate and initialize tag set\n");
                break;
            }
            dev->queue = queue;
        }
#else   // more flexible variant
        {//configure tag_set
            dev->tag_set.ops = &_mq_ops;
            dev->tag_set.nr_hw_queues = 1;
            dev->tag_set.queue_depth = 128;
            dev->tag_set.numa_node = NUMA_NO_NODE;
            dev->tag_set.cmd_size = sizeof(hwhdev_cmd_t);
            dev->tag_set.flags = BLK_MQ_F_SHOULD_MERGE ;
            dev->tag_set.driver_data = dev;

            ret = blk_mq_alloc_tag_set(&dev->tag_set);
            if (ret) {
                printk(KERN_WARNING "hwhdev: unable to allocate tag set\n");
                break;
            }
        }

        {//configure queue
            struct request_queue *queue = blk_mq_init_queue(&dev->tag_set);
            if (IS_ERR(queue)) {
                ret = PTR_ERR(queue);
                printk(KERN_WARNING "hwhdev: Failed to allocate queue\n");
                break;
            }
            dev->queue = queue;
        }
#endif
        dev->queue->queuedata = dev;

        {// configure disk
            struct gendisk *disk = alloc_disk(1); //only one partition 
            if (disk == NULL) {
                printk(KERN_WARNING "hwhdev: Failed to allocate disk\n");
                ret = -ENOMEM;
                break;
            }

            disk->flags |= GENHD_FL_NO_PART_SCAN; //only one partition 
            //disk->flags |= GENHD_FL_EXT_DEVT;
            disk->flags |= GENHD_FL_REMOVABLE;

            disk->major = _hwhdev_major;
            disk->first_minor = 0;
            disk->fops = &_fops;
            disk->private_data = dev;
            disk->queue = dev->queue;
            sprintf(disk->disk_name, "hwhdev%d", 0);
            set_capacity(disk, dev->capacity);

            dev->disk = disk;
            add_disk(disk);
        }

        printk(KERN_WARNING "hwhdev: simple block device was created\n");
    }while(false);

    if (ret){
        hwhdev_remove_device();
        printk(KERN_WARNING "hwhdev: Failed add block device\n");
    }

    return ret;
}

static int __init hwhdev_init(void)
{
    int ret = SUCCESS;

    _hwhdev_major = register_blkdev(_hwhdev_major, _hwhdev_name);
    if (_hwhdev_major <= 0){
        printk(KERN_WARNING "hwhdev: unable to get major number\n");
        return -EBUSY;
    }

    ret = hwhdev_add_device();
    if (ret)
        unregister_blkdev(_hwhdev_major, _hwhdev_name);
        
    return ret;
}

static void __exit hwhdev_exit(void)
{
    hwhdev_remove_device();

    if (_hwhdev_major > 0)
        unregister_blkdev(_hwhdev_major, _hwhdev_name);
}

module_init(hwhdev_init);
module_exit(hwhdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Code Imp");
  • 修改 _sblkdev_buffer_size 设置块设备的大小

编写 Makefile

  • 这里提供一段自行编写的 Makefile
ifneq ($(KERNELRELEASE),) 
obj-m := hwhdev.o 
 
else 
PWD := $(shell pwd) #当前文件夹目录
KDIR := /usr/src/linux-5.11.1/ #内核源码目录
all: 
	$(MAKE) -C $(KDIR) M=$(PWD) modules 
clean: 
	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions 
endif

编译驱动模块

  • 由于 Makefile 中提供了 all 指令,可以直接执行 sudo make all,进行编译
  • 这里 sudo make all 等价于执行了 sudo make -C /usr/src/linux-5.11.1 M=$PWD

安装驱动模块

  • 安装模块
sudo insmod hwhdev.ko
  • 查看已安装模块
lsmod

效果如下:
在这里插入图片描述

挂载设备

  • 格式化磁盘
sudo mkdosfs /dev/hwhdev0

注:其中 hwhdev0 是设备名,可以使用 lsblk 命令查看

  • 挂载设备到 /mnt
sudo mount /dev/hwhdev0 /mnt

效果如下:
在这里插入图片描述

  • 卸载设备
sudo umount /mnt

效果如下:
在这里插入图片描述

进阶任务

  • 将驱动以模块的形式编译入内核
  • 所需修改的文件(从源码目录开始)如下
drivers/Kconfig
drivers/Makefile

drivers/hwhdev/Kconfig #需要新建
drivers/hwhdev/Makefile

修改 drivers/hwhdev/Kconfig
  • 新建 Kconfig 并添加如下内容
config HWH_DEV
	tristate "HWH_DEV driver support"

效果如下:
在这里插入图片描述

修改 drivers/hwhdev/Makefile
  • 对应添加一行 obj-
obj-$(CONFIG_HWH_DEV)	+= hwhdev.o

效果如下:
在这里插入图片描述

修改 devices/Kconfig
  • endmenu 上面对应添加一行 source
    效果如下:
    在这里插入图片描述
修改 devices/Makefile
  • 在最后一行对应添加一行 obj-
    在这里插入图片描述
编译安装
  • 修改配置

    sudo make gconfig
    
  • Device drivers 下找到 HWH_DEV 设置为模块编译(若想要永久安装模块可以设为内核编译),如下图
    在这里插入图片描述

  • 保存配置

  • 编译安装内核,重启

  • 使用 modprobe hwhdev 安装模块

  • 0
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鞠杉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值