Linux 驱动基础知识笔记

1、入门node

一、字符设备驱动linux

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


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

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

三、检测用户传来的空间是否是合法cookie

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

四、异步通知网络

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

五、/proc数据结构

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

通常状况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);

六、内核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文件的内容来决定。

2、驱动模型

一、内核对象

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对象。

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

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

三、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

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

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

  使用class_create能够建立一个类,系统注册的类能够在/sysfs/class目录下找到

五、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);

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

七、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:
}

八、设备事件通知

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规则文件中一个不以“#”开头的行就是一条规则。每条规则包含匹配键和执行键。配置键以“==”号与值链接;执行用“=”

九、设备树

  设备树用来描述板卡板级硬件信息。设备树位于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.

3、内核同步机制

一、原子操做

typefef stuct (volatile int counter;)atomic_t;

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

二、自旋锁(一直循环直到条件知足)致使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恢复中

三、读写锁

读写锁(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

四、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函数则原地等待,它被唤醒时,便可释放旧的数据指针。

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

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

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

六、读写信号量:与读写锁原理差很少。

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

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

八、等待队列

  等待队列用于异步通知和阻塞式访问。若是进程须要等待某些条件放生才能继续,则可使用等待队列机制。在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函数唤醒等待队列。

4、内存管理和链表

一、物理地址和虚拟地址

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

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

二、内存分配

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

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

三、cache

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

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

四、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地址映射到虚拟地址。

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

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

六、DMA映射

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

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

七、链表是双向链表:能够双向遍历

5、任务和调度

一、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);

二、内核线程kthread_create

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

三、内核调用应用程序

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

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

四、软中断机制

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

五、工做队列

1)原理

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

2)延迟工做队列:延迟工做队列基于工做队列,能够实现延迟一段时间再将工做加入到工做队列

六、内核时间

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

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

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

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

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

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

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

二、嵌入式Linux系统构成

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

三、硬件初始化

硬件初始化放在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

四、clk体系

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

五、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来操做相应的寄存器。

六、寄存器访问

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 用来清除看门狗定时间中断,写入任意值将清除中断。

七、电平控制

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

八、硬件中断处理

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

若是中断处理过程很是复杂,能够分红两个部分:上半部和下半部。上半部完成一些紧急事物,下半部完成剩余的事物。上半部不能够中断,下半部能够。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)

九、看门狗驱动框架

在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);注册

十、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,该函数会调用建立设备节点函数)

十一、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)
相关文章
相关标签/搜索