资讯

精准传达 • 有效沟通

从品牌网站建设到网络营销策划,从策略到执行的一站式服务

linux设备驱动基础知识

这篇文章给大家分享的是有关linux设备驱动基础知识的内容。小编觉得挺实用的,因此分享给大家做个参考。一起跟随小编过来看看吧。

成都创新互联公司主要从事成都做网站、成都网站设计、网页设计、企业做网站、公司建网站等业务。立足成都服务庐山,十载网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:028-86922220

一、注册

1、字符设备驱动

1)注册字符设备

static inline int register_chrdev(unsigned int major, const char *name,
  const struct file_operations *fops);

2)cdev_add 其实1)调用了cdev_add

int cdev_add(struct cdev *p, dev_t dev, unsigned count);
/* 调用关系 */
register_chrdev
    __register_chrdev
        cdev_add

2、用户空间和内核空间的数据拷贝

1)copy_to_user/copy_from_user://拷贝一个空间
static __always_inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n);
static __always_inline unsigned long __must_checkcopy_from_user(void *to, const void __user *from, unsigned long n)
2)put_user(x,p)/get_user://从p指针传单个值
#define put_user(x, ptr)					\
({								\
	void __user *__p = (ptr);				\
	might_fault();						\
	access_ok(VERIFY_WRITE, __p, sizeof(*ptr)) ?		\
		__put_user((x), ((__typeof__(*(ptr)) __user *)__p)) :	\
		-EFAULT;					\
})

3、检测用户传来的空间是不是合法

access_ok(int ,const void *addr,ulong)
ex:if(!access_ok(verify_write,buffer,count))return error;

4、异步通知

fasync_helper(int fd,struct file,int on,struct fasync_struct **);//on 0表示去除异步通知,1表示添加异步通知
kill_fasync(stuct fasync_struct **fp,int sig,int band);//当时间到达,将用来通知相关的进程

5、/proc

  通过它可以在运行时访问内核的内部数据结构,改变内核设置,通过它发送信息。ps、top命令就是通过读取/PROC下的文件来后去信息。

一般情况proc自动加载,如果启动没有自动加载,可以用:mount -t proc proc /proc

内核还提供了一些/proc文件系统的接口函数:proc_mkdir;proc_create;proc_create_data;proc_remove;remove_proc_entry;

struct proc_dir_entry *proc_mkdir(const char *name,struct proc_dir_entry *parent);

6、内核makefile

kbuild Makefile

obj-y表示连接进内核,obj-m表示编译成可以加载的模块

1)目标定义

obj-(CONFIG_I2C_BOARDINFO)+=i2c-boardinfo.o

2)多文件模块定义

obj-(CONFIG_FB)+=fb.o
fb-y:=fbmem.o fbmon.o.....
fb-objs:=$(fb-y)

3)目录迭代

obj-$(CONFIG_FB_OMAP)+=OMAP/

如果CONFIG_FB_OMAP的值是y或者m,kbuid会将omap目录列入向相下迭代的目标中,但是其作用仅限于此,至于omap目录下文件是要作为模块编译还是连接进入内核,还要由omap目录下的makefile文件的内容来决定。

二、驱动模型

1、内核对象

1)kobject(内核对象,是内核设备管理机制的最高的层抽象):一个kobject对应sysfs文件系统一个目录,还负责设备热插拔等事件的处理工作。

对应有一些接口函数:kobject_init;kobject_add;将kobject加入到系统;kobject_init_and_add;。。。等

void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
int kobject_add(struct kobject *kobj, struct kobject *parent,const char *fmt, ...);

常见的kobject包括:

struct kobject *dev_kobh;//设备对象;
kobject *sysfs_dev_char_kobj;//字符设备对象;
struct kobject *sysfs_dev_block_kobj;//块设备对象;
struct kobject *kernel_kobj;//sysfs下的kernel对象。

2、内核对象的类型:kobj_type{....sysfs_ops..};

sysfs_ops为内核对象在sysyfs文件系统中的接口:show(kobject。。)显示,store(kobject。。)存储

3、kset kobject 通过kset组织层次化结构

kset{
struct list_head list;//同一kset的链表
spinlock_t list_lock;//锁
struct kobject kobj;//自身的kobject
struct kset_uevent_ops *uenent_ops;//uevent 相关操作,如事件过滤
}

常见的kset包括:

struct kset *bus,*class,*system

