PCI总线驱动
1.pci驱动注册
pci_register_driver(struct pci_driver *drv)
static struct pci_driver hamachi_driver = {
.name = DRV_NAME,
.id_table = hamachi_pci_tbl,
.probe = hamachi_init_one,
.remove = __devexit_p(hamachi_remove_one),
};
2.私有数据
pci_set_drvdata 设置驱动私有数据
pci_get_drvdata 获取驱动私有数据
3.PCI配置空间
pci_read_config_byte/word/dword(struct pci_dev *pdev, int offset, int *value)
pci_write_config_byte/word/dword(struct pci_dev *pdev, int offset, int *value)
4.PCI的I/O和内存空间
pci_resource_start(struct pci_dev *dev, int bar) Bar值的范围为0-5 ;从配置区相应寄存器得到I/O区域的基址:
pci_resource_length(struct pci_dev *dev, int bar) Bar值的范围为0-5;从配置区相应寄存器得到I/O区域的内存区域长度
pci_resource_flags(struct pci_dev *dev, int bar) Bar值的范围为0-5;从配置区相应寄存器得到I/O区域的内存的相关标志
request_mem_region(io_base, length, name) 申请I/O端口
release_mem_region(io_base, length, name) 释放I/O端口
inb() inw() inl() outb() outw() outl() 读写I/O端口
pci_enable_device 启用设备的I/O
和内存资源,分配不足的资源,如果需要,还要唤醒一个处于暂停状态的设备。需要注意的是,这个操作可能会失败。
pci_set_master 设定设备工作在总线主设备模式
ioremap_nocache 让CPU 可以访问设备的内存
pci_dma_supported
pci_set_dma_mask()和dma_set_mask()辅助函数用于检查总线是否可以接收给定大小的总线地址(mask),如果可以,则通知总线层给定的外围设备将使用该大小的总线地址。
pci_alloc_consistent 返回一致性dma映射缓冲区的虚拟地址
upci_free_consistent 释放一致性dma缓冲区映射
pci_save_state 配置空间(包括PCI、PCI-X、PCI-E)存到pci_dev里头
原子操作
Atomic_read(v) 返回原子变量的值
atomic_set(v,i) 设置原子变量的值
Atomic_add(int i, atomic_t *v) 原子的递增计数的值
static inline int atomic_add_return(int i, atomic_t *v) 只不过将变量v的最新值返回
Atomic_sub(int i, atomic_t *v) 原子的递减计数的值
static inline int atomic_sub_return(int i, atomic_t *v) 只不过将变量v的最新值返回
atomic_cmpxchg(atomic_t *ptr, int old, int new) 比较old和原子变量ptr中的值,如果相等,那么就把new值赋给原子变量。 返回旧的原子变量ptr中的值
atomic_clear_mask 原子的清除掩码
atomic_inc(v) 原子变量的值加一
atomic_inc_return(v) 同上,只不过将变量v的最新值返回
atomic_dec(v) 原子变量的值减去一
atomic_dec_return(v) 同上,只不过将变量v的最新值返回
atomic_sub_and_test(i, v) 给一个原子变量v减去i,并判断变量v的最新值是否等于0
atomic_add_negative(i,v) 给一个原子变量v增加i,并判断变量v的最新值是否是负数
static inline int atomic_add_unless(atomic_t *v, int a, int u) 只要原子变量v不等于u,那么就执行原子变量v加a的操作。 如果v不等于u,返回非0值,否则返回0值
char设备注册
1.int register_chrdev_region(dev_t from, unsigned count, const char *name)
为一个字符驱动获取一个或多个设备编号来使用。用于分配指定的设备编号范围。如果申请的设备编号范围跨越了主设备号,它会把分配范围内的编号按主设备号分割成较小的子范围,并在每个子范围上调用 __register_chrdev_region() 。如果其中有一次分配失败的话,那会把之前成功分配的都全部退回。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
用于动态申请设备编号范围,这个函数好像并没有检查范围过大的情况,不过动态分配总是找个空的散列桶,所以问题也不大。通过指针参数返回实际获得的起始设备编号。
void unregister_chrdev_region(dev_t from, unsigned count)
void cdev_init(struct cdev *cdev, const struct file_operations *fops) cdev 静态内存定义初始化
struct cdev *cdev_alloc(void) cdev动态内存定义初始化
int cdev_add(struct cdev *p, dev_t dev, unsigned count)初始化 cdev 后,把它添加到系统中去
void cdev_del(struct cdev *p)释放 cdev 占用的内存
2.misc注册cdev
static struct file_operations led_fops;
static struct miscdevice up4412_led_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "abc",
.fops = &led_fops,
};
注册(返回0成功):
ret = misc_register(&up4412_led_dev);
注销:
misc_deregister(&up4412_led_dev);
申请内存
get_zeroed_page(unsigned int flags);
返回一个指向新页的指针并且用零填充了该页.
__get_free_page(unsigned int flags);
类似于 get_zeroed_page, 但是没有清零该页.
__get_free_pages(unsigned int flags, unsigned int order);
分配并返回一个指向一个内存区第一个字节的指针, 内存区可能是几个(物理上连续)页长但是没有清零.
kmalloc()分配连续的物理地址,用于小内存分配
void *vmalloc(unsigned long size)在虚拟内存空间分配一块连续的内存区(虚拟空间连续但是物理空间不一定连续)
void vfree(void *addr)
__pa( address )转换虚拟地址到物理地址
__va(addrress ) 将物理地址转换为虚拟地址
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
void free_page(unsigned long addr)
void free_pages(unsigned long addr, unsigned long order)
内核链表
struct list_head my_head;
(1)初始化
INIT_LIST_HEAD(&my_head);
每个list_head在加入链表之前,都要先初始化一下
(2)添加
list_add(new, &my_head);
list_add_tail(new, &my_head);
向链表中加入新成员
(3)删除
list_del(new);
(4)通过list_head的指针找到外部数据结构体的指针
利用container_of宏
struct b {
long test;
char ch;
struct list_head list;
...
};
void my_test(struct list_head *entry)
{
//通过entry找到外部的结构体b
struct b *tmp = container_of(entry, struct b, list);
printk("%d:%c\n", tmp->test, tmp->ch);
...
}
(5)链表的遍历
struct list_head *pos;
struct b *tmp;
list_for_each(pos, &my_head) {
tmp = container_of(pos, struct b, list);
...
}
list_for_each_entry(...);
struct b *tmp;
list_for_each_entry(tmp, &my_head, list) {
printk("%d\n", tmp->test);
...
}
如果遍历链表的目的是释放链表,推荐使用:
list_for_each_entry_safe(...);
struct b *tmp1, *tmp2;
list_for_each_entry_safe(tmp1, tmp2, &my_head, list) {
list_del(&tmp1->list);
kfree(tmp1);
...;
}
访问寄存器
把物理地址映射成虚拟地址
static void __iomem *vir_base;
vir_base = ioremap(GPIO_BASE, GPIO_SIZE);
if (!vir_base) {
printk("Cannot ioremap\n");
return -EIO;
}
访问寄存器,一般采用基地址加偏移的模式
/* 8位寄存器 */
char value;
value = readb(vir_base + offset);
writeb(value, (vir_base+offset));
/* 16位寄存器 */
short value;
value = readw(vir_base+offset);
writew(value, (vir_base+offset));
/* 32位寄存器 */
int value;
value = readl(vir_base+offset);
writel(value, (vir_base+offset));
/* 64位寄存器 */
u64 value;
value = readq(vir_base+offset);
writeq(value, (vir_base+offset));
//如果不再访问寄存器,应该取消映射
iounmap(vir_base);
gpio库的使用
在<mach/gpio.h>中获得GPIO的编号赋给gpio_num
向gpio库申请使用gpio
ret = gpio_request(gpio_num, "myio")
对io进行配置
s3c_gpio_cfgpin(gpio_num, S3C_GPIO_OUTPUT)
还可以配置为S3C_GPIO_INPUT和S3C_GPIO_SFN(x)
上述宏定义在<plat/gpio-cfg.h>
设置gpio输出0或1
gpio_set_value(gpio_num, 0|1)
获得gpio输出的值(0或1)
int ret = gpio_get_value(gpio_num)
释放gpio
gpio_free(gpio_num)
蜂鸣器驱动
在用pwm库来控制GPIO之前,首先要将GPIO配置为PWM输出:
int gpio_num = EXYNOS4_GPD0(0)
gpio_request(...)
s3c_gpio_cfgpin(gpio_num, S3C_GPIO_SFN(2))
struct pwm_device *dev;
申请pwm通道(根据4412手册,id从0到3)
dev = pwm_request(pwd_id, "xxx")
释放pwm通道
pwm_free(dev)
配置pwm的占空比和频率,时间以ns为单位
pwm_config(dev, 一个周期中高电平的ns数,整个周期的ns数)
内核中不要用浮点数,假如占空比为47%,则计算高电平的ns数可以
1周期的ns数/100*47
使能pwm
pwm_enable(dev)
关闭pwm
pwm_disable(dev)
内核自旋锁
(1)临界区中只能进入一个人
(2)等待的人忙等(只有SMP才会真正忙等)
(3)持有锁的人不能睡眠
使用之前先初始化
spinlock_t mylock;
spin_lock_init(&mylock);
普通加解锁
spin_lock(&mylock);
临界区
spin_unlock(&mylock);
如果临界区可能被中断处理函数打断,并影响到要保护的变量,则应该在加锁的同时关闭中断
unsigned long flags;
spin_lock_irqsave(&mylock, flags);加锁同时关中断,将CPSR的当前值存储到flags中
临界区
spin_unlock_irqrestore(&mylock, flags);解锁时打开中断,并将flags的值恢复到CPSR中
mutex互斥锁
mutex的特性:(等待锁的时间常常为ms级)
(1)临界区中一个人
(2)睡眠等
(3)持有锁时可以睡眠(必须确保能被唤醒)
用之前要先初始化
struct mutex mylock;
mutex_init(&mylock);
加锁和解锁
mutex_lock(&mylock);
ret = mutex_lock_interruptible(&mylock);
if (ret)
return -ERESTARTSYS;
临界区
mutex_unlock(&mylock);
semaphore(信号量)
在现在的内核中,基本上已经不用semaphore来保护临界区。如果内核中某些资源限定了访问人数(比如只允许3个人同时访问),这时候可以用semaphore进行保护。
按照资源的限定初始化信号量
struct semaphore mysem;
sema_init(&mysem, 3);
down(&mysem);
ret = down_interruptible(&mysem);
...//访问受限资源的代码
up(&mysem);
内核队列
struct workqueue_struct *create_workqueue(const char *name)用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程
struct workqueue_struct *create_singlethread_workqueue(const char *name)创建workqueue,只创建一个内核线程
void destroy_workqueue(struct workqueue_struct *wq)释放workqueue队列
int schedule_work(struct work_struct *work)调度执行一个具体的任务,执行的任务将会被挂入Linux系统提供的workqueue
int schedule_delayed_work(struct delayed_work *work, unsigned long delay)延迟一定时间去执行一个具体的任务,功能与schedule_work类似,多了一个延迟时间
int queue_work(struct workqueue_struct *wq, struct work_struct *work)调度执行一个指定workqueue中的任务
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay)延迟调度执行一个指定workqueue中的任务,功能与queue_work类似
int cancel_delayed_work(struct delayed_work *work)在这个工作还未执行的时候就把它给取消掉
void flush_workqueue(struct workqueue_struct *wq);
void flush_scheduled_work(void)一般在调用cancel_delayed_work后都会继续调用flush_delayed_work这个是用来等到正在执行的队列执行完。实际上后者是为了解决cancel时的死锁问题。
要使用工作队列,首先要做的是创建一些需要推后完成的工作
DECLARE_WORK(name,void (*func) (void *), void *data)创建一个名为name,待执行函数为func,参数为data的work_struct结构
DECLARE_DELAYED_WORK(name, func);
INIT_WORK(structwork_struct *work, woid(*func) (void *), void *data)在运行时通过指针创建一个工作
PREPARE_WORK(struct work_struct work, work_func_t func);
INIT_DELAYED_WORK(struct delayed_work work, work_func_t func);
PREPARE_DELAYED_WORK(struct delayed_work work, work_func_t func);
wait_queue_head_t mywait;
队列头使用前要初始化
init_waitqueue_head(&mywait);
进入睡眠
wait_event(mywait, dev->wp!=dev->buf_size);
ret = wait_event_interruptible(mywait, dev-wp!=dev->buf_size);
唤醒等待队列中的睡眠进程
wake_up(&mywait);
wake_up_interruptible(&mywait);
ndelay(10); //延迟10ns
udelay(20); //延迟20us
mdelay(30); //延迟30ms
struct timeval tval;
struct timespec tspec;
调用内核的函数来获得绝对时间
do_gettimeofday(&tval);
getnstimeofday(&tspec);
定时器
声明定时器
struct timer_list mytimer;
定时器的执行函数,当定时器到期后,由硬件定时器中断执行一次
static void my_timer_func(unsigned long data)
{
...//不可睡眠
}
初始化定时器
setup_timer(&mytimer, my_timer_func, data);
初始化定时器时传入的参数为timer_list的指针;执行函数;传给执行函数的参数
启动定时器
mod_timer(&mytimer, jiffies+HZ);
定时器一旦启动,就会加入一个timer_list的链表,一旦到时,就会被执行。
启动定时器的人和执行的人不是一个。即使启动者退出,定时器仍然执行。
删除定时器
del_timer(&mytimer);
如果模块要rmmod,在卸载之前,必须删除所有没执行的定时器。
内核中断
中断号都定义在<mach/irqs.h>中,可以用两种不同的方法来查找中断号:
(1)芯片内部的外设
首先明确设备的名字,然后利用名字匹配,自行在irqs.h中找到对应的中断号;
比如看门狗设备对应的中断号为IRQ_WDT, rtc硬件对应的为IRQ_RTC_ALARAM/IRQ_RTC_TIC
(2)芯片外部连接的设备
由于设备的中断引脚都连接到GPIO,因此可以利用GPIO号来找到中断号
中断号 = gpio_to_irq(GPIO号)
驱动人员在设计中断处理函数时,要遵循的要求是:
(1)可嵌套不可重入
(2)不能睡眠
(3)如果硬件有中断的状态寄存器,软件要负责清除中断的标志位。一般来说,如果不清除标志位,设备无法再次产生中断
kzalloc(size, GFP_KERNEL); //可能睡眠
kzalloc(size, GFP_ATOMIC); //不会睡眠
1.
确定中断号
#define KEY_IRQ gpio_to_irq(gpio号);
中断处理函数
static irqreturn_t key_service(int irq, void *dev_id)
{
...
return IRQ_HANDLED 或 IRQ_NONE;
}
注册中断处理函数,必须检查返回值
u32 flags = IRQF_TRIGGER_FALLING
| IRQF_TRIGGER_RISING;
ret = request_irq(KEY_IRQ, /* 中断号 */
key_service, /* 中断处理函数 */
flags, /* 中断的标志 */
"xxx", /* 中断处理函数的名字 */
dev_id);
/* 最后的参数dev_id为传给中断处理函数的参数,一般会设置为私有结构体的指针,不能为NULL */
实际上,如果是非共享的中断,dev_id可以为NULL
注销中断处理函数
free_irq(irq, dev_id) dev_id一定要和request_irq中的最后一个参数一致。
人为关闭(mask)/打开某个中断:
disable_irq(int irq);
enable_irq(int irq);
上面的两个函数支持嵌套,也就是说,如果调用了3次disable_irq,需要enable_irq3次,才能真正使能中断
要确保先调用disable_irq,再调用enable_irq;
如果要屏蔽整个cpu的中断,可以用:
local_irq_disable();
local_irq_enable();
实际上是将CPSR寄存器的I位置1或清0
2.
中断下半部
在进入中断处理函数前,会默认关闭本中断。对于某些要求迅速响应或数据吞吐量很大的中断,要考虑将中断处理函数的工作分为两个部分,分别称为中断的上半部和下半部。
下半部的实现有多种方法,包括softirq,tasklet和工作队列(work queue)。对于驱动来说,只会使用tasklet和工作队列(work queue)
打开或关闭本cpu的下半部:
local_bh_enable();
local_bh_disable();
tasklet
(1)在上半部执行完后马上执行,但此时中断是全部打开的;
(2)执行tasklet时内核仍处于中断上下文,因此不能睡眠;
(3)tasklet的执行函数不会重入;
(4)如果在tasklet的执行期间再次发生调度,第二次调度无效;
声明tasklet结构体
struct tasklet_struct mytask;
tasklet的执行函数
void bo_service(unsigned long data)
{
}
上半部的执行函数
irqreturn_t up_service(int irq, void *dev_id)
{
//首先完成和硬件交互之类的重要工作
//触发tasklet下半部
tasklet_schedule(&mytask);
或tasklet_hi_schedule(&mytask);
...
}
初始化tasklet
tasklet_init(&mytask, bo_service, (unsigned long)dev);
工作队列
(1)推后到进程上下文执行,此时中断是全部打开的;
(2)执行work时处于进程上下文,因此可以睡眠;
(3)work的执行函数不会重入;
(4)如果在work的执行期间再次发生调度,第二次调度无效;
宏定义
ioctl()的cmd的大小为 32位,共分 4 个域:
_IOC_DIR
bit31~bit30 2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。
_IOC_SIZE
bit29~bit15 14位为 "数据大小" 区,表示 ioctl() 中的 arg 变量传送的内存大小。
_IOC_TYPE
bit20~bit08 8位为 “魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
_IOC_NR()
bit07~bit00 8位为 "区别序号" 区,是区分命令的命令顺序序号。