ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Linux 驱动基础知识笔记

2020-01-14 11:56:09  阅读:561  来源: 互联网

标签:struct int void 笔记 基础知识 中断 内核 Linux kobject


一、入门

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)



标签:struct,int,void,笔记,基础知识,中断,内核,Linux,kobject
来源: https://blog.51cto.com/zhaoxiaohu/2466580

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有