分类: 嵌入式
2014-06-04 14:58:05
原文地址:Linux模块编程的另类玩法——硬件协议探究 作者:tq08g2z
通常情况下,内核程序主要是为应用程序提供机制的。对于驱动程序来说,就是要尽可能充分的,将硬件的各种特性都展现出来。同时还要保证稳定、可靠,也就是说要细致的实现各种锁定,并发控制,访问权限控制等等,否则的话,对于整个计算机系统来说,操作系统层的存在与否大概也就是完全失去意义了。但是,如果很粗糙的使用操作系统提供的各种设施,则也是很有得可玩的。
编写驱动,就是要展现硬件的各种特性啊。可是,刚开始的时候,对硬件的协议都还不了解呢,要如何来研究复杂的硬件协议呢,实验究竟要如何操作呢,到底要怎么样进行下去呢,这实在是一个很重要额问题?其实,如果很粗糙的使用内核提供的各种设施,同时添加各种各样的策略性代码,也就基本上屏蔽了操作系统设计与实现的各种复杂性了,但是又使我们可以很方便的直接操作硬件。
操作硬件,在模块代码中还是需要使用各种各样的资源的。尽管我们要很粗糙的使用内核设施,但是各种资源的申请和释放还是要注意的。模块编程所需要的最基本的另个函数,一个是模块加载时要执行的模块初始化函数,另外一个是模块退出时要执行的函数,如果硬件协议简单的话,甚至可以仅仅实现这两个函数,然后在这两个函数来添加各种各样的策略。
I/O内存区域
操作硬件需要使用硬件寄存器,获得对硬件寄存器资源的独占访问权限,还是一个好习惯。对于I/O内存来说,可以使用在文件
struct
resource * request_mem_region(resource_size_t start,
resource_size_t n,const char
*name);
这个函数将在成功完成时返回非NULL,否则返回NULL。参数的语义都相当明确啊,第一个是I/O内存的起始地址,第二个是I/O内存区域的长度。而第三个则是用来标识占有这块I/O内存区域的所有者的字符串,在/proc/iomem文件中可以看到这个字符串和这块I/O内存区域相对应。
相应的可以使用下面的函数来释放I/O内存:
void
release_mem_region(resource_size_t start, resource_size_t n);
在获得了对于I/O内存区域的独占访问权限,还要将I/O内存区域映射到内核的虚拟地址空间,相应的将I/O内存区域映射到虚拟地址空间和解除这种映射的函数为:
#include
void
__iomem *ioremap(unsigned long phys_addr, size_t size);
void
__iomem *ioremap_nocache(unsigned long phys_addr, size_t size);
void
iounmap(volatile void __iomem *io_addr);
对于I/O内存区域的访问,内核也是提供了一组专门的函数的(在
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
其实现类似于这样:
#define
ioread8(p) ({ unsigned int __v =
__raw_readb(p); __v; })
addr应该是从ioremap获得的地址(可能包含一个整数的偏移量);返回值则是从给定I/O内存中读取到的值。
写入I/O内存:
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
上述函数的重复版本:
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
上述函数从给定的buf向给定的addr读取或写入count个值。count以被写入的数的大小为单位。——均在给定的addr处执行所有的I/O操作。
如果要在一块I/O内存上执行操作,则可以使用:
void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
void *和char *似乎是等价的(在ARM平台上),步长为1。
一组老的I/O内存函数(宏),不执行类型检查,因此其安全性较差:
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
中断
除了I/O内存区域之外,中断线通常也是不得不使用的重要资源。内核维护了一个中断信号线的注册表,该注册表类似于I/O端口的注册表。模块在使用中断前要先请求一个中断通道(或者中断请求IRQ),然后在使用后释放该通道。
下列在头文件 中声明的函数实现了该接口:
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags, const char *name, void *dev_id);
void free_irq(unsigned int irq, void *dev_id);
从内核的2.6.19 版开始,中断服务例程接口,也就是irq_handler_t类型,有了一些变化。过去,中断处理接口的第三个参数是一个指向CPU寄存器的指针(struct pt_regs *),但是现在irq_handler_t类型的定义为:
typedef irqreturn_t (*irq_handler_t)(int, void *);
已经移除了第三个参数。而且IRQF_xxx族中断标志取代了SA_xxx族。
request_irq返回0表示申请成功,为负值时表示错误码。
irq,请求的中断号
flags是一个与中断管理相关的位掩码。
IRQF_DISABLED - keep irqs disabled when calling the action handler
IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
IRQF_SHARED – 允许在多个设备间共享中断号。
IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
IRQF_TIMER – 标记这个中断为时钟中断。
IRQF_PERCPU – 中断为per cpu
IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
registered first in an shared interrupt is considered for performance reasons)
IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished. Used by threaded interrupts which need to keep the irq line disabled until the threaded handler has been run.
name,传递给request_irq的字符串,用来在/proc/interrupts中显示中断的拥有者。
dev_id,这个指针用于共享的中断信号线。它是唯一的标示符,在中断信号线空闲时可以使用它,驱动程序也可以使用它指向驱动程序自己的私有数据区(用来识别哪个设备产生中断)。
关于中断函数的返回值,在linux/irqreturn.h文件中定义,可选的值为如下几个:
* @IRQ_NONE interrupt was not from this device
* @IRQ_HANDLED interrupt
was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
OK,如此一来,则在适当的地方,加入一些printk语句,配合着使用一些简单的如内存分配之类的内核设施,则可以很方便的进行硬件协议的研究了。