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");
上机实验
- 从这里下载代码,并进行编译,然后拷贝到目标板的/root目录中
- 通过命令insmod misc.ko加载驱动,此时led默认点亮
- 此时通过命令echo 0 > /dev/test_led可以熄灭LED,通过命令echo 1 > /dev/test_led可以点亮LED