Linux驱动开发基础(二)

一、地址映射

1.MMU原理

MMU(Memory Manage Unit):内存管理单元
MMU主要功能:

①完成虚拟空间物理空间的映射

②内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性

虚拟空间到物理空间的映射即地址映射。对于 32 位的处理器来说,虚拟地址(VA,Virtual Address
)范围是 2^32=4GB;阿尔法开发板上有512MB 的 DDR3,即物理内存。物理内存512MB经过 MMU 可以将其映射到整个 4GB 的虚拟空间

Linux内核启动的时候会初始化MMU,设置好内存映射,CPU访问的都是虚拟地址

在老版本的 Linux 中要求处理器必须有 MMU,但是现在Linux 内核已经支持无 MMU 的处理器了
 

2.内存映射函数

LInux初始化时开启了MMU,并且设置了内存映射,CPU只能访问虚拟地址,不能直接向寄存器地址写入数据,必须得到寄存器物理地址在Linux系统中对应的虚拟地址。物理内存和虚拟内存之间的转换,需要用到: ioremap 和 iounmap两个函数

①ioremap

ioremap函数用于获取指定物理地址空间对应的虚拟地址空间,定义在arch/arm/include/asm/io.h 文件中

#define ioremap(cookie,size) __arm_ioremap((cookie), (size),MT_DEVICE)

void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype)
{
    return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0));
}

 ioremap 是个宏定义,真正起作用的是函数__arm_ioremap。

phys_addr:要映射给的物理起始地址(cookie)
size:要映射的内存空间大小
mtype: ioremap 的类型,可以选择 MT_DEVICE、 MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC, ioremap 函数选择 MT_DEVICE
返回值: __iomem 类型的指针,指向映射后的虚拟空间首地址

 获取某个寄存器对应的虚拟地址

#define addr (0X020E0068)  // 物理地址

static void __iomem*  va; //指向映射后的虚拟空间首地址的指针

va=ioremap(addr, 4);   // 得到虚拟地址首地址

 阿尔法的寄存器是 4 字节,因此映射的内存长度为 4,映射完成后va进行读写操作,就是对物理地址寄存器操作。

②iounmap

卸载驱动的时需要使用 iounmap 函数释放掉 ioremap 函数所做的映射
 

void iounmap (volatile void __iomem *addr)

参数 addr:要取消映射的虚拟地址空间首地址

取消寄存器的映射地址:iounmap(va);

 二、I/O内存访问函数

当外部寄存器或内存映射到 IO 空间时,称为 I/O 端口。当外部寄存器或内存映射到内存空间时,称为 I/O 内存。但是对于 ARM 来说没有 I/O 空间,因此 ARM 体系下只有 I/O 内存(可以直接理解为内存)。使用 ioremap 函数将寄存器的物理地址映射到虚拟地址后,可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

1.读操作函数

u8 readb(const volatile void __iomem *addr)

u16 readw(const volatile void __iomem *addr)

u32 readl(const volatile void __iomem *addr)

readb、 readw 和 readl 分别对应 8bit、 16bit 和 32bit 读操作,参数 addr 就是要读取写内存地址,返回值是读取到的数据
 

2.写操作函数

void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)

writeb、 writew 和 writel分别对应 8bit、 16bit 和 32bit 写操作,参数 value 是要写入的数值, addr 是要写入的地址
 

三、字符设备操作函数

在 Linux 中使用 cdev 结构体表示一个字符设备, cdev 结构体在 include/linux/cdev.h 文件中

struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};

在 cdev 中有两个重要的成员变量: ops 和 dev,字符设备文件操作函数集合file_operations 以及设备号 dev_t

1.cdev_init 函数

cdev_init 函数对cdev进行初始化

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
cdev :要初始化的 cdev 结构体变量

fops :字符设备文件操作函数集合

2.cdev_add 函数

cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量)
 

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
p :向要添加的字符设备(cdev 结构体变量)

dev :设备所使用的设备号

count :要添加的设备数量

3.cdev_del 函数

使用 cdev_del 函数在卸载驱动时从 Linux 内核中删除相应的字符设备

void cdev_del(struct cdev *p)
p :要删除的字符设备

cdev_del 和 unregister_chrdev_region 这两个函数合起来相当于 unregister_chrdev 函数
 

四、自动创建设备节点 

 使用modprobe加载驱动程序后还需要使用mknod指令创建设备节点。可以在驱动中实现自动创建设备节点的功能以后,使用 modprobe 加载驱动模块成功的话就会自动在/dev 目录下创建对应的设备文件

1.mdev机制

udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除, udev 可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件

使用modprobe 命令成功加载驱动模块后就会自动在/dev 目录下创建对应的设备节点文件,使用
rmmod 命令卸载驱动模块以后就删除掉/dev 目录下的设备节点文件。

使用 busybox 构建根文件系统的时, busybox 会创建一个 udev 的简化版本—mdev,所以在嵌入式 Linux 中使用mdev 来实现设备节点文件的自动创建与删除, Linux 系统中的热插拔事件也由mdev 管理。

2.创建类和删除类
 

自动创建设备节点是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。

首先要创建一个 class 类, class 是个结构体,定义在文件include/linux/device.h 里面

#define class_create(owner, name) \
({                                 \
    static struct lock_class_key __key; \
    __class_create(owner, name, &__key); \
})

struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)

class_create 是类创建函数, class_create 是个宏定义。展开以后如下:

struct class *class_create (struct module *owner, const char *name)
owner :THIS_MODULE
name :类名字
返回值:指向结构体 class 的指针,也就是创建的类

 卸载驱动程序的时需要删除掉类,类删除函数为 class_destroy

void class_destroy(struct class *cls);
 cls:要删除的类

3.创建设备

创建好类后还不能实现自动创建设备节点,还需要在这个类下创建一个设备。使用 device_create 函数在类下面创建设备

struct device *device_create(struct class *class,
        struct device *parent,
        dev_t devt,
        void *drvdata,
        const char *fmt, ...)

device_create 是个可变参数函数

class :设备要创建哪个类下面

parent :父设备,一般为 NULL,也就是没有父设备

devt 是设备号

drvdata :设备可能会使用的一些数据,一般为 NULL

fmt :设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件

返回值就是创建好的设备
 

 卸载驱动的时需要删除掉创建的设备,设备删除函数为 device_destroy,

void device_destroy(struct class *class, dev_t devt)
class :要删除的设备所处的类

devt :要删除的设备号

 注意:在卸载驱动时,要先删除设备,然后在在删除类。应该在创建设备时用了类。

 五、文件私有数据

每个硬件设备都有一些属性,如主设备号(dev_t),类(class)、设备(device)、开关状态(state)等等,在编写驱动的时可以将这些属性全部写成变量的形式

对于一个设备的所有属性信息最好将其设置为一个结构体。

/* 设备结构体 */
struct test_dev{
    dev_t devid; /* 设备号 */
    struct cdev cdev; /* cdev */
    struct class *class; /* 类 */
    struct device *device; /* 设备 */
    int major; /* 主设备号 */
    int minor; /* 次设备号 */
};

然后编写驱动 open 函数的时将设备结构体作为私有数据添加到设备文件中。在 open 函数里面设置好私有数据后,在 write、 read、 close 等函数中直接读取 private_data即可得到设备结构体


 


 

猜你喜欢

转载自blog.csdn.net/qq_53144843/article/details/123760019