驱动入门的进一步深入

继上一篇驱动入门的进一步深入

*********************

注册字符设备驱动新接口

老接口:

ret = register_chrdev(MYMAJOR,MYNAME,&test_fops);

包括主设备号、名字、file_operations

新接口:

register_chrdev_region/alloc_chrdev_region + cdev_xx

register_chrdev_region:

需要指定设备号

alloc_chrdev_region :

内核分配设备号

cdev_xx:

包括cdev_init 、 cdev_add 、cdev_del等操作

设备号:

MKDEV、MAJOR、MINOR三个宏

MKDEV 返回值类型:dev_t类型

***************************************

指定设备号方法:

*

首先,定义static dev_t dev_id;

先用MKDEV申请一个设备号:

dev_id = MKDEV(MYMAJOR, 0); //设备号

MYMAJOR是指定的主设备号

后面的是次设备号

然后注册:

int register_chrdev_region(dev_t from, unsigned count, const char *name)

retval = register_chrdev_region(dev_id, MYCNT,MYNAME);

if (retval) {

printk(KERN_ERR "Unable to register minors for usb_device\n");

return retval;

}

用返回的dev_id

MYCNT:注册的数量

MYNAME:注册的名字

retval:返回值成功是0 ,失败返回负数

然后,注册驱动:

static struct cdev test_cdev;

static const struct file_operations test_fops

cdev_init(&test_cdev, &test_fops); //绑定两个结构体

retval = cdev_add(&test_cdev, dev_id, 1); //添加驱动

if (retval)

cdev_del(&test_cdev);

最后,注销

unregister_chrdev_region(dev_id, 1);

**************************************

上面就是完整的用新接口注册驱动的步骤

***********************

用内核自动分配设备号:

自动分配的话就不需要先用MKDEV申请设备号:

dev_id = MKDEV(MYMAJOR, 0); //设备号

而是直接用定义的结构体变量加上定义定义的子设备号,加上名字

就可以完成自动分配设备号

typedef u_long dev_t;(源结构体)

static dev_t mydev;

// 第1步:分配主次设备号

retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);

int alloc_chrdev_region(dev_t *dev, u[nsigned baseminor, unsigned count,

const char *name)

{

struct char_device_struct *cd;

cd = __register_chrdev_region(0, baseminor, count, name);

if (IS_ERR(cd))

return PTR_ERR(cd);

*dev = MKDEV(cd->major, cd->baseminor);

return 0;

}

*****************

这里对led灯的驱动操作

在模块层次代码中,应该要简洁,及对read进来的相关指令,这个指令应该是

简单的,例如1、2、3这样的,可以用宏定义

就是说,较为复杂的一些操作,例如对输入的指令的判断,用到的

strcmp等,应该在应用层完成

**********************

这里是对设备注册的一个操作

之前注册驱动时用到的是一个全局变量的结构体,

占据固定内存

static struct cdev test_cdev;

现在,用到

struct cdev *cdev_alloc(void)

{

struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);

if (p) {

INIT_LIST_HEAD(&p->list);

kobject_init(&p->kobj, &ktype_cdev_dynamic);

}

return p;

}

这一个函数,可以设置一个struct cdev类型的指针

struct cdev *cdev;

static struct cdev *test_cdev;

//注册驱动,使用cdev_alloc

test_cdev = cdev_alloc();

if (!test_cdev)

return -EINVAL;

test_cdev->owner = THIS_MODULE;

test_cdev->ops = test_fops;

//cdev_init(&test_cdev, &test_fops); //绑定两个结构体

这里使用的*指针的意义就是,用全局变量指定一个指针,只占一个指针大小的内存,

当需要使用时,就用cdev_alloc为指针动态分配一个堆内存,指向用的结构体,

需要注意的就是,在内核中,堆内存的释放一般都不是简单的对应关系,

它是内部有一个数的增加机制,有用到申请堆内存的就加一,用完就减一,

目前还不太了解其中机制

释放函数:

kfree(cls);

struct cdev {

struct kobject kobj;

struct module *owner;

const struct file_operations *ops;

struct list_head list;

dev_t dev;

unsigned int count;

};

这个就是用到的结构体,里面有指向模块、文件操作、链表、设备等

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

{

memset(cdev, 0, sizeof *cdev);

INIT_LIST_HEAD(&cdev->list);

kobject_init(&cdev->kobj, &ktype_cdev_default);

cdev->ops = fops;

}