4、设备模型层次:模型包括device、device_driver、bus、class(设备类型)

  设备和设备总线均挂载在总线上,总线完成设备、设备驱动的匹配

  使用class_create可以创建一个类,系统注册的类可以在/sysfs/class目录下找到

5、sysfs文件系统

  系统中每个kobject对应这sysyfs中的一个目录,而每一个sysyfs中的目录代表一个kobject对象,每个sysfs文件代表对应kobject属性。

sysfs文件系统最基本的函数包括:

sysfs_create_file创建文件,sysfs_create_dir_ns创建目录等

static inline int __must_check sysfs_create_file(struct kobject *kobj,const struct attribute *attr);

6、platform 平台概念的引入能更好的描述设备的资源信息,例如总线地址、中断、dma信息到呢个。也叫做虚拟总线。

7、attributes:设备、驱动、类均有自己的属性,这些属性在attribute结构的基础上,增加了显示与存储接口。

struct attribute{
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_classs *key;
strct loce_class_key skey:
}

8、设备事件通知

1)kobject uevent 是内核中东发送给应用层设备事件。

kobject uevent包括 

enum kobject_action {
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_BIND,
KOBJ_UNBIND,
KOBJ_MAX
};

通过netlink机制,内核通过kobject_uevent->kobject_uevent_env函数发送给netlink客户端;

2)uevent helper

  如果内核支持uevent helper ,kobject_uevent_env就会调用应用层的uevent helper程序

  Linux下的设备管理通常使用udev工具。mdev用来在嵌入式中替代udev。udev包含一个一直运行的后台进程。与udev不同,mdev不是一直运行的后台程序,它使用内核唤醒,则mdev要被设置成uevent_helper程序。

3)udev

  它是用来监控udev客户端的控制信息,内核的hotplug事件,配置文件变化事件。当有设备插拔时,udev是会收到通知,它根据事件中参数和sysfs中的信息,调用合适的事件处理函数,创建和删除/dev节点。

  udev是通过netlink机制获取内核的uevent事件。mdev是通过直接访问/sys/class/目录来获取设备信息。

  udev按照规则文件中的规则处理uevent事件,udev规则文件在目录/etc/udev/rules.d下面。udev通过文件系统的inotify功能,监控其规则文件目录/etc/udev/rules.d,一旦该目录下的规则文件变化,它就重新加载规则文件。udev规则文件中一个不以“#”开头的行就是一条规则。每条规则包含匹配键和执行键。配置键以“==”号与值连接;执行用“=”

9、设备树

  设备树用来描述板卡板级硬件信息。设备树位于Linux内核目录代码arch/arm/boot/dts下,dts文件为板级定义,dtsi危机为soc级定义。Linux设备树编译 make dtbs。

内核启动时会建立设备树节点:

setup_arch
{
mdesc=setup_machine_fdt(__atags_pointer);//建立设备树
unflagten_device_tree();//扫描设备树,转换成device_node
。。
}

  bootloader将设备树的地址传给内核,放在R2寄存器中,在arch/arm/kernel/head-common.s文件同__mmap_switched赋值给__atags_pointer.

三、内核同步机制

1、原子操作

typefef stuct (volatile int counter;)atomic_t;

volatile修饰符告诉编译器不要对该类型的数据进行优化

2、自旋锁(一直循环直到条件满足)导致cpu效率降低

spin_lock_init;
spin_lock;
spin_trylock;s
pin_unlock

spin_lock获取成功立即返回,否则原地打转。try函数尝试获取,如果立即获取则返回真,否则返回假。

中断安全的自旋锁函数:

硬件中断

spin_lock_irq;
spin_unlock_irq;
spin_lock_irqsave;
spin_unlock_irqresore;

软件中断

spin_lock_bh;
spin_unlock_bh;

禁止本地cpu上的中断与内核抢占。save保存本地中断状态,restore恢复中

3、读写锁

读写锁(rwlock)是一种特殊的自旋锁。允许同时有多个读者来访问共享资源。一个读写锁同时只能有一个写着和多个读者。

如果读写锁当前没有读着也没有写着,写者可以立即获取读写锁,否则自旋,直到没有任何写和读着。如果读写锁没有写者,那么读者可以立即获取读写锁,否则自旋,直到写着释放该读写锁。

rwlock_t x;
rwlock_init(x);//动态初始化读写锁
rwlock_t x=RW+LOCK_UNLOCKED//静态初始化

