2.5 misc驱动

MISC设备简介

在linux中有许多设备无法进行精确分类,为了方便管理和节省主设备号,通常将此类设备规划到MISC(混杂设备)类中。MISC设备本质是字符设备,主设备号统一为10,次设备号可以由系统自动分配,也可以由用户指定。

struct miscdevic对象

Linux用 struct miscdevic 对象表示一个MISC设备,其核心成员如下:

/* 子设备号,为 MISC_DYNAMIC_MINOR 表示由系统自动分配 */
int minor; 
/* 设备名字,创建的设备文件也叫这个名字 */
const char *name;
/* 设备操作函数集 */
const struct file_operations *fops; 

注册MISC设备

MISC设备的注册相对于注册传统字符设备来说简单了很多,它只需要调用int misc_register(struct miscdevice *misc)函数即可完成注册,以下是传统字符设备注册流程和MISC字符设备注册流程的对比:

/* MISC设备注册流程 */
//调用misc_register完成注册
misc_register();

/* 传统字符设备注册流程 */
//申请/注册设备号
alloc_chrdev_region(); 
//初始化 cdev
cdev_init();
//添加 cdev
cdev_add();
//创建类
class_create();
//创建设备
device_create();

注销MISC设备

MISC设备的注销也非常简单,只需要调用函数void misc_deregister(struct miscdevice *misc)即可完成MISC设备的注销,以下是传统字符设备注册流程和MISC字符设备注销流程的对比:

/* MISC设备注销流程 */
//调用misc_deregister完成注销
misc_deregister();

/* 传统字符设备注销流程 */
//删除设备
device_destroy();
//删除类
class_destroy();
//删除 cdev
cdev_del();
//注销设备号
unregister_chrdev_region();

代码实现

代码在上一章节的基础上进行修改而来,主要修改了register_led函数和unregister_led函数,代码全部内容如下:


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h>

//次设备号
#define LED_MINOR				MISC_DYNAMIC_MINOR
//设备名称
#define LED_NAME				"test_led"

//总线基地址定义
#define PERIPH_BASE				0x40000000
#define MPU_AHB4_PERIPH_BASE	(PERIPH_BASE + 0x10000000)
#define RCC_BASE				(MPU_AHB4_PERIPH_BASE + 0x0000)
#define GPIOI_BASE				(MPU_AHB4_PERIPH_BASE + 0xA000)

//RCC寄存器地址,用于使能GPIOI的时钟(RCC_BASE + 0XA28)
//RCC时钟寄存器,控制GPIOI的时钟
#define RCC_MP_AHB4ENSETR_OFFSET	0XA28

//GPIO寄存器地址,用于控制GPIO
//端口模式寄存器,配置输入、输出、复用、模拟
#define GPIOI_MODER_OFFSET			0x0000
//端口输出类型寄存器
#define GPIOI_OTYPER_OFFSET			0x0004
//端口输出速度寄存器
#define GPIOI_OSPEEDR_OFFSET		0x0008
//端口上拉/下拉寄存器
#define GPIOI_PUPDR_OFFSET			0x000C
//端口置位/复位寄存器,其中高16位用于控制置位,低16位用于控制复位
#define GPIOI_BSRR_OFFSET			0x0018

struct led_device{
	//IO内存虚拟地址
	void __iomem *RCC_ADDR;
	void __iomem *GPIOI_ADDR;

	//寄存器虚拟地址
	void __iomem *RCC_MP_AHB4ENSETR;
	void __iomem *GPIOI_MODER;
	void __iomem *GPIOI_OTYPER;
	void __iomem *GPIOI_OSPEEDR;
	void __iomem *GPIOI_PUPDR;
	void __iomem *GPIOI_BSRR;

	//I/O内存资源,同一段IO内存只能申请一次,但是可以映射多次
	struct resource *RCC_resource;
	struct resource *GPIOI_resource;

	//混杂设备对象
	struct miscdevice misc;
	//led状态,0灭,1亮
	int led_state;
};

//led句柄
static struct led_device led;

void led_switch(struct led_device *led, uint8_t state)
{
	uint32_t val = 0;

	if(state)
	{
		//设置GPIO0为低电平,点亮led
		val = readl(led->GPIOI_BSRR);
		val |= (1 << 16);
		writel(val, led->GPIOI_BSRR);
	}
	else
	{
		//设置GPIOI0为高电平,熄灭led
		val = readl(led->GPIOI_BSRR);
		val |= (1 << 0);
		writel(val, led->GPIOI_BSRR);
	}
}

