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)
相關文章
相關標籤/搜索