这里不使用cdev_init,

而是直接实例化结构体其中的几个,

实际上也是和上面的函数是一样的,只不过是少实例几个而已

还有就是,上面实例化的owner

是在file_operation中的那个,

.owner = THIS_MODULE,

********************

总结:

以上的就是关于设备号的新旧使用,手动/自动分配使用,

关于设备注册的函数使用,

一个是全局变量直接定义,一个是定义一个全局变量的指针使用

关于设备的初始化,

可以用init函数,也可以直接实例化

*************

解决每次需要手动增添设备文件的不足:

手动添加设备文件:

root@sugar /root# mknod /dev/test c 250 0

解决方案:

udev(嵌入式中使用mdev)

udev 是 Linux 系统中的一个子系统,用于管理设备事件。

mdev :

BusyBox 提供的轻量级设备管理工具,相当于精简版的 udev。

rootfs中Etc/init.d/rcS文件

echo /sbin/mdev > /proc/sys/kernel/hotplug

mdev -s

echo /sbin/mdev > /proc/sys/kernel/hotplug

mdev -s

作用:初始化 /dev 目录下的设备节点。

原理:

-s 参数让 mdev 扫描 /sys 文件系统,根据当前已连接的硬件信息,

在 /dev 目录下动态创建设备节点(如 /dev/sda、/dev/ttyUSB0 等)

作用:将 /sbin/mdev 设置为内核的 热插拔事件处理程序。

补充:

/proc/sys/kernel/hotplug 的特殊性:

该文件存在于内存中(procfs),重启后失效,

需在启动脚本(如 /etc/rc.local)中持久化配置

完整 Linux 系统通常使用 udev 或 systemd-udevd,

但 mdev 更适合资源受限的嵌入式环境

总结:

这两条命令共同实现了 设备节点的动态创建/删除,

是嵌入式 Linux 自动识别硬件的基础

udev是应用层的一个应用程序

内核驱动和应用层udev之间有一套信息传输机制(netlink协议)

应用层启动udev,内核驱动中使用相应接口

驱动注册和注销时信息会传给udev,由udev在应用层进行设备

文件的创建和删除

使用相关函数添加设备文件:

1、class_create

//源定义源函数

static struct class *adb_dev_class;

#define class_create(owner, name) \

({ \

static struct lock_class_key __key; \

__class_create(owner, name, &__key); \

})

owner:fos中的owner,指定类的所有者是哪个模块

name:自定义的class名字

//创建类

test_dev_class = class_create(THIS_MODULE, "sugar_class");

if (IS_ERR(test_dev_class))

return -EINVAL;

//结束时销毁

void class_destroy(struct class *cls)

{

if ((cls == NULL) || (IS_ERR(cls)))

return;

class_unregister(cls);

}

class_destroy(test_dev_class);

2、device_create

//设备文件创建,就是mknod 创建的文件名

struct device *device_create(struct class *class, struct device *parent,

dev_t devt, void *drvdata, const char *fmt, ...)

{

va_list vargs;

struct device *dev;

va_start(vargs, fmt);

dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);

va_end(vargs);

return dev;

}

device_create(test_dev_class, NULL, dev_id, NULL, "test");

void device_destroy(struct class *class, dev_t devt)

{

struct device *dev;

dev = class_find_device(class, NULL, &devt, __match_devt);

if (dev) {

put_device(dev);

device_unregister(dev);

}

}

device_destroy(test_dev_class,dev_id);

************************

上面的使用就是一个设备类的创建,

类中的一个设备文件的创建(就是mknod创建的设备文件的名字)

结合:

创建:

class_create()

device_create()

销毁:

device_destroy()

class_destroy()

*********************

成功现象:

root@sugar /root# insmod module_test.ko

39.180515 major = 250, minor = 0.

root@sugar /root# lsmod

Not tainted

module_test 2102 0 - Live 0xbf000000

root@sugar /root# ls /dev/test -l

crw-rw---- 1 root 0 250, 0 Jan 1 12:00 /dev/test

root@sugar /root# ./app

直接安装执行,不需要再手动mknod创建设备文件

insmod后直接查看也可以查看到device_destroy中输入的test文件

******************

查看创建的class

root@sugar /sys# cd class/

root@sugar class# ls

sugar_class

root@sugar class# cd sugar_class/