读写尝试

read_lock;wirte_lock;read_trylock
read_unlock;wirte_unlock; write_trylock

4、rcu(读-复制-修改) 之使用于读多写少的情况。

原理是对于被rcu保护的共享数据机构,读者不需要获取任何锁就可以访问它,但写者在访问它时需要先复制一个副本,然后对副本进行修改,最后调用一个函数在合适的时机修改。就是所有引用数据的任务都退出。

读者

#define rcu_read_lock() preempt_disable() //进入读操作临界区标记
#define rcu_read_unlock() preempt_enable() //退出读操作临界区

写者一般对副本操作,然后将副本设定成正本,最后同步或者异步的释放旧的。

struct rcu_head{
struct tcu_head*next;//下一个rcu_head
void (*func)(stuct rcu_head*);//获取竞争条件后的处理函数
};

添加回调函数 同步rcu

void call_rcu(struc rcu_head*,rcu_callback_T func);\ void synchronize_rcu(void);

call_rcu函数调用后,直接返回,rcu软中断会调用回调汗死释放旧的数据指针。sysnchronize_rcu函数则原地等待,它被唤醒时,即可释放旧的数据指针。

5、信号量:是一种睡眠锁。如果信号量被占用,信号量将将会将其调用者加入等待队列。

  自旋锁和信号量的第一个区别:前者不引起调用者睡眠。自旋锁和信号量的选用主要看锁被持有的时间长短,如果短,就用自旋锁。第二个区别:信号量有多个持有者,而自旋锁只能有一个持有者。

sema_init(struct semaphore *sem,int val);down()down_trylock()down_interruptible(能被信号打断);获取,up()释放,唤醒等待队列

6、读写信号量:与读写锁原理差不多。

7、互斥量:mutex,同一时间只允许一个访问者,互斥量加锁失败会进入睡眠等待唤醒。

mutex_init(mutex);void mutex_lock(mutex*);;int mutex_trylock();void mutex_unlock();

8、等待队列

  等待队列用于异步通知和阻塞式访问。如果进程需要等待某些条件放生才能继续,则可以使用等待队列机制。在Linux内核中通常使用等待队列来实现阻塞式访问。

初始化一个等待队列

void init_waitqueue_head(wait_queue_head_t*q);

等待事件发生函数:

wait_event(wq,condition)//不可中断的等待
wait_event_interruptible(wq,condition)//可中断的等待
wait_event_timeout(wq,condition,timeout)
wait_event_interruptible_timeout

唤醒等待队列

wake_up(wait_queue_head_t,*Q);//唤醒所有等待q的进程
wake_up_interruptible(*Q);//只唤醒可以中断休眠的进程

加入或退出等待队列

add_wait_queue(wait_queue_head_t *,wait_queue_t*)
add_wait_queue_exclusive
remove_wait_queue

加入等待队列的线程将等待唤醒。阻塞式字符驱动一般读函数中等待,并在中断或内核线程中使用wake_up函数唤醒等待队列。

四、内存管理和链表

1、物理地址和虚拟地址

  如果cpu没有mmu则发出的地址就是直接传到芯片引脚,这个地址脚物理地址;如果有mmu,则发出的地址就是虚拟地址,mmu会将虚拟地址映射成物理地址。

  mmu将虚拟地址映射到物理地址是以页为单位,对于32位cpu,通常一个页4KB。物理内存中的页称为物理页面或者页帧。mmu使用页表来记录虚拟地址页面与物理内存页面之间的映射关系。

2、内存分配

最长用的内存申请和释放函数:

void *kmalloc(size_t size,gfp_t flags);
void *kzalloc(size_t size,gfp_t flags);//调用kmalloc分配内存并将内存清零
void kfree(const void*x);

Kmalloc函数分配的地址空间是线性映射的,它一般分配小于128kb的内存。

flags GFP_KERNEL内核空间进程使用。GFP_USER为用户空间分配空间,GFP_HIGHUSER从高端地址分配 。。。等

如果要分配大块内存,应使用面向页的技术

unsigned long get_zeored_page(gfp_t gfp_mask);//返回一个单个的,零填充的页
unsigned long __get_free_pages(gfp_t mask,unsigned int order);//直接获取整页的内存(页数是2 的幂)
free_page(addr,order);

如果需要申请一块连续的虚拟地址内存,物理地址不是连续的,页表查询比较频繁,效率底:

