驱动开发前传及led驱动(s5pv210)

涉及app、驱动两个的调用,包括读写操作、led驱动操作,

涉及动态映射、静态映射

makefile:

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

#KERN_VER = $(shell uname -r)

#KERN_DIR = /lib/modules/$(KERN_VER)/build

ubuntu的内核源码树

在ubuntu中编译安装模块就要索引到这个目录位置

root@ubuntu:/lib/modules# uname -r

4.15.0-142-generic

root@ubuntu:/lib/modules/4.15.0-142-generic/build# ls

arch Documentation include Kconfig mm scripts ubuntu

block drivers init kernel Module.symvers security usr

certs firmware ipc lib net sound virt

crypto fs Kbuild Makefile samples tools

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

KERN_DIR = /usr/local/drivers/kernel

ubuntu中要操作的内核源码目录位置

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

make -C $(KERN_DIR) M=`pwd` modules

这个就是索引到$(KERN_DIR)这个目录位置 然后进行modules ,将输出的文件放到

M=`pwd`,就是当前编译的位置

清除

.PHONY : clean

clean:

make -C $(KERN_DIR) M=`pwd` modules clean

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

在ubuntu中装载时的模块信息:

insmod :

装载模块

lsmod:

查看装载的模块信息

root@ubuntu:/mnt/hgfs/Embedded/drivers/2chardev/1moduletest# insmod module_test.ko

root@ubuntu:/mnt/hgfs/Embedded/drivers/2chardev/1moduletest# lsmod

Module Size Used by

module_test 16384 0

rmmod:

卸载模块

root@ubuntu:/mnt/hgfs/Embedded/drivers/2chardev/1moduletest# rmmod module_test.ko

root@ubuntu:/mnt/hgfs/Embedded/drivers/2chardev/1moduletest# lsmod

Module Size Used by

rfcomm 77824 2

modinfo:

查看模块信息

文件名路径filename

作者author

许可证(有双许可)license

文件名称name

用于获取Linux内核模块源代码版本号srcversion

魔数vermagic(这里在ubuntu装载,就是ubuntu的版本号信息)

root@ubuntu:/mnt/hgfs/Embedded/drivers/2chardev/1moduletest# modinfo module_test.ko

filename: /mnt/hgfs/Embedded/drivers/2chardev/1moduletest/module_test.ko

author: sugar

license: GPL

srcversion: A85C0A45B7BF7A40A03F847

depends:

retpoline: Y

name: module_test

vermagic: 4.15.0-142-generic SMP mod_unload

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

在要操作的内核时的模块信息

root@ubuntu:/mnt/hgfs/Embedded/drivers/2chardev/1moduletest# modinfo module_test.ko

filename: /mnt/hgfs/Embedded/drivers/2chardev/1moduletest/module_test.ko

author: sugar

license: GPL

depends:

vermagic: 2.6.35.7 preempt mod_unload ARMv7

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

在 开发板中操作:

lsmod insmod rmmod 都时一样的

只有 modinfo出错:

@sugar modules# modinfo module_test.ko

modinfo: can't open '/lib/modules/2.6.35.7/modules.dep': No such file or directory

@sugar /lib# mkdir modules

@sugar modules# mkdir 2.6.35.7

@sugar modules# depmod

@sugar modules# modinfo module_test.ko

filename: module_test.ko

就是缺失了文件,补充上文件后,使用depmod指令就可以自动生成三个文件

@sugar modules# cd 2.6.35.7/

@sugar 2.6.35.7# ls

modules.alias modules.dep modules.symbols

这样之后就可以使用modinfo查看模块信息了

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

这里开始对文件填充,完成驱动的设计

********

首先明白我们要完成的是字符设备的驱动,那么就要完成对设备的注册,

不然内核无法识别我们完成驱动,那么就无法完成调用

*******

1、/linux/Fs.h

static inline int register_chrdev(unsigned int major, const char *name,

const struct file_operations *fops)

{

return __register_chrdev(major, 0, 256, name, fops);

}

字符设备驱动注册的原函数

major:设备号 (先查看哪些设备号没有被使用,不能申请一个已经使用的设备号,要么就填0,让内核自动帮分配设备号,这样就可以万无一失)

name:名字

fops:绑定的文件操作,就是file_operation

这个函数应该和

static int __init chardev_init(void)

模块装载函数放在一起,就是初始化时就完成注册

注销设备驱动