root@sugar sugar_class# ls

test

root@sugar sugar_class# cd test/

root@sugar test# ls

dev power subsystem uevent

用cat查看,可以查看里面的相关信息

root@sugar test# cat uevent

MAJOR=250

MINOR=0

DEVNAME=test

root@sugar test# cat dev

250:0

函数代码分析:

class_create()

__class_create(owner, name, &__key); //实际调用

cls = kzalloc(sizeof(*cls), GFP_KERNEL);//申请堆内存

__class_register //注册

cp = kzalloc(sizeof(*cp), GFP_KERNEL);

kset_register

kobject_uevent

struct kobject *kobj //输出型参数,作为返回值

返回的就是kobject结构体的相应参数

struct kobject {

const char *name;

struct list_head entry;

struct kobject *parent;

struct kset *kset;

struct kobj_type *ktype;

struct sysfs_dirent *sd;

struct kref kref;

unsigned int state_initialized:1;

unsigned int state_in_sysfs:1;

unsigned int state_add_uevent_sent:1;

unsigned int state_remove_uevent_sent:1;

unsigned int uevent_suppress:1;

};

kfree(cls);

device_create()

device_create_vargs

dev = kzalloc(sizeof(*dev), GFP_KERNEL);

dev->devt = devt;

dev->class = class;

dev->parent = parent;

dev->release = device_create_release;

dev_set_drvdata(dev, drvdata);

kobject_set_name_vargs

device_register

device_initialize

device_add

kobject_add

device_create_file

device_create_sys_dev_entry

devtmpfs_create_node

device_add_class_symlinks

device_add_attrs

bus_add_device

dpm_sysfs_add

device_pm_add //power management

kobject_uevent //输出结构体相关参数

kfree(dev->p);

*************************

以上就是两个类和设备文件的函数分析

***********************

新旧字符设备号的注册:

register_chrdev

register_chrdev_region/alloc_chrdev_region

register_chrdev

__register_chrdev(major, 0, 256, name, fops);//实际调用

__register_chrdev_region(major, baseminor, count, name);

if (major == 0) {

for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {

if (chrdevsi == NULL)

break;

}//这里的处理就是解释到为什么设备号的分配从后往前分配

register_chrdev_region

__register_chrdev_region(MAJOR(n), MINOR(n),

next - n, name);

alloc_chrdev_region

__register_chrdev_region(0, baseminor, count, name);

总结:

实际上,三个都是调用的__register_chrdev_region这个函数,

只不过是里面的传进去的参数的定义不同

新的这个两个主要是设备号定义,为0

旧的是无法定义子设备号

*************************

静态页表的分析:

之前对寄存器的操作有:

静态映射、动态映射两种方法

静态映射:

使用的就是自定义好的寄存器地址相关的虚拟地址

其中,涉及到的一个就是静态映射表,

有了这个表才能实现一段物理地址到虚拟地址的映射关系

1、映射表涉及到的物理地址和虚拟地址的值的相关宏定义

2、映射表的建立函数

/kernel/arch/arm/mach-s5pv210/mach-smdkc110.c

smdkc110_map_io

s5p_init_io(NULL, 0, S5P_VA_CHIPID);

iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc));

create_mapping

上面函数涉及到的就是io表的初始化,创建映射

iotable_init中涉及到的映射有s5p_iodesc

这是一个结构体数组,数组中的每一个元素就是一个映射,

描述了一段物理地址到虚拟地址之间的映射

表中记录的这些映射关系被iotable_init调用,这个函数负责将这个结构体

数组格式的表建立称MMU所能识别的页表映射关系,这样开机后就可以直接

使用对应的虚拟地址访问对应的物理地址

其中,页表显示都是4k、8k、16k...

arch/arm/plat-s5p/cpu.c

映射表对应的结构体数组:

/* Initial IO mappings */