void *vmalloc(size);
void *vmalloc_user(size);为用户空间分配内存
void vfree(void *addr);

3、cache

高速缓存。Linux使用slab机制管理cache。kmem_cache_create创建slab缓存。

kmem_cache_alloc//从cache中分配内存
kmem_cache_free
kmem_cache_destroy//销毁slab缓存

4、IO端口到虚拟地址映射

  arm中,外设I/0端口具有和内存一样的物理地址,外设的i/O内存资源地址是已知的,有硬件的设计决定。Linux的驱动程序并不能直接通过物理地址访问I/0内存资源,而必须将物理地址转换成虚拟地址。

1)静态映射

  在arm存储系统中,使用mmu实现虚拟地址到物理地址的映射。mmu的实现过程,实际上就是一个查表映射的过程。建立页表是实现mmu功能不可或缺的一步。页表位于系统的内存中,页表的每一项对应于一个虚拟地址到物理地址的映射。

Linux内存的create_mapping函数创建线性映射表。

stuct map_desc{
unsigned ling virtual;//虚拟地址
unsigned long pfn;//__phys_to_pfn(phy_addr)
unsiged long length;//长度
unsiged int type;
}
void __init create_mapping(struct map_desc*md);
/* 例:
arm平台使用iotable_init来创建平台专用映射:*/
void __init iotable_init(struct map_desc *io_desc,int nr);
 
static struct mcp_Desc smdk6410_iodesc[] = {};// 需要建立的映射在此添加
s3c64xx_init_io(smdk6410_iodesc,ARRAY_SIZE(smdk6410_iodesc));
{
iotable_init(smdk6410_iodesc,ARRAY_SIZE(smdk6410_iodesc));;
..
..
..
}

2)ioremap

如果需要在模块中动态映射IO,可以采用ioremap函数。此函数将i/o内存资源的物理地址映射到核心虚拟地址空间。

typedef phys_addr_t resource_size_t;
void __iomem *ioremap(resource_size_t res_cookie/*物理地址*/,size_t size);
void iounmap(volatile void __iomem *iomem_cookie);//取消映射

例:

reserve_virt_addr=ioremap(100*1024*1024,10*1024*1024);//将101MB开始的10MB地址映射到虚拟地址。

5、内核空间到用户空间的映射

  mmap接口。将内核地址映射到用户地址,应用程序可以直接访问内存地址。

  系统调用

 unsigned long mmap(unsigned long addr,unsigned long len,int prot,int flags,int fd,long off);//取消映射munmap函数

驱动需要实现

memapmem_fops{
..
.mmap = memapmem_mmap;
}

例:

fd=open("/dev/mmap",O_RDWR);
addr=mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

6、DMA映射

1)建立一致性DMA映射:dma_alloc_coherent(禁止页表的Cacheable项和Bufferable)

2)建立非一致性DMA映射:dma_alloc_noncoherent

7、链表是双向链表:可以双向遍历

五、任务和调度

1、schedule

  linux进程在等待资源就绪的过程中,可以主动让出cpu,自身进入休眠状态,等待唤醒后继续检查资源是否就绪。进程可以调用schedule函数让出cpu,进程被唤醒后将从schedule函数的下一条代码开始执行。

void _sched schedule(void)
signed long _sched schedule_timeout(timeout)//带超时的调度
例:
process a:
set_current_state(TASK_INTERRUPTIBLE);
spin_lock(&list_lock);
if(list_empty(&list_head)){
spin_unlock(&list_lock);
schedule();
spin_lock(&list_lock);
}
set_current_state(SASK_RUNNING);
spin_unlock(&list_lock);
process b:
spin_lock(&list_lock);
list_add_tail(&list_head,new_node);
spin_lock(&list_lock);
wake_up_process(process a);

2、内核线程kthread_create

  kthread_cretate创建的线程不能立马运行,需要wake_up_process函数唤醒。kthread_run(先调用kthread_create,再调用wake_up_process)宏完成了kthread_create与wake_up_process两步。      kthread_stop结束内核线程,应保证线程函数尚未结束,否则会一直等待。

3、内核调用应用程序

int call_usermodehelper(char *path,char **argv,char **envp,int wait);

path程序路径,argv参数,envp环境变量,wait等待结束标志

4、软中断机制

