PCI驱动使用函数

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),  
};
  1. 私有数据

pci_set_drvdata 设置驱动私有数据

pci_get_drvdata 获取驱动私有数据

  1. 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)
  1. 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里头
pci_restore_state 从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 占用的内存

  1. 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在加入链表之前,都要先初始化一下

  1. 添加
list_add(new, &my_head);
list_add_tail(new, &my_head);

向链表中加入新成员

  1. 删除
list_del(new);
  1. 通过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);    
    ...
}
  1. 链表的遍历
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

  1. 中断下半部

在进入中断处理函数前,会默认关闭本中断。对于某些要求迅速响应或数据吞吐量很大的中断,要考虑将中断处理函数的工作分为两个部分,分别称为中断的上半部和下半部。
下半部的实现有多种方法,包括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位为 “区别序号” 区,是区分命令的命令顺序序号。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值