static inline void unregister_chrdev(unsigned int major, const char *name)

{

__unregister_chrdev(major, 0, 256, name);

}

只需要设备号major和名字name

注册和注销一定是要存在的,都是成对

就像module_init

module_exit

2、/linux/Fs.h

struct file_operations{}

这个就是内核完成对相应函数操作的一个结构体

为什么需要这个,原因就是这个是驱动相关的,这个有相应的操作,

比如read、write等,但是,我们的操作在app应用层的read、write,

这很明显不是同一个,所以需要这一个结构体,完成应用层和驱动的一个联系

在app中调用,然后通过这个结构体,用函数指针绑定一个驱动中实例化的同样的

函数,完成操作

例子:

file_operations

/* File operations struct for character device */

static const struct file_operations twa_fops = {

.owner = THIS_MODULE,

.unlocked_ioctl = twa_chrdev_ioctl,

.open = twa_chrdev_open,

.release = NULL

};

文件名字改为自己要操作的函数名字

例如:

/* File operations struct for character device */

static const struct file_operations test_fops = {

.owner = THIS_MODULE,

.open = test_chrdev_open,

.release = test_chrdev_close

};

然后根据绑定函数完成相应函数功能

所以上面的字符设备注册函数:

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

返回值,0是成功注册,负数时失败

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

开发板操作

0、如果用自定义设备号,查看开发板已有设备号

1、写好驱动函数和app函数

编译到开发板执行

2、insmod .ko

完成注册

3、查看注册的设备号

4、用mknod 创建设备名字

mknod /dev/name c 250 0

name是自己创建的名字

250是主设备号,0是次设备号

root@sugar /root# insmod module_test.ko

775.991976 register_chrdev success...

root@sugar /root# cat /proc/devices

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

root@sugar /root# ./app

812.862579 test_chrdev_open success

812.864797 test_chrdev_close success

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

这个是完成一个读写操作,

首先是read、write函数

static ssize_t test_read(struct file *file, __user char *buffer, size_t count,

loff_t *ppos)

static ssize_t test_write(struct file *file, const __user char *buffer,

size_t count, loff_t *ppos)

然后是

关系到app 内核的两个内存的两个读写问题

联系到两个函数

copy_from_user

copy_to_user

函数原型:

static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)

{

if (access_ok(VERIFY_READ, from, n))

n = __copy_from_user(to, from, n);

else /* security hole - plug it */

memset(to, 0, n);

return n;

}

static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)

{

if (access_ok(VERIFY_WRITE, to, n))

n = __copy_to_user(to, from, n);

return n;

}

copy_from_user

注意函数内参数意义:

这个是从user中复制数据到内核中,

前一个参数to,为kbuf,为目标buf,后一个from就是源数据,从user中读取过来

copy_to_user

这个则对应read操作,从内核中读取到user

开发板中的操作:

root@sugar /root# insmod module_test.ko

42.113445 register_chrdev success...

root@sugar /root# cat /proc/devices

250 sugar

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

root@sugar /root# ./app

83.790004 test_chrdev_open success

83.792262 test_write ok

83.794697 copy_from_user success ...

83.798570 test_read ok

83.800958 copy_to_user success ...

83.804642 test_chrdev_close success

open successthe read buf is hello_test

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

上面就是一个驱动前的一个框架,未涉及到相关硬件驱动

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

写相关硬件驱动前,首先明白一个静态映射

用到操作系统,里面的内存必定是用到虚拟地址

注意:

不同版本内核中的静态映射表表位置不同

不同soc的静态映射位置、文件名不同

所谓的映射表就是头文件中的宏定义

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

所以,了解静态映射表

1、虚拟地址基地址

arch/arm/plat-samsung/include/plat/map-base.h

#define S3C_ADDR_BASE (0xFD000000)

Samsung移植时确定的静态映射表的基地址,

表中所有的基地址都是以这个地址+偏移量得到的

2、主映射表

arch/arm/plat-s5p/include/plat/map-s5p.h

#define S5P_VA_GPIO S3C_ADDR(0x00500000)

#define S5P_VA_UART0 (S3C_VA_UART + 0x0)

#define S5P_VA_UART1 (S3C_VA_UART + 0x400)

.

.

.

cpu在安排寄存器地址时不是随意分配的,而是按模块区分的

例如gpio、uart....

每一个模块内的地址都是连续的

所以内核在定义寄存器地址时都是先找到基地址,