1)原理

  硬件中断是硬件产生的中断信号,软中断是软件模拟的中断。硬件产生中断后,会将中断通知给cpu,cpu查询向量表将中断映射成具体的程序。软中断完成在操作系统内部,内核运行一个守护进程来实现中断查询与执行,这个线程的功能类似处理器的中断控制器。构成软件中断机制的核心元素包含:软件中断状态(soft interrupt state)、软中断向量表(softirq_vec)、软中断线程(softirq thread)

  系统在ksoftirqd内核进程中调用__do_softirq循环检测软中断是否处于pending状态,如果是,则执行相应处理函数。

  在linux 4.5内核最多可以有10中软中断,包括定时器、网络软中断、tasklet。优先级从0-9,对应10 个已经定义好的函数。

  内核将整个的中断处理流程分为了上半部和下半部。上半部就是之前所说的中断处理函数,它能最快的响应中断,并且做一些必须在中断响应之后马上要做的事情。而一些需要在中断处理函数后继续执行的操作,内核建议把它放在下半部执行。

2)tasklet

  软中断是利用软件模拟的中断机制,常用来执行异步任务。tasklet是利用软中断实现的一种下半部机制。

  软中断和tasklet优先级较高,性能较好,调度快,但不能睡眠。而工作队列是内核的进程调度,相对来说较慢,但能睡眠。所以,如果你的下半部需要睡眠,那只能选择工作队列。否则最好用tasklet。

三个步骤:

(1)编写tasklet处理程序
static void tasklet_callback(ulong data);
(2)声明tasklet
DECLEARE_TASKLET(tasklet,tasklet_callback,0);
(3)调度tasklet
static irqreturn_t irq_handler(int irq,void *arg)
{
tasklet_schedule(&tasklet);
return IRQ_HANDLED;
}

5、工作队列

1)原理

  工作队列类似tasklet,允许调用者请求在将来某一个时间调用一个函数。tasklet在软中断上下文中允许,所以tasklet执行很快。工作队列在一个特殊内核进程上下文运行,有很多灵活性,并且能够休眠。工作队列包括一系列将要执行的任务和执行这些任务的内核线程。每个工作队列有一个专门的线程,所有的而任务必须在进程的上下文中运行,这样可以安全的休眠。Linux提供一系列全局work queue,包含system_wq、system_highpri_wq等。驱动程序可以创建并使用他们自己的工作队列。

2)延迟工作队列:延迟工作队列基于工作队列,可以实现延迟一段时间再将工作加入到工作队列

6、内核时间

1)时间概念

(1)时钟周期(clock cycle):晶振振荡器在1s内产生的时钟脉冲个数。Linux用宏CLOCK_TICK_RATE来表示计数器的输入时钟脉冲的频率。

(2)时钟滴答(clock tick):一次时钟中断产生一次时钟滴答。系统每个时钟周期产生一次时钟中断。

(3)时钟滴答频率:1s内的时钟滴答次数。Linux内核用HZ来表示时钟滴答的频率,而HZ通常就是1s。

(4)全局变量(jiffies):一个32为无符号整数,用来表示自内核上一次启动以来的时钟滴答次数。每滴答一次,内核的时钟中断处理函数timer_interrupt会将该变量加1.

(5)xtime:timeval结构全局变量,记载系统自开机以来的当前时间,基准为1970.1.1

(6)系统时钟:也是软件时钟,由软件根据时间中断计时。

内核可以应下面函数获取和设置系统时间:

void do_gettimeofday(struct timeval *tv);int do_settimeofday(struct timespec *tv)
timeval和timespec与jiffies转换 timespec_to_jiffies;timeval_to_jiffies

2)Linux下的延迟

内核定义了一堆宏来实现延迟:

#define time_after(a,b)
#define time_before
#define time_after_eq(a,b)
 
#define ndelay(n)//纳秒
#define udelay(n)//微秒
#define mdealy(n)//毫秒

以上都是忙等待,会导致其他任务此时间无法使用cpu,下面是不必忙等待的短延迟方法:

void msleep(u int);ulong msleep_interruptible(u int);单位是milliseconds。

3)内核定时器

timer_list{
struct list_head list;
ulong ecpires;//定时器到期时间
ulong data;//传递给处理函数的
void (*fun)(ulong);//回调函数
}

操作:

增加:add_timer(timer_list *)

删除:del_timer

修改ecpire值:mod_timer

六、简单硬件设备驱动程序