static struct map_desc s5p_iodesc\[\] __initdata = {

{

{

.virtual = (unsigned long)S5P_VA_GPIO,

.pfn = __phys_to_pfn(S5P_PA_GPIO),

.length = SZ_4K,

.type = MT_DEVICE,

},

这块就是我们要操作的gpio对应的映射关系转换模块

还有一个问题,就是开机时调用映射表建立函数

开机时(kernel启动时)smdkc110_map_io怎么被调用的

start_kernel(main.c)

setup_arch(573)

paging_init

devicemaps_init

/*

* Ask the machine support to map in the statically mapped devices.

*/

if (mdesc->map_io)

mdesc->map_io();

************************

动态映射

这里解决一个问题,就是动态映射对多个寄存器需要多次映射的问题

解决:

仿效真实驱动中,用结构体封装的方式来进行单次多个寄存器的地址映射

这样就可以不用多次进行单个寄存器的映射了

//定义一个结构体包含两个寄存器

typedef struct GPJ0REG{

volatile unsigned int gpj0_con;

volatile unsigned int gpj0_dat;

}gpj0_refs;

注意:

定义结构体不能用static,

如果我们在文件作用域内使用`static`关键字修饰一个结构体定义,

这是不允许的,编译器会报错。

因为结构体定义本身并不占用存储空间,它只是一个类型描述

还有,结构体内定义的变量不要与外部变量名字有冲突,否则有错

#define GPJ0_PA_BASE 0xe0200240

gpj0_refs *pGPJ0_REG;

//用两步完成动态映射

if (!request_mem_region(GPJ0_PA_BASE, sizeof(gpj0_refs), "GPJ0REG"))

return -EINVAL;

pGPJ0_REG=ioremap(GPJ0_PA_BASE, sizeof(gpj0_refs));

//销毁

iounmap(pGPJ0_REG);

release_mem_region(GPJ0_PA_BASE, sizeof(gpj0_refs));

**********************************

最后,是一个writel、readl函数

用来操作寄存器(当完成映射后)

writel()

#define writel(b,addr) __raw_writel(__cpu_to_le32(b),addr)

__raw_writel

#define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v))

这个就是实际工作的函数

实质就是我们之前设置的一个定义即操作的寄存器地址+要进行的操作

#define rGPJ0CON *((volatile unsigned int *)S5PV210_GPJ0CON)

#define rGPJ0DAT *((volatile unsigned int *)S5PV210_GPJ0DAT)

#define GPJ0CON_PA 0xe0200240

static void __iomem *baseaddr; // 寄存器的虚拟地址的基地址

通过ioremap 得到虚拟地址

writel(0x11111111, baseaddr+0);

writel(((0<<3) | (0<<4) | (0<<5)), baseaddr+4);

iowrite32()

#define iowrite32(v, addr) writel((v), (addr))

writel

实际就是上面的函数

************************

至此,可以完成一个led的驱动

其中,包括使用

1、新旧设备号注册函数、驱动的alloc申请(alloc_chrdev_region)

2、对test_cdev的一个操作,一个是用全局变量做全部的初始化,

一个使用一个全局指针,后面对齐实例化操作(替换一个cdev_init()函数)

//static struct cdev test_cdev;

static struct cdev *test_cdev;

3、类的创建

目的就是不用手动mknod取创建设备文件

class_create

device_create

4、对动态映射的申请优化,使用一个指针接收虚拟地址映射,

可以用结构体定义其中的寄存器,亦可以自己在地址中加上相应的偏移量

request_mem_region

ioremap

5、对寄存器的操作

iowrite32

writel

相关推荐
Dillon Dong12 分钟前
【风电控制】TI TMS320F28379D 双CPU架构解析与任务分布设计
嵌入式硬件·算法·变流器·风电控制
为思念酝酿的痛6 小时前
POSIX信号量
linux·运维·服务器·后端
人还是要有梦想的7 小时前
linux下用搜狗输入法,中英文切换
linux·运维·服务器
bush47 小时前
嵌入式linux学习记录二
linux·运维·学习
9分钟带帽7 小时前
linux_通过NFS挂载远程服务器的硬盘
linux·服务器
三易串口屏8 小时前
实验20 自动灭火场景实验
嵌入式硬件·串口屏·三易串口屏·uart 通信
蒸蛋一级爱好者8 小时前
TFTP协议
单片机·嵌入式硬件
优信电子9 小时前
STM32/C51驱动 DHTC11 温湿度传感器
stm32·单片机·嵌入式硬件·c51·温湿度传感器·dhtc11·环境测量
周周记笔记9 小时前
【元器件专题】三极管-如果B极给一个方波信号,那么V0输出也可以设计为一个方波信号
单片机·嵌入式硬件
潜创微科技9 小时前
IT68353:DP 1.4 + HDMI 2.0 + USB-C 三合一转 HDMI 2.0 单芯片KVM切换方案
嵌入式硬件·音视频