然后用基地址+偏移量来寻找具体的一个寄存器

map-s5p.h中定义的就是要用到的几个模块的寄存器基地址。

map-s5p.h中定义的是模块的寄存器基地址的虚拟地址。

3、现在要操作的是led,用到gpio

gpio相关的主映射表位于

arch/arm/mach-s5pv210/include/mach/regs-gpio.h

用到GPJ0

表中是GPIO的各个端口的基地址的定义

#define S5PV210_GPJ0_BASE (S5P_VA_GPIO + 0x240)

4、GPJ0的具体寄存器定义:

arch/arm/mach-s5pv210/include/mach/gpio-bank.h

#define S5PV210_GPJ0CON (S5PV210_GPJ0_BASE + 0x00)

#define S5PV210_GPJ0DAT (S5PV210_GPJ0_BASE + 0x04)

#define S5PV210_GPJ0PUD (S5PV210_GPJ0_BASE + 0x08)

#define S5PV210_GPJ0DRV (S5PV210_GPJ0_BASE + 0x0c)

#define S5PV210_GPJ0CONPDN (S5PV210_GPJ0_BASE + 0x10)

#define S5PV210_GPJ0PUDPDN (S5PV210_GPJ0_BASE + 0x14)

总结:

实质就是一个虚拟地址总基地址+各个模块的基地址+模块内端口的基地址+模块内端口的具体寄存器地址

S5PV210_GPJ0CON = S3C_ADDR_BASE+0x00500000+ 0x240 + 0x00

= 0xFD000000 + 0x00500000 + 0x240 + 0x00

0xFD500240

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

动态:

建立动态映射(静态动态两个映射可以共存的)

建立的过程

首先是申请需要映射的内存资源

然后才是到 真正用来映射的物理地址到虚拟地址的转换

(这里因为要操作led,用到gpj0)

#define GPJ0CON_PA 0xe0200240

#define GPJ0DAT_PA 0xe0200244

unsigned int *pGPJ0CON;

unsigned int *pGPJ0DAT;

1、request_mem_region

向内核申请需要映射的资源

if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))

return -EBUSY;

if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT"))

return -EBUSY;

2、ioremap

真正用来实现映射,传进去物理地址,映射返回一个虚拟地址

//传入物理地址转化为虚拟地址

pGPJ0CON = ioremap(GPJ0CON_PA, 4);

pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);

ioremap 的返回值是一个指针(unsigned int *)

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

用上面这两个就可以完成一个物理地址到虚拟地址的一个申请并映射过程

直接对两个虚拟地址进行操作即可

*pGPJ0CON = 0x11111111;

*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));

*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));

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

申请、映射

结束的时候也一定要进行

解除映射、释放申请

iounmap

iounmap(pGPJ0CON);

iounmap(pGPJ0DAT);

传进去一个映射返回的虚拟地址

release_mem_region

release_mem_region(GPJ0CON_PA, 4);

release_mem_region(GPJ0DAT_PA, 4);

传进去一个物理地址 和大小

相关推荐
编程圈子11 小时前
电机驱动开发学习2. 直流无刷电机工作原理
驱动开发·学习
智者知已应修善业16 小时前
【用74LS151的实现(16序列信号发生器)】2024-6-1
驱动开发·经验分享·笔记·硬件架构·硬件工程
道一云黑板报16 小时前
告别提示词工程:为什么“循环工程”才是 AI 编程的未来?
人工智能·驱动开发·软件工程·ai编程
天南散修1 天前
MT7916 BA流程
网络·驱动开发·wifi·802.11
Soari1 天前
EtherCAT Master Stack —— 面向工业实时控制的开源 EtherCAT 主站协议栈
驱动开发
天南散修2 天前
MT7916驱动中802.11转换为802.3
linux·网络·驱动开发·wifi·802.11
A.说学逗唱的Coke2 天前
【AI·Coding】TDD × SDD × AI Coding:从“测试驱动“到“规范驱动“的智能协作实践
人工智能·驱动开发·tdd
l'm coming2 天前
[linux]内核启动加载驱动文件的流程
linux·arm开发·驱动开发·嵌入式
2601_949695594 天前
昨天刚解决:说说我是怎么修好Realtek高清晰音频管理器打不开的
驱动开发·计算机外设·电脑
尔染君子4 天前
嵌入式Linux驱动开发(按键驱动)
linux·驱动开发