涉及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);
传进去一个物理地址 和大小