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)