1、处理器访问硬件设备主要通过下面几种方式:

(1)内存方式。外设的内存空间被映射到处理器的地址空间,处理器通过访问映射地址来访问硬件

(2)I/O接口。处理器与I/O设备之间通过一定的接口连接,这个接口就是I/O接口。I/O接口中包括一组寄存器以及控制电路。

(3)管脚(pin)。管脚可以用来对芯片进行复位,并接收来自设备的中断信号。另外有些芯片还可以通过管脚进行简单的模式配置。

  在x86体系中,I/O地址空间与内存地址空间是分开的,寄存器位于I/O空间是,称为I/O端口。在arm等体系中,I/O通常是和内存统一编制的,也称为I/O内存,是系统中访问速度最快的内存。

2、嵌入式Linux系统构成

bootlader (传参,设备树(R2寄存器)等)-》kernel-》根文件系统-》其他文件系统挂载在根文件系统下面

3、硬件初始化

硬件初始化放在kernel下的arch目录下,如arch/arm/mach-xxx/mach-xxxx.c

DT_MACHINE_START(LS1021A, "Freescale LS1021A")
.smp = smp_ops(ls1021a_smp_ops),
.dt_compat = ls1021a_dt_compat,
MACHINE_END

4、clk体系

时钟就像人的心跳,没有时钟,外设就无法运行。时钟相关代码在/driver/clk

5、dev/mem与dev/kmem

/dev/mem是物理内存的映射,可以用来访问物理I/O设备,例如接口控制器的寄存器。/dev/kmem是虚拟内存的映射,可以用来看下kernel的变量等信息。

例:

target = strtoul(argv[1],0,0);

打开内存设备:

fd=open("/dev/mem",O_RDWR|O_SYNC);

映射一个页面

map_base=mmcp(0,MAP_SIZE,PORT_READ|PORT_WRITE,MAP_SHARED,fd,target&~MAP_MASK);

根据数据类型获取内存的值

vir_addr = map_base+(target&map_mask);

然后就可以通过操作vir_addr来操作相应的寄存器。

6、寄存器访问

1)如S3C6410X处理器,支持32为物理地址空间,这些空间分为两个部分,一部分用于存储,一部分用于外设。

  通过spine总线访问主存,主存范围i是0x00000000~0x6fffffff

引导镜像区:-0x07ffffff

内部存储区:-0x0fffffff

静态存储区:-0x3fffffff 用于访问SROM,SRAM NOR FLASH

动态存储区:-0x6fffffff

  外设区域通过peri总线访问,范围0X70000000-0X7FFFFFFF.

  Linux必须将外设的物理地址映射成虚拟地址才能使用。

  地址映射可以采用固定地址映射

#define S3C_VA_IRQ S3C_ADDR(0X00000000) /*irq控制器*/

  另一种方式采用ioremap函数。

  当I/O寄存器与内存统一编址时,I/O寄存器也称I/O内存。当I/O寄存器与内存分开编址时,I/O寄存器也称I/O端口。在I/O内存资源地址映射成虚拟地址后,为了保证驱动程序的跨平台性,应该使用Linux中特定的函数访问I/O内存资源,而不应该通过指向虚拟地址的指针来访问。

void writew(u16,volatile void __iomem*addr);
void iowrite16(u16,void __iomem*addr);
void iorwrite16_rep(const volatile void __iomem*addr,void *buffer,uint cont);//连续的

2)看门狗

为保证系统出现异常时能自动启动,处理器均提供了看门狗功能。看门狗单元即可以产生复位信号,也可以被用作一个普通的16位间隔定时器来产生中断服务。

看门狗寄存器

WTCON 0x7e004000 r/w 看门狗定时器控制寄存器

WTDAT 0X7E004004 R/W 看门狗定时器数据寄存器

WTCNT 0X7E004008 R/W 看门狗计数器计数控制器

WTCLRINT 0X7E00400C W 中断清除寄存器

WTDAT 保存看门狗定时器重载计数值。WTCNT保存看门狗定时器当前的值。WTCLRINT 用来清除看门狗定时间中断,写入任意值将清除中断。

7、电平控制

  一般电平包括高、底电平两种。常用的电平包括TTL电平、CMOS电平和RS232电平,各种电平的电压范围不同,TTL电平信号+5V等价于逻辑1,0V等价于0.一般输入,<1.2V为低电平,>2.0V为高,输出,<0.8低,>2.4高。电平控制离不开GPIO控制。

