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),  
};


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里头

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 占用的内存


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库的使用

中获得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)
上述宏定义在

设置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,在卸载之前,必须删除所有没执行的定时器。



内核中断

中断号都定义在中,可以用两种不同的方法来查找中断号:

(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位为 "区别序号" 区,是区分命令的命令顺序序号。


你可能感兴趣的:(PCI驱动使用函数)