static int led_open(struct inode *inode, struct file *file)
{
	uint32_t minor;

	//提取次设备号
	minor = MINOR(inode->i_rdev);

	//通过次设备号区分具体的设备
	if(minor == led.misc.minor)
	{
		file->private_data = &led;
		return 0;
	}
	else
		return EINVAL;
}

static int led_release(struct inode *inode, struct file *file)
{
	return 0;
}

static ssize_t led_read(struct file *file, char __user *buf, size_t len, loff_t *pos)
{
	char kernal_data;
	struct led_device *led = file->private_data;

	if(led->led_state == 0)
		kernal_data = '0';
	else if(led->led_state == 1)
		kernal_data = '1';
	else
		return -EINVAL;
	
	//将数据从内核空间拷贝到用户空间
	if(copy_to_user(buf, &kernal_data, 1))
		return -EFAULT;

	return 1;
}

static ssize_t led_write(struct file *file, const char __user *buf, size_t len, loff_t *pos)
{
	char user_data;
	struct led_device *led = file->private_data;
	
	//将数据从用户框架拷贝到内核框架
	if(copy_from_user(&user_data, buf, 1))
		return -EFAULT;

	if(user_data == '0')
	{
		//熄灭led
		led->led_state = 0;
		led_switch(led, 0);
	}
	else if(user_data == '1')
	{
		//点亮led
		led->led_state = 1;
		led_switch(led, 1);
	}
	else
		return -EINVAL;

	return len;
}

//取消I/O内存映射
static void io_unmap(struct led_device *led)
{
	if(led->RCC_ADDR)
	{
		/* 取消IO内存映射
		 * addr 映射后的虚拟地址
		 */
		iounmap(led->RCC_ADDR);
		led->RCC_ADDR = NULL;
		led->RCC_MP_AHB4ENSETR = NULL;
	}
	if(led->GPIOI_ADDR)
	{
		iounmap(led->GPIOI_ADDR);
		led->GPIOI_ADDR = NULL;
		led->GPIOI_MODER = NULL;
		led->GPIOI_OTYPER = NULL;
		led->GPIOI_OSPEEDR = NULL;
		led->GPIOI_PUPDR = NULL;
		led->GPIOI_BSRR = NULL;
	}

//	if(led->RCC_resource)
//	{
//		/* 释放I/O内存资源
//		 * start 物理地址起始
//		 * n 大小
//		 */
//		release_mem_region(RCC_BASE, 4096);
//		led->RCC_resource = NULL;
//	}
//	if(led->GPIOI_resource)
//	{
//		release_mem_region(GPIOI_BASE, 128);
//		led->GPIOI_resource = NULL;
//	}
}

//进行I/O内存映射
static int io_map(struct led_device *led)
{
//	//在系统中I/O内存资源不可以被重复申请,因为内核中的官方驱动模块依据申请了此段IO内存资源,所以这里无法再次申请
//	/* 申请IO内存资源
//	 * start 物理地址起始
//	 * n 大小
//	 * name 内存资源名称
//	 * 成功返回内存资源句柄
//	 */
//	led->RCC_resource = request_mem_region(RCC_BASE, 4096, "RCC_BASE");
//	if(!led->RCC_resource)
//	{
//		io_unmap(led);
//		return -EIO;
//	}
//	led->GPIOI_resource = request_mem_region(GPIOI_BASE, 128, "GPIOI_BASE");
//	if(!led->GPIOI_resource)
//	{
//		io_unmap(led);
//		return -EIO;
//	}

	//在系统中I/O内存可以被重复映射
	/* 映射IO寄存器
	 * port 寄存器物理地址
	 * size 映射大小
	 * 成功返回映射后的虚拟地址
	 **/
	led->RCC_ADDR = ioremap(RCC_BASE, 4096);
	if(!led->RCC_ADDR)
	{
		io_unmap(led);
		return -EIO;
	}
	led->RCC_MP_AHB4ENSETR = led->RCC_ADDR + RCC_MP_AHB4ENSETR_OFFSET;
	led->GPIOI_ADDR = ioremap(GPIOI_BASE, 128);
	if(!led->GPIOI_ADDR)
	{
		io_unmap(led);
		return -EIO;
	}
	led->GPIOI_MODER = led->GPIOI_ADDR + GPIOI_MODER_OFFSET;
	led->GPIOI_OTYPER = led->GPIOI_ADDR + GPIOI_OTYPER_OFFSET;
	led->GPIOI_OSPEEDR = led->GPIOI_ADDR + GPIOI_OSPEEDR_OFFSET;
	led->GPIOI_PUPDR = led->GPIOI_ADDR + GPIOI_PUPDR_OFFSET;
	led->GPIOI_BSRR = led->GPIOI_ADDR + GPIOI_BSRR_OFFSET;

	return 0;
}