8、硬件中断处理

  由硬件产生的一种电信号,并直接送入中断控制器输入引脚,再由中断控制器向处理器发送相应的信号。

如果中断处理过程非常复杂,可以分成两个部分:上半部和下半部。上半部完成一些紧急事物,下半部完成剩余的事物。上半部不可以中断,下半部可以。Linux中的下半部包括软中断、tasklet机制和工作队列、定时器等。

发生中断时:

cpu跳到"vector_irq", 保存现场, 调用C函数handle_arch_irq

handle_arch_irq:

a. 读 int controller, 得到hwirq

b. 根据hwirq得到virq

c. 调用 irq_desc[virq].handle_irq

  驱动注册中断处理函数:驱动程序 request_irq(virq, my_handler)

9、看门狗驱动框架

在Linux/drivers/watchdog目录。

看门狗设备结构

struct watchdog_device

注册与注销看门狗:int watchdog_register_device(watchdog_device *);void watchdog_unregister_device();

看门狗有一个重要的参数,就是看门狗操作:

struct watchdog_ops{
int (*start)(struct watchdog_device*);
...
}

watchdog_register_device会调用一个杂项设备驱动,注册一个字符设备驱动。

例:

static const struct watchdog_info s3c2410_wdt_ident = {
.options = OPTIONS,
.firmware_version = 0,
.identity = "S3C2410 Watchdog",
};
static const struct watchdog_ops s3c2410wdt_ops = {

.owner = THIS_MODULE,
.start = s3c2410wdt_start,
.stop = s3c2410wdt_stop,
.ping = s3c2410wdt_keepalive,
.set_timeout = s3c2410wdt_set_heartbeat,
.restart = s3c2410wdt_restart,
};
static const struct watchdog_device s3c2410_wdd = {
.info = &s3c2410_wdt_ident,
.ops = &s3c2410wdt_ops,
.timeout = S3C2410_WATCHDOG_DEFAULT_TIME,
};
watchdog_register_device(&wdt->wdt_device);注册

10、RTC驱动

  嵌入式系统一般有两个时间,一个是RTC时间,一个是Linux系统时间。RTC时间存储在RTC控制器中,系统断电后通过电池供电,保证系统下次重新上电都能读到正确的时间。通常在系统启动脚本中读取RTC时间,并将RTC时间设置为系统时间。Linux中的date命令是用来读取和设置系统时间;而hwclock命令是用来读取和设置RTC时间的。

注册与注销RTC驱动

devm_rtc_device_register(&pdev->dev, "s3c", &s3c_rtcops,THIS_MODULE);

RTC设备类的操作函数接口

struct rtc_class_ops {
int (*ioctl)(struct device *, unsigned int, unsigned long);
int (*read_time)(struct device *, struct rtc_time *);
int (*set_time)(struct device *, struct rtc_time *);
int (*read_alarm)(struct device *, struct rtc_wkalrm *);
int (*set_alarm)(struct device *, struct rtc_wkalrm *);
int (*proc)(struct device *, struct seq_file *);
int (*set_mmss64)(struct device *, time64_t secs);
int (*set_mmss)(struct device *, unsigned long secs);
int (*read_callback)(struct device *, int data);
int (*alarm_irq_enable)(struct device *, unsigned int enabled);
int (*read_offset)(struct device *, long *offset);
int (*set_offset)(struct device *, long offset);
};

RTC驱动也包含一个通用的设备层,负责创建/dev/trc设备,并向应用层提供统一接口(调用devm_rtc_device_register注册RTC,该函数会调用创建设备节点函数)

11、LED类设备

  Linux 内核定义了LED类设备专门的处理各种外设的LED灯。

struct led_classdev{
 ..
}
#define led_classdev_register(parent, led_cdev) \
  of_led_classdev_register(parent, NULL, led_cdev)
void led_classdev_unregister(struct led_classdev *led_cdev)

看完上述内容,你们对linux设备驱动有进一步的了解吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注创新互联行业资讯频道,感谢各位的阅读。


本文标题:linux设备驱动基础知识
文章分享:http://cdkjz.cn/article/igedjd.html
多年建站经验

多一份参考,总有益处

联系快上网,免费获得专属《策划方案》及报价

咨询相关问题或预约面谈,可以通过以下方式与我们联系

大客户专线   成都:13518219792   座机:028-86922220