linux驱动学习–串口相关—知识汇总
一.驱动的框架
//串口驱动模块的创建函数---------------------------------------------
static int __init uart_init_module(void)
{
//1.字符设备的设备号部分
register_chrdev_region();
或者
alloc_chrdev_region();
//2.注册平台设备和平台驱动
platform_device_register_simple();//平台设备
platform_driver_register();//平台驱动
//3.a初始化cdev的函数,b把cdev加入内核
//4.驱动类和驱动设备类的创建
class_create();
class_device_create();
}
//驱动模块的删除
static void __exit uart_exit_module(void)
{
//注销平台驱动
platform_driver_unregister(&uart_plat_driver);
//注销平台设备
platform_device_unregister(uart_platform_device);
//注销设备类
class_device_destroy(mydev_class, devno);
//注销驱动类
class_destroy(mydev_class);
//删除内核注册的cdev
cdev_del(&my_uart_devices.cdev);
//注销平台设备编号
unregister_chrdev_region(devno,1);
}
二.相关知识
1.物理地址到虚拟地址的映射函数
void *ioremap(unsigned long phys_addr, unsigned long size);
//访问物理内存phys_addr和这段内存区的大小,返回物理地址对应的虚拟地址
void ioumap(void *addr);//这里的addr是虚拟地址
为了便于不同mcu移植,对虚拟地址中的数据操作,可以用下面的函数
unsigned int ioread32(void *addr)//从内存读取数据,返回指定地址的值
void iowrite32(unsigned int value,void *addr)//往指定内存写入数据
__iomem是linux2.6.9内核中加入的特性。是用来个表示指针是指向一个I/O的内存空间。
__force表示所定义的变量类型是可以做强制类型转换
attribute____关键字主要是用来,在函数或数据声明中设置其属性
#define S3C2410_ADDR(x) ((void __iomem __force *)0xF0000000 + (x))
//指向I/O内存的(__iomem)
//强制类型转换(__force)
# define __force __attribute__((force))
//---------------------------------------------------------------
//在map.h中定义了虚拟地址和物理地址
#define S3C2410_ADDR(x) (0xF0000000 + (x))//虚拟址
//对应关系memory controller registers
#define S3C24XX_VA_MEMCTRL S3C2410_ADDR(0x00100000)
#define S3C2400_PA_MEMCTRL (0x14000000)
#define S3C2410_PA_MEMCTRL (0x48000000)
#define S3C24XX_SZ_MEMCTRL SZ_1M
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
__builtin_expect((x),1) 表示 x 的值为真的可能性更大;
__builtin_expect((x),0) 表示 x 的值为假的可能性更大。
__builtin_expect()期望 exp 表达式的值等于常量 c ,从而 GCC 为你优化程序,将符合这个条件的分支放在合适的地方,先执行其它。
2.加锁
spin_lock_irqsave(&port->lock, flags);
//代码不被多个cpu进程访问
//代码不被中断打断(加锁)
spin_unlock_irqrestore(&port->lock, flags);
3.圆(环形)缓存区
□□□□□□□□□
↑________|
好比两个人围着一张圆形的桌子在追逐,跑的人被网络IO线程所控制,当写入数据时,这个人就往前跑;追的人就是逻辑线程,会一直往前追直到追上跑的人。如果追上了怎么办?那就是没有数据可读了,先等会儿呗
head | 头指针 | 项目插入点—write |
---|---|---|
tail | 尾指针 | 寻找的下一个点—read |
size | 常量值 | 大小为2n大小为2n |
//返回buffer中没有读的count
#define CIRC_CNT(head,tail,size) (((head) - (tail)) & ((size)-1))
//返回还有几个空间可以写
#define CIRC_SPACE(head,tail,size) CIRC_CNT((tail),((head)+1),(size))
//假定size =8,head=6,tail=4.
//tail-(head+1) = -3 ,计算机中是用补码来表示的。-3的补码是1111 1101 & 0000 0111 = 0000 0101 =5表示还有5个空间可以写.0/1/2/3/7刚好等于5个空间.(-1的补码是1111 1111)
#define CIRC_CNT_TO_END(head,tail,size)//这将返回可以从缓冲区中提取的连续项的数量,而不必返回到缓冲区的开始位置。
#define CIRC_SPACE_TO_END(head,tail,size)//这将返回缓冲区中剩余的连续空间量,可以立即插入项目,而无需返回到缓冲区的开始位置
当尾指针等于头指针时,缓冲区是空的; 并且当头指针比尾指针少一个时,缓冲区已满。 添加项目时头部索引增加,删除项目时尾部索引增加。 尾部索引不应该跳转头部索引,并且两个索引在到达缓冲区末尾时都应该包装为0,从而允许无限量的数据流过缓冲区。
宏 | head=6 tail=4 | head=4 tail=6 |
---|---|---|
CIRC_CNT | 2 | 6 |
CIRC_SPACE | 5 | 1 |
CIRC_SPACE_TO_END | 2 | 2 |
CIRC_CNT_TO_END | 2 | 1 |
图例size=23图例size=23 | [–][–][–][–][a↓][ b ][ ↓ ][ –] | [ c ][ d ][ e ][ f ][ ↓ ][ –][a↓][ b ] |
图例序号 | _0 _1 _2 _3 _4 _5 _6 _7 | _ 0 _ 1 _ 2 _ 3 _ 4 _ 5 _ 6 _7 |
缓冲区作用,如串口通讯-9600波特率,每3ms要传送位,传送字节要24ms传完,搬送字节进FSUB时间不能大于3ms,处理数据放入缓区
4.Makefile相关
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
“2>” 代表重定向操作错误提示信息。只有这两个字符并不能删除错误输出。
文件 | 文件描述符 |
---|---|
输入文件—标准输入 | 0(缺省是键盘,为0时是文件或其他命令的输出) |
输出文件—标准输出 | 1(缺省是屏幕,为1时是文件) |
错误输出文件—标准错误 | 2(缺省是屏幕,为2时是文件) |
/dev/null看作"黑洞".写入内容都永远丢失
/dev/zero伪文件, 实际产生连续null流(二进制零)
5.cdev初始化
一个 cdev 一般它有两种定义初始化方式:静态的和动态的。
静态内存定义初始化:
struct cdev cdev;
cdev_init(&cdev, &fops);
cdev.owner=THIS_MODULE;
动态内存定义初始化:
struct cdev *cdev
cdev = cdev_alloc();
cdev->ops = &fops;
cdev->owner=THIS_MODULE;