static void gpio_init(struct led_device *led)
{
	uint32_t val;

	/* 使能PI时钟 */
	val = readl(led->RCC_MP_AHB4ENSETR);
	val |= (0X1 << 8);
	writel(val, led->RCC_MP_AHB4ENSETR);
	/* 设置PI0通用的输出模式。*/
	val = readl(led->GPIOI_MODER);
	val &= ~(0X3 << 0);
	val |= (0X1 << 0);
	writel(val, led->GPIOI_MODER);
	/* 设置PI0为推挽模式。*/
	val = readl(led->GPIOI_OTYPER);
	val &= ~(0X1 << 0);
	writel(val, led->GPIOI_OTYPER);
	/* 设置PI0为高速。*/
	val = readl(led->GPIOI_OSPEEDR);
	val &= ~(0X3 << 0);
	val |= (0x2 << 0);
	writel(val, led->GPIOI_OSPEEDR);
	/* 设置PI0为上拉。*/
	val = readl(led->GPIOI_PUPDR);
	val &= ~(0X3 << 0);
	val |= (0x1 << 0);
	writel(val, led->GPIOI_PUPDR);
	/* 默认开启LED */
	led_switch(led, 1);
	led->led_state = 1;
}

static void gpio_deinit(struct led_device *led)
{
	uint32_t val;

	/* 关闭LED */
	led_switch(led, 0);
	led->led_state = 0;
	/* 设置PI0为悬空。*/
	val = readl(led->GPIOI_PUPDR);
	val &= ~(0X3 << 0);
	writel(val, led->GPIOI_PUPDR);
	/* 设置PI0通用的输入模式。*/
	val = readl(led->GPIOI_MODER);
	val &= ~(0X3 << 0);
	writel(val, led->GPIOI_MODER);
}

static struct file_operations led_ops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.release = led_release,
	.read = led_read,
	.write = led_write,
};
static int register_led(struct led_device *led)
{
	int result;

	//次设备号,如果为MISC_DYNAMIC_MINOR则表示动态分配
	led->misc.minor = LED_MINOR;
	//设备文件名,注册成功后会在/dev/目录创建相应的设备文件
	led->misc.name = LED_NAME;
	//设备操作函数集合
	led->misc.fops = &led_ops;
	//注册混杂设备
	result = misc_register(&led->misc);
	printk("MINOR %d\r\n", led->misc.minor);
	return result;
}

static void unregister_led(struct led_device *led)
{
	//注销混杂设备
	misc_deregister(&led->misc);
}

static int __init led_init(void)
{
	int result;

	//IO内存映射
	result = io_map(&led);
	if(result != 0)
	{
		printk("map io mem failed\r\n");
		return result;
	}
	//初始化LED设备
	gpio_init(&led);

	//注册led字符设备
	result = register_led(&led);
	if(result != 0)
	{
		gpio_deinit(&led);
		io_unmap(&led);
		printk("register led failed\r\n");
		return result;
	}

	return 0;
}

static void __exit led_exit(void)
{
	//注销led设备
	unregister_led(&led);
	//反初始化GPIO
	gpio_deinit(&led);
	//取消IO内存映射
	io_unmap(&led);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("led test");

上机实验

  1. 这里下载代码,并进行编译,然后拷贝到目标板的/root目录中
  2. 通过命令insmod misc.ko加载驱动,此时led默认点亮
    在这里插入图片描述
  3. 此时通过命令echo 0 > /dev/test_led可以熄灭LED,通过命令echo 1 > /dev/test_led可以点亮LED
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值