您好,登錄后才能下訂單哦!
回顧:內核競態與并發
什么情況下會產生競態
1)SMP
2)單CPU支持任務搶占
3)中斷和進程之間
4)中斷和中斷之間
解決競態的方法
1)中斷屏蔽
2)原子操作
位原子操作
×××原子操作 atomic{ int。。。。}
3)自旋鎖
優點:一旦可以獲取鎖,立即獲取
缺點:長時間獲取鎖不成功,會消耗CPU資源
它所保護的臨界資源(代碼段)通常比較短
4)信號量
down(。。。)會導致睡眠
等等看前一章中
自旋鎖只允許一個持有者,信號量可以有多個持有者
信號量保護的臨界資源(代碼段)通常比較長
2,等待隊列 #include<linux/sched.h>
Read() recv()
Wait_event_interrptible //阻塞
Wake_up_interruptible //喚醒
阻塞/非阻塞
實際上,應用程序并不關心驅動里面read/write具體實現,只管調用并獲取返回值
如果設備沒有準備好數據給應用程序讀或者沒有準備好接受用戶程序寫,驅動程序應當阻塞進程,使它進入睡眠,直到請求可以得到滿足
阻塞讀
在阻塞型驅動程序中,如果進程調用read設備操作,但是設備沒有數據或數據不足,進程應該被阻塞,當有新數據到達后,喚醒被阻塞進程
阻塞寫
在阻塞型驅動程序中,如果進程調用write設備操作,但是設備沒有足夠的空間供其寫入,進程該被阻塞,但設備中的數據讀走后,緩沖區中空出部分空間,應該喚醒被阻塞進程
應用程序非阻塞讀
阻塞方式是文件讀寫操作的默認方式
應用程序可以通過使用O_NONBLOCK標志來人為的設置讀寫操作為非阻塞方式
定義在<asm-generic/fcntl.h>
如果設置了O_NONBLOCK標志,read和write的處理行為相同
if(0==pcdevp->)&&(O_NONBLICK&file->f_flags){
printk(KERN_ALET “Char......”);
return -EAGAIN;
---------------------------------------------------------------
多路監聽偵測 select #include <linux/poll.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
Nfds 要監聽的文件描述符最大值+1
Readfds,要監聽的讀文件描述符集合
Writefds要監聽的寫文件描述符集合
Exceptfds 要監聽的異常文件描述集合
Timeout 監聽的超時時間
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
內核中要實現的函數
Unsigned int(*poll)()
Fd_set rfds;
Struct timeval tv
先
FD_ZERO()
加入集合FD_SET()
POLLIN
有數據可讀。
POLLRDNORM
有普通數據可讀。
POLLRDBAND
有優先數據可讀。
POLLPRI
有緊迫數據可讀。
POLLOUT
寫數據不會導致阻塞。
POLLWRNORM
寫普通數據不會導致阻塞。
POLLWRBAND
寫優先數據不會導致阻塞。
POLLMSG
SIGPOLL 消息可用。
此外,revents域中還可能返回下列事件:
POLLER
指定的文件描述符發生錯誤。
POLLHUP
指定的文件描述符掛起事件。
POLLNVAL
指定的文件描述符非法。
這些事件在events域中無意義,因為它們在合適的時候總是會從revents中返回。使用poll()和select()不一樣,你不需要顯式地請求異常情況報告。
POLLIN | POLLPRI等價于select()的讀事件,POLLOUT |POLLWRBAND等價于select()的寫事件。POLLIN等價于POLLRDNORM |POLLRDBAND,而POLLOUT則等價于POLLWRNORM。
中斷
驅動需要包含 #include <linux/gpio.h>
判斷一個IO是否合法:int gpio_is_valid(int number);
設置GPIO的方向,如果是輸出同時設置電平:
/* set as input or output, returning 0 or negative errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
獲取輸入引腳的電平:
/* GPIO INPUT: return zero or nonzero */
int gpio_get_value(unsigned gpio);
/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);
int gpio_cansleep(unsigned gpio);
To access such GPIOs, a different set of accessors is defined:
/* GPIO INPUT: return zero or nonzero, might sleep */
int gpio_get_value_cansleep(unsigned gpio);
/* GPIO OUTPUT, might sleep */
void gpio_set_value_cansleep(unsigned gpio, int value);
獲取一個GPIO并聲明標簽:
/* request GPIO, returning 0 or negative errno.
* non-null labels may be useful for diagnostics.
*/
int gpio_request(unsigned gpio, const char *label);
/* release previously-claimed GPIO */
void gpio_free(unsigned gpio);
將GPIO映射為IRQ中斷:
/* map GPIO numbers to IRQ numbers */
int gpio_to_irq(unsigned gpio);
/* map IRQ numbers to GPIO numbers (avoid using this) */
int irq_to_gpio(unsigned irq);
設置GPIO IRQ中斷類型:
if (!sw->both_edges) {
if (gpio_get_value(sw->gpio))
set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_FALLING);
else
set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_RISING);
在驅動中使用延時函數mdelay,需要包含<linux/delay.h>文件
對于硬件產生的變化,需要CPI進行處理,通過情況下CPU獲取該變化的方式有種
1)輪詢
2)中斷 #include <linux/interrup>
Request irq(...)
注冊中斷request_irq(...)
Irq 要注冊的中斷號 arm/mach-xxxxx/include/mach/irqs.h
Gpio_to_irq()
handle 中斷處理函數
Irqreturn_t xxxxx(int irq,void *dev_id)
Irqflags 中斷標志
Cat /proc/interrupts
IRQF_SHARED
flag定義 描述
IRQF_TRIGGER_XXX 描述該interrupt line觸發類型的flag
IRQF_DISABLED 首先要說明的是這是一個廢棄的flag,在新的內核中,該flag沒有任何的作用了。具體可以參考:Disabling IRQF_DISABLED
舊的內核(2.6.35版本之前)認為有兩種interrupt handler:slow handler和fast handle。在request irq的時候,對于fast handler,需要傳遞IRQF_DISABLED的參數,確保其中斷處理過程中是關閉CPU的中斷,因為是fast handler,執行很快,即便是關閉CPU中斷不會影響系統的性能。但是,并不是每一種外設中斷的handler都是那么快(例如磁盤),因此就有 slow handler的概念,說明其在中斷處理過程中會耗時比較長。對于這種情況,在執行interrupt handler的時候不能關閉CPU中斷,否則對系統的performance會有影響。
新的內核已經不區分slow handler和fast handle,都是fast handler,都是需要關閉CPU中斷的,那些需要后續處理的內容推到threaded interrupt handler中去執行。
IRQF_SHARED 這是flag用來描述一個interrupt line是否允許在多個設備中共享。如果中斷控制器可以支持足夠多的interrupt source,那么在兩個外設間共享一個interrupt request line是不推薦的,畢竟有一些額外的開銷(發生中斷的時候要逐個詢問是不是你的中斷,軟件上就是遍歷action list),因此外設的irq handler中最好是一開始就啟動判斷,看看是否是自己的中斷,如果不是,返回IRQ_NONE,表示這個中斷不歸我管。 早期PC時代,使用8259中斷控制器,級聯的8259最多支持15個外部中斷,但是PC外設那么多,因此需要irq share。現在,ARM平臺上的系統設計很少會采用外設共享IRQ方式,畢竟一般ARM SOC提供的有中斷功能的GPIO非常的多,足夠用的。 當然,如果確實需要兩個外設共享IRQ,那也只能如此設計了。對于HW,中斷控制器的一個interrupt source的引腳要接到兩個外設的interrupt request line上,怎么接?直接連接可以嗎?當然不行,對于低電平觸發的情況,我們可以考慮用與門連接中斷控制器和外設。
IRQF_PROBE_SHARED IRQF_SHARED用來表示該interrupt action descriptor是允許和其他device共享一個interrupt line(IRQ number),但是實際上是否能夠share還是需要其他條件:例如觸發方式必須相同。有些驅動程序可能有這樣的調用場景:我只是想scan一個irq table,看看哪一個是OK的,這時候,如果即便是不能和其他的驅動程序share這個interrupt line,我也沒有關系,我就是想scan看看情況。這時候,caller其實可以預見sharing mismatche的發生,因此,不需要內核打印“Flags mismatch irq……“這樣冗余的信息
IRQF_PERCPU 在SMP的架構下,中斷有兩種mode,一種中斷是在所有processor之間共享的,也就是global的,一旦中斷產生,interrupt controller可以把這個中斷送達多個處理器。當然,在具體實現的時候不會同時將中斷送達多個CPU,一般是軟件和硬件協同處理,將中斷送達一個CPU處理。但是一段時間內產生的中斷可以平均(或者按照既定的策略)分配到一組CPU上。這種interrupt mode下,interrupt controller針對該中斷的operational register是global的,所有的CPU看到的都是一套寄存器,一旦一個CPU ack了該中斷,那么其他的CPU看到的該interupt source的狀態也是已經ack的狀態。
和global對應的就是per cpu interrupt了,對于這種interrupt,不是processor之間共享的,而是特定屬于一個CPU的。例如GIC中interrupt ID等于30的中斷就是per cpu的(這個中斷event被用于各個CPU的local timer),這個中斷號雖然只有一個,但是,實際上控制該interrupt ID的寄存器有n組(如果系統中有n個processor),每個CPU看到的是不同的控制寄存器。在具體實現中,這些寄存器組有兩種形態,一種是banked,所有CPU操作同樣的寄存器地址,硬件系統會根據訪問的cpu定向到不同的寄存器,另外一種是non banked,也就是說,對于該interrupt source,每個cpu都有自己獨特的訪問地址。
IRQF_NOBALANCING 這也是和multi-processor相關的一個flag。對于那些可以在多個CPU之間共享的中斷,具體送達哪一個processor是有策略的,我們可以在多個CPU之間進行平衡。如果你不想讓你的中斷參與到irq balancing的過程中那么就設定這個flag
IRQF_IRQPOLL
IRQF_ONESHOT one shot本身的意思的只有一次的,結合到中斷這個場景,則表示中斷是一次性觸發的,不能嵌套。對于primary handler,當然是不會嵌套,但是對于threaded interrupt handler,我們有兩種選擇,一種是mask該interrupt source,另外一種是unmask該interrupt source。一旦mask住該interrupt source,那么該interrupt source的中斷在整個threaded interrupt handler處理過程中都是不會再次觸發的,也就是one shot了。這種handler不需要考慮重入問題。
具體是否要設定one shot的flag是和硬件系統有關的,我們舉一個例子,比如電池驅動,電池里面有一個電量計,是使用HDQ協議進行通信的,電池驅動會注冊一個threaded interrupt handler,在這個handler中,會通過HDQ協議和電量計進行通信。對于這個handler,通過HDQ進行通信是需要一個完整的HDQ交互過程,如果中間被打斷,整個通信過程會出問題,因此,這個handler就必須是one shot的。
IRQF_NO_SUSPEND 這個flag比較好理解,就是說在系統suspend的時候,不用disable這個中斷,如果disable,可能會導致系統不能正常的resume。
IRQF_FORCE_RESUME 在系統resume的過程中,強制必須進行enable的動作,即便是設定了IRQF_NO_SUSPEND這個flag。這是和特定的硬件行為相關的。
IRQF_NO_THREAD 有些low level的interrupt是不能線程化的(例如系統timer的中斷),這個flag就是起這個作用的。另外,有些級聯的interrupt controller對應的IRQ也是不能線程化的(例如secondary GIC對應的IRQ),它的線程化可能會影響一大批附屬于該interrupt controller的外設的中斷響應延遲。
IRQF_EARLY_RESUME
IRQF_TIMER
如何判斷哪個按鍵觸發的中斷
如何判斷怎么觸發(上升沿,下降沿,高電平,低電平)中斷的
使用管腳的輸入功能(配置成輸入功能)判斷電平
重新配置為外部中斷功能
如何去抖動
Linux內核中中斷處理程序的一般結構
頂半部:完成盡可能少的緊急功能,往往是簡單的讀取寄存器,清除中斷標志。登記底半部。
底半部:完成中斷處理程序中絕大部分工作,通常這部分都比較耗時。
1)軟中斷
2)tasklet(利用了軟中斷的機制)
struct tasklet_struct{
Void(*func)(unsigned long);//底半部完成函數
Unsigned long data;
。。。
}
tasklet_scedule(。。。)//完成底半部的登記
DECLARE_TASKLET 定義并初始化
3)工作者隊列
Struct work_struct{
}
INIT_WAORK//初始化work
Schrdiule_work //登記work
flush_work//
Tasklet和工作者隊列有啥區別
Asklet中的func函數工作于中斷上下文,func不允許睡眠的,work中的func工作于進程上下文
IO與內存:
統一編址(ARM):
MOV
獨立編址(X86):
MOV R0 [0X100]
IN/OUT 0X100
ARM PowePC MPIS 都是用統一編址
X86使用獨立編址
Linux編程使用到的都是虛擬地址,驅動開發時,從芯片手冊得到的物理地址,需要轉換成虛擬地址后再使用
/*包含初始化宏定義的頭文件,代碼中的module_init和module_exit在此文件中*/
#include <linux/init.h>
/*包含初始化加載模塊的頭文件,代碼中的MODULE_LICENSE在此頭文件中*/
#include <linux/module.h>
/*定義module_param module_param_array的頭文件*/
#include <linux/moduleparam.h>
/*定義module_param module_param_array中perm的頭文件*/
#include <linux/stat.h>
/*三個字符設備函數*/
#include <linux/fs.h>
/*MKDEV轉換設備號數據類型的宏定義*/
#include <linux/kdev_t.h>
/*定義字符設備的結構體*/
#include <linux/cdev.h>
/*分配內存空間函數頭文件*/
#include <linux/slab.h>
/*包含函數device_creatchar_driver_ledse 結構體class等頭文件*/
#include <linux/device.h>
#include <linux/wait.h>
/*自定義頭文件*/
#include "char_driver_leds.h"
#include <linux/sched.h>
//#include <stdio.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
/*Linux中申請GPIO的頭文件*/
#include <linux/gpio.h>
/*三星平臺的GPIO配置函數頭文件*/
/*三星平臺EXYNOS系列平臺,GPIO配置參數宏定義頭文件*/
#include <plat/gpio-cfg.h>
/*三星平臺4412平臺,GPIO宏定義頭文件*/
#include <mach/gpio-exynos4.h>
MODULE_LICENSE("Dual BSD/GPL");
/*聲明是開源的,沒有內核版本限制*/
MODULE_AUTHOR("songmao");
/*聲明作者*/
static int led_gpios[] = {
EXYNOS4_GPL2(0),EXYNOS4_GPK1(1),
};
#define LED_NUM ARRAY_SIZE(led_gpios)
static int irq_gpio[] ={
EXYNOS4_GPX1(1),EXYNOS4_GPX1(2),
EXYNOS4_GPX1(0),EXYNOS4_GPX1(1)
};
#define IRQ_NUM ARRAY_SIZE(irq_gpio)
int led_num[4];
int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;
/*輸入主設備號*/
module_param(numdev_major,int,S_IRUSR);
/*輸入次設備號*/
module_param(numdev_minor,int,S_IRUSR);
static struct class *myclass;
struct reg_dev *my_devices;
/*打開操作*/
static int chardevnode_open(struct inode *inode, struct file *file)
{
struct reg_dev *reg_devp =NULL;
reg_devp =container_of(inode->i_cdev,struct reg_dev,cdev);
file->private_data=reg_devp;
printk(KERN_EMERG "chardevnode_open is success!\n");
/* if(!atomic_dec_and_test(&(reg_devp->atc)))
{
printk(KERN_ERR "atomic:device can open only once!");
atomic_inc(&(reg_devp->atc));
return -EBUSY;
}*/
spin_lock(&(reg_devp->lock));
if(OPEN_NUM<=reg_devp->open_num){
spin_unlock(&(reg_devp->lock));
printk(KERN_ERR "atomic:device can open over num!");
return -EBUSY;
}
reg_devp->open_num++;
spin_unlock(&(reg_devp->lock));
return 0;
}
/*關閉操作*/
static int chardevnode_release(struct inode *inode, struct file *file)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
printk(KERN_EMERG "chardevnode_release is success!\n");
//atomic_inc(&(reg_devp->atc));
/*spin_lock(&(reg_devp->lock));
reg_devp->open_num--;
spin_unlock(&(reg_devp->lock));*/
up(&(reg_devp->sem_open));
return 0;
}
/*IO操作*/
static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
switch(cmd)
{
case 0:
case 1:
if (arg > LED_NUM) {
return -EINVAL;
}
gpio_set_value(led_gpios[arg], cmd);
break;
default:
return -EINVAL;
}
printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %d \n",cmd,arg);
up(&(reg_devp->sem_read));
up(&(reg_devp->sem_write));
return 0;
}
ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
//down_interruptible(&(reg_devp->sem_read));
wait_event_interruptible(reg_devp->wqh,0!=reg_devp->led);
reg_devp->led=0;
printk(KERN_INFO"chardevnode_read success");
return 0;
}
ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
//down_interruptible(&(reg_devp->sem_write));
reg_devp->led=1;
wake_up_interruptible(&(reg_devp->wqh));
printk(KERN_INFO"chardevnode_write success");
return 0;
}
loff_t chardevnode_llseek(struct file *file, loff_t offset, int ence){
return 0;
}
struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = chardevnode_open,
.release = chardevnode_release,
.unlocked_ioctl = chardevnode_ioctl,
.read = chardevnode_read,
.write = chardevnode_write,
.llseek = chardevnode_llseek,
};
static void chardeviced_cdd_work_func(struct work_struct *work)
{
int i = 0,j,ledss;
struct reg_dev *pcdevp = container_of(work,struct reg_dev,cdd_work);
printk(KERN_INFO" CharDeviceDriver: Entry cdd_work");
printk(KERN_ALERT"CharDeviceDriver:CDD work name:%s \n",pcdevp->cdd_work_name);
for(i=0;i<4;i++)
{
if(led_num[i]>0)
{
j=i%2;
if(i>1)
ledss=1;
else
ledss=0;
gpio_set_value(led_gpios[j],ledss);
}
}
static void chardevicedriver_cdd_delayed_work_func(struct work_struct *work)
{
struct reg_dev *pcdevp = container_of(work,struct reg_dev,cdd_delayed_work);
printk(KERN_INFO" CharDeviceDriver: Entry chardeviceddriver_cdd_delayed_work_func");
printk(KERN_ALERT"CharDeviceDriver:CDD delayed work namename:%s \n",pcdevp->cdd_delayed_work_name);
}
static irq_handler_t chardevnode_irq(int irq,void *dev_id)
{
struct reg_dev *reg_devp = (struct reg_dev *)dev_id;
for(i=0;i<IRQ_NUM;i++){
s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0));
led_num[i] = gpio_get_value(irq_gpio[i]);
s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0));
}
tasklet_schedule(&(my_devices));
schedule_work(&(reg_devp->cdd_work));
queue_delayed_work(reg_devp->cdd_workqueue,&(reg_devp->cdd_delayed_work),3*HZ);
return IRQ_HANDLED;
}
/*設備注冊到系統*/
static void reg_init_cdev(struct reg_dev *dev,int index){
int err;
int devno = MKDEV(numdev_major,numdev_minor+index);
/*數據初始化*/
cdev_init(&dev->cdev,&my_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &my_fops;
/*注冊到系統*/
err = cdev_add(&dev->cdev,devno,1);
if(err){
printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err);
}
else{
printk(KERN_EMERG "cdev_add %d is success!\n",numdev_minor+index);
}
}
static int gpio_init(void){
int i=0,ret;
for(i=0;i<LED_NUM;i++){
ret = gpio_request(led_gpios[i], "LED");
if (ret) {
printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,i,ret);
return -1;
}
else{
s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
gpio_set_value(led_gpios[i], 1);
}
}
for(i=0;i<IRQ_NUM;i++){
ret = gpio_request(irq_gpio[i], "IRQ");
if (ret) {
printk("%s: request GPIO %d for IRQ failed, ret = %d\n", DEVICE_NAME,i,ret);
return -1;
}
else{
s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0F));
}
}
ret = request_irq(IRQ_EINT(9),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt0",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(9)");
goto FAIL_IRQ_EINT;
}
ret = request_irq(IRQ_EINT(10),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt1",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(10)");
}
ret = request_irq(IRQ_EINT(17),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt2",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(17)");
}
ret = request_irq(IRQ_EINT(18),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt3",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(17)");
}
printk(KERN_INFO"Chardevnode_irq:Success is IRQ_EINT");
return 0;
FAIL_IRQ_EINT:
free_irq(IRQ_EINT(9),my_devices);
free_irq(IRQ_EINT(10),my_devices);
free_irq(IRQ_EINT(17),my_devices);
free_irq(IRQ_EINT(18),my_devices);
for(i=0;i<LED_NUM;i++)
gpio_free(led_gpios[i]);
for(i=0;i<IRQ_NUM;i++){
ret = gpio_free(irq_gpio[i]);
}
static int __init scdev_init(void)
{
int ret = 0,i;
dev_t num_dev;
printk(KERN_EMERG "numdev_major is %d!\n",numdev_major);
printk(KERN_EMERG "numdev_minor is %d!\n",numdev_minor);
if(numdev_major){
num_dev = MKDEV(numdev_major,numdev_minor);
ret = register_chrdev_region(num_dev,DEVICE_MINOR_NUM,DEVICE_NAME);
}
else{
/*動態注冊設備號*/
ret = alloc_chrdev_region(&num_dev,numdev_minor,DEVICE_MINOR_NUM,DEVICE_NAME);
/*獲得主設備號*/
numdev_major = MAJOR(num_dev);
printk(KERN_EMERG "adev_region req %d !\n",numdev_major);
}
if(ret<0){
printk(KERN_EMERG "register_chrdev_region req %d is failed!\n",numdev_major);
}
myclass = class_create(THIS_MODULE,DEVICE_NAME);
my_devices = kmalloc(DEVICE_MINOR_NUM * sizeof(struct reg_dev),GFP_KERNEL);
if(!my_devices){
ret = -ENOMEM;
goto fail;
}
memset(my_devices,0,DEVICE_MINOR_NUM * sizeof(struct reg_dev));
/*設備初始化*/
for(i=0;i<DEVICE_MINOR_NUM;i++){
my_devices[i].data = kmalloc(REGDEV_SIZE,GFP_KERNEL);
memset(my_devices[i].data,0,REGDEV_SIZE);
/*設備注冊到系統*/
reg_init_cdev(&my_devices[i],i);
/*創建設備節點*/
device_create(myclass,NULL,MKDEV(numdev_major,numdev_minor+i),NULL,DEVICE_NAME"%d",i);
//atomic_inc(&(my_devices[i].atc));
/*spin_lock_init(&(my_devices[i].lock));*/
my_devices[i].open_num=0;
/*sema_init(&(my_devices[i].sem_open,2);
sema_init(&(my_devices[i].sem_read,1);
sema_init(&(my_devices[i].sem_write,0);*/
//init_waitqueue_head(&(my_devices[i].wqh));
//strcpy( &(my_devices[i].cdd_work_name),"cdd_work_name");
}
ret = gpio_init();
if(ret){
printk(KERN_EMERG "gpio_init failed!\n");
}
INIT_WORK(&(my_devices->cdd_work,chardeviced_cdd_work_func,NULL);
//my_devices[i].cdd_workqueue;
create_workqueue("cdd_workqueue");
if(IS_ERR(my_devices->cdd_workqueue)){
print(KERN_ERR"ChardeviceDriver: Failure to create work_queue!\n");
ret=PTR_ERR(my_devices->cdd_workqueue);
goto failure_creat_work;
}
printk(KERN_INFO"ChardeviceDriver:Success to create work_queue!\n");
strcpy( &(my_devices->cdd_work_name),"cdd_delayed_work_name");
INIT_DELAYED_WORK(my_devices->cdd_delayed_work,chardevicedriver_cdd_delayed_work_func);
printk(KERN_EMERG "scdev_init!\n");
/*打印信息,KERN_EMERG表示緊急信息*/
return 0;
failure_creat_work:
free_irq(IRQ_EINT(9),my_devices);
free_irq(IRQ_EINT(10),my_devices);
free_irq(IRQ_EINT(17),my_devices);
free_irq(IRQ_EINT(18),my_devices);
for(i=0;i<LED_NUM;i++)
gpio_free(led_gpios[i])
for(i=0;i<IRQ_NUM;i++){
ret = gpio_free(irq_gpio[i]);
fail:
/*注銷設備號*/
unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
printk(KERN_EMERG "kmalloc is fail!\n");
return ret;
}
static void __exit scdev_exit(void)
{
int i;
printk(KERN_EMERG "scdev_exit!\n");
free_irq(IRQ_EINT(9),my_devices);
free_irq(IRQ_EINT(10),my_devices);
free_irq(IRQ_EINT(17),my_devices);
free_irq(IRQ_EINT(18),my_devices);
/*除去字符設備*/
for(i=0;i<DEVICE_MINOR_NUM;i++){
cdev_del(&(my_devices[i].cdev));
/*摧毀設備節點函數d*/
device_destroy(myclass,MKDEV(numdev_major,numdev_minor+i));
}
/*釋放設備class*/
class_destroy(myclass);
/*釋放內存*/
kfree(my_devices);
/*釋放GPIO*/
for(i=0;i<LED_NUM;i++){
gpio_free(led_gpios[i]);
}
unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
}
module_init(scdev_init);
/*初始化函數*/
module_exit(scdev_exit);
/*卸載函數*/
1)申請I/O內存 #include <asm/io.h>
request_mem_region(start,n,name)物理地址
Start 待申請的起始地址(物理地址)
n,從start開始地址的字節數
Name,
2)映射ioremap(phys,addr,size)
phys_addr,等同于1)中的start
size等同于n
return value:映射后的虛擬地址 vir_addr
如:vir_addr=ioremap(0xE0200008,4)
ioread8 ioread16 ioread32
ioreite8 iowrite16 iowrite32
((volatile unsigned int *)vir_addr) =0xXXXX;
volatile
寄存器變量,硬件地址,中斷或者多線程中共享的全局變量,防止編譯器錯誤優化
4)取消映射
ioumap(*addr)
addr, ioremap返回的虛擬地址vir_addr
5)釋放I/O內存
release_mem_region(start,n)
回顧:內核競態與并發
什么情況下會產生競態
1)SMP
2)單CPU支持任務搶占
3)中斷和進程之間
4)中斷和中斷之間
解決競態的方法
1)中斷屏蔽
2)原子操作
位原子操作
×××原子操作 atomic{ int。。。。}
3)自旋鎖
優點:一旦可以獲取鎖,立即獲取
缺點:長時間獲取鎖不成功,會消耗CPU資源
它所保護的臨界資源(代碼段)通常比較短
4)信號量
down(。。。)會導致睡眠
等等看前一章中
自旋鎖只允許一個持有者,信號量可以有多個持有者
信號量保護的臨界資源(代碼段)通常比較長
2,等待隊列 #include<linux/sched.h>
Read() recv()
Wait_event_interrptible //阻塞
Wake_up_interruptible //喚醒
阻塞/非阻塞
實際上,應用程序并不關心驅動里面read/write具體實現,只管調用并獲取返回值
如果設備沒有準備好數據給應用程序讀或者沒有準備好接受用戶程序寫,驅動程序應當阻塞進程,使它進入睡眠,直到請求可以得到滿足
阻塞讀
在阻塞型驅動程序中,如果進程調用read設備操作,但是設備沒有數據或數據不足,進程應該被阻塞,當有新數據到達后,喚醒被阻塞進程
阻塞寫
在阻塞型驅動程序中,如果進程調用write設備操作,但是設備沒有足夠的空間供其寫入,進程該被阻塞,但設備中的數據讀走后,緩沖區中空出部分空間,應該喚醒被阻塞進程
應用程序非阻塞讀
阻塞方式是文件讀寫操作的默認方式
應用程序可以通過使用O_NONBLOCK標志來人為的設置讀寫操作為非阻塞方式
定義在<asm-generic/fcntl.h>
如果設置了O_NONBLOCK標志,read和write的處理行為相同
if(0==pcdevp->)&&(O_NONBLICK&file->f_flags){
printk(KERN_ALET “Char......”);
return -EAGAIN;
---------------------------------------------------------------
多路監聽偵測 select #include <linux/poll.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
Nfds 要監聽的文件描述符最大值+1
Readfds,要監聽的讀文件描述符集合
Writefds要監聽的寫文件描述符集合
Exceptfds 要監聽的異常文件描述集合
Timeout 監聽的超時時間
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
內核中要實現的函數
Unsigned int(*poll)()
Fd_set rfds;
Struct timeval tv
先
FD_ZERO()
加入集合FD_SET()
POLLIN
有數據可讀。
POLLRDNORM
有普通數據可讀。
POLLRDBAND
有優先數據可讀。
POLLPRI
有緊迫數據可讀。
POLLOUT
寫數據不會導致阻塞。
POLLWRNORM
寫普通數據不會導致阻塞。
POLLWRBAND
寫優先數據不會導致阻塞。
POLLMSG
SIGPOLL 消息可用。
此外,revents域中還可能返回下列事件:
POLLER
指定的文件描述符發生錯誤。
POLLHUP
指定的文件描述符掛起事件。
POLLNVAL
指定的文件描述符非法。
這些事件在events域中無意義,因為它們在合適的時候總是會從revents中返回。使用poll()和select()不一樣,你不需要顯式地請求異常情況報告。
POLLIN | POLLPRI等價于select()的讀事件,POLLOUT |POLLWRBAND等價于select()的寫事件。POLLIN等價于POLLRDNORM |POLLRDBAND,而POLLOUT則等價于POLLWRNORM。
中斷
驅動需要包含 #include <linux/gpio.h>
判斷一個IO是否合法:int gpio_is_valid(int number);
設置GPIO的方向,如果是輸出同時設置電平:
/* set as input or output, returning 0 or negative errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
獲取輸入引腳的電平:
/* GPIO INPUT: return zero or nonzero */
int gpio_get_value(unsigned gpio);
/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);
int gpio_cansleep(unsigned gpio);
To access such GPIOs, a different set of accessors is defined:
/* GPIO INPUT: return zero or nonzero, might sleep */
int gpio_get_value_cansleep(unsigned gpio);
/* GPIO OUTPUT, might sleep */
void gpio_set_value_cansleep(unsigned gpio, int value);
獲取一個GPIO并聲明標簽:
/* request GPIO, returning 0 or negative errno.
* non-null labels may be useful for diagnostics.
*/
int gpio_request(unsigned gpio, const char *label);
/* release previously-claimed GPIO */
void gpio_free(unsigned gpio);
將GPIO映射為IRQ中斷:
/* map GPIO numbers to IRQ numbers */
int gpio_to_irq(unsigned gpio);
/* map IRQ numbers to GPIO numbers (avoid using this) */
int irq_to_gpio(unsigned irq);
設置GPIO IRQ中斷類型:
if (!sw->both_edges) {
if (gpio_get_value(sw->gpio))
set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_FALLING);
else
set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_RISING);
在驅動中使用延時函數mdelay,需要包含<linux/delay.h>文件
對于硬件產生的變化,需要CPI進行處理,通過情況下CPU獲取該變化的方式有種
1)輪詢
2)中斷 #include <linux/interrup>
Request irq(...)
注冊中斷request_irq(...)
Irq 要注冊的中斷號 arm/mach-xxxxx/include/mach/irqs.h
Gpio_to_irq()
handle 中斷處理函數
Irqreturn_t xxxxx(int irq,void *dev_id)
Irqflags 中斷標志
Cat /proc/interrupts
IRQF_SHARED
flag定義 描述
IRQF_TRIGGER_XXX 描述該interrupt line觸發類型的flag
IRQF_DISABLED 首先要說明的是這是一個廢棄的flag,在新的內核中,該flag沒有任何的作用了。具體可以參考:Disabling IRQF_DISABLED
舊的內核(2.6.35版本之前)認為有兩種interrupt handler:slow handler和fast handle。在request irq的時候,對于fast handler,需要傳遞IRQF_DISABLED的參數,確保其中斷處理過程中是關閉CPU的中斷,因為是fast handler,執行很快,即便是關閉CPU中斷不會影響系統的性能。但是,并不是每一種外設中斷的handler都是那么快(例如磁盤),因此就有 slow handler的概念,說明其在中斷處理過程中會耗時比較長。對于這種情況,在執行interrupt handler的時候不能關閉CPU中斷,否則對系統的performance會有影響。
新的內核已經不區分slow handler和fast handle,都是fast handler,都是需要關閉CPU中斷的,那些需要后續處理的內容推到threaded interrupt handler中去執行。
IRQF_SHARED 這是flag用來描述一個interrupt line是否允許在多個設備中共享。如果中斷控制器可以支持足夠多的interrupt source,那么在兩個外設間共享一個interrupt request line是不推薦的,畢竟有一些額外的開銷(發生中斷的時候要逐個詢問是不是你的中斷,軟件上就是遍歷action list),因此外設的irq handler中最好是一開始就啟動判斷,看看是否是自己的中斷,如果不是,返回IRQ_NONE,表示這個中斷不歸我管。 早期PC時代,使用8259中斷控制器,級聯的8259最多支持15個外部中斷,但是PC外設那么多,因此需要irq share。現在,ARM平臺上的系統設計很少會采用外設共享IRQ方式,畢竟一般ARM SOC提供的有中斷功能的GPIO非常的多,足夠用的。 當然,如果確實需要兩個外設共享IRQ,那也只能如此設計了。對于HW,中斷控制器的一個interrupt source的引腳要接到兩個外設的interrupt request line上,怎么接?直接連接可以嗎?當然不行,對于低電平觸發的情況,我們可以考慮用與門連接中斷控制器和外設。
IRQF_PROBE_SHARED IRQF_SHARED用來表示該interrupt action descriptor是允許和其他device共享一個interrupt line(IRQ number),但是實際上是否能夠share還是需要其他條件:例如觸發方式必須相同。有些驅動程序可能有這樣的調用場景:我只是想scan一個irq table,看看哪一個是OK的,這時候,如果即便是不能和其他的驅動程序share這個interrupt line,我也沒有關系,我就是想scan看看情況。這時候,caller其實可以預見sharing mismatche的發生,因此,不需要內核打印“Flags mismatch irq……“這樣冗余的信息
IRQF_PERCPU 在SMP的架構下,中斷有兩種mode,一種中斷是在所有processor之間共享的,也就是global的,一旦中斷產生,interrupt controller可以把這個中斷送達多個處理器。當然,在具體實現的時候不會同時將中斷送達多個CPU,一般是軟件和硬件協同處理,將中斷送達一個CPU處理。但是一段時間內產生的中斷可以平均(或者按照既定的策略)分配到一組CPU上。這種interrupt mode下,interrupt controller針對該中斷的operational register是global的,所有的CPU看到的都是一套寄存器,一旦一個CPU ack了該中斷,那么其他的CPU看到的該interupt source的狀態也是已經ack的狀態。
和global對應的就是per cpu interrupt了,對于這種interrupt,不是processor之間共享的,而是特定屬于一個CPU的。例如GIC中interrupt ID等于30的中斷就是per cpu的(這個中斷event被用于各個CPU的local timer),這個中斷號雖然只有一個,但是,實際上控制該interrupt ID的寄存器有n組(如果系統中有n個processor),每個CPU看到的是不同的控制寄存器。在具體實現中,這些寄存器組有兩種形態,一種是banked,所有CPU操作同樣的寄存器地址,硬件系統會根據訪問的cpu定向到不同的寄存器,另外一種是non banked,也就是說,對于該interrupt source,每個cpu都有自己獨特的訪問地址。
IRQF_NOBALANCING 這也是和multi-processor相關的一個flag。對于那些可以在多個CPU之間共享的中斷,具體送達哪一個processor是有策略的,我們可以在多個CPU之間進行平衡。如果你不想讓你的中斷參與到irq balancing的過程中那么就設定這個flag
IRQF_IRQPOLL
IRQF_ONESHOT one shot本身的意思的只有一次的,結合到中斷這個場景,則表示中斷是一次性觸發的,不能嵌套。對于primary handler,當然是不會嵌套,但是對于threaded interrupt handler,我們有兩種選擇,一種是mask該interrupt source,另外一種是unmask該interrupt source。一旦mask住該interrupt source,那么該interrupt source的中斷在整個threaded interrupt handler處理過程中都是不會再次觸發的,也就是one shot了。這種handler不需要考慮重入問題。
具體是否要設定one shot的flag是和硬件系統有關的,我們舉一個例子,比如電池驅動,電池里面有一個電量計,是使用HDQ協議進行通信的,電池驅動會注冊一個threaded interrupt handler,在這個handler中,會通過HDQ協議和電量計進行通信。對于這個handler,通過HDQ進行通信是需要一個完整的HDQ交互過程,如果中間被打斷,整個通信過程會出問題,因此,這個handler就必須是one shot的。
IRQF_NO_SUSPEND 這個flag比較好理解,就是說在系統suspend的時候,不用disable這個中斷,如果disable,可能會導致系統不能正常的resume。
IRQF_FORCE_RESUME 在系統resume的過程中,強制必須進行enable的動作,即便是設定了IRQF_NO_SUSPEND這個flag。這是和特定的硬件行為相關的。
IRQF_NO_THREAD 有些low level的interrupt是不能線程化的(例如系統timer的中斷),這個flag就是起這個作用的。另外,有些級聯的interrupt controller對應的IRQ也是不能線程化的(例如secondary GIC對應的IRQ),它的線程化可能會影響一大批附屬于該interrupt controller的外設的中斷響應延遲。
IRQF_EARLY_RESUME
IRQF_TIMER
如何判斷哪個按鍵觸發的中斷
如何判斷怎么觸發(上升沿,下降沿,高電平,低電平)中斷的
使用管腳的輸入功能(配置成輸入功能)判斷電平
重新配置為外部中斷功能
如何去抖動
Linux內核中中斷處理程序的一般結構
頂半部:完成盡可能少的緊急功能,往往是簡單的讀取寄存器,清除中斷標志。登記底半部。
底半部:完成中斷處理程序中絕大部分工作,通常這部分都比較耗時。
1)軟中斷
2)tasklet(利用了軟中斷的機制)
struct tasklet_struct{
Void(*func)(unsigned long);//底半部完成函數
Unsigned long data;
。。。
}
tasklet_scedule(。。。)//完成底半部的登記
DECLARE_TASKLET 定義并初始化
3)工作者隊列
Struct work_struct{
}
INIT_WAORK//初始化work
Schrdiule_work //登記work
flush_work//
Tasklet和工作者隊列有啥區別
Asklet中的func函數工作于中斷上下文,func不允許睡眠的,work中的func工作于進程上下文
IO與內存:
統一編址(ARM):
MOV
獨立編址(X86):
MOV R0 [0X100]
IN/OUT 0X100
ARM PowePC MPIS 都是用統一編址
X86使用獨立編址
Linux編程使用到的都是虛擬地址,驅動開發時,從芯片手冊得到的物理地址,需要轉換成虛擬地址后再使用
/*包含初始化宏定義的頭文件,代碼中的module_init和module_exit在此文件中*/
#include <linux/init.h>
/*包含初始化加載模塊的頭文件,代碼中的MODULE_LICENSE在此頭文件中*/
#include <linux/module.h>
/*定義module_param module_param_array的頭文件*/
#include <linux/moduleparam.h>
/*定義module_param module_param_array中perm的頭文件*/
#include <linux/stat.h>
/*三個字符設備函數*/
#include <linux/fs.h>
/*MKDEV轉換設備號數據類型的宏定義*/
#include <linux/kdev_t.h>
/*定義字符設備的結構體*/
#include <linux/cdev.h>
/*分配內存空間函數頭文件*/
#include <linux/slab.h>
/*包含函數device_creatchar_driver_ledse 結構體class等頭文件*/
#include <linux/device.h>
#include <linux/wait.h>
/*自定義頭文件*/
#include "char_driver_leds.h"
#include <linux/sched.h>
//#include <stdio.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
/*Linux中申請GPIO的頭文件*/
#include <linux/gpio.h>
/*三星平臺的GPIO配置函數頭文件*/
/*三星平臺EXYNOS系列平臺,GPIO配置參數宏定義頭文件*/
#include <plat/gpio-cfg.h>
/*三星平臺4412平臺,GPIO宏定義頭文件*/
#include <mach/gpio-exynos4.h>
MODULE_LICENSE("Dual BSD/GPL");
/*聲明是開源的,沒有內核版本限制*/
MODULE_AUTHOR("songmao");
/*聲明作者*/
static int led_gpios[] = {
EXYNOS4_GPL2(0),EXYNOS4_GPK1(1),
};
#define LED_NUM ARRAY_SIZE(led_gpios)
static int irq_gpio[] ={
EXYNOS4_GPX1(1),EXYNOS4_GPX1(2),
EXYNOS4_GPX1(0),EXYNOS4_GPX1(1)
};
#define IRQ_NUM ARRAY_SIZE(irq_gpio)
int led_num[4];
int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;
/*輸入主設備號*/
module_param(numdev_major,int,S_IRUSR);
/*輸入次設備號*/
module_param(numdev_minor,int,S_IRUSR);
static struct class *myclass;
struct reg_dev *my_devices;
/*打開操作*/
static int chardevnode_open(struct inode *inode, struct file *file)
{
struct reg_dev *reg_devp =NULL;
reg_devp =container_of(inode->i_cdev,struct reg_dev,cdev);
file->private_data=reg_devp;
printk(KERN_EMERG "chardevnode_open is success!\n");
/* if(!atomic_dec_and_test(&(reg_devp->atc)))
{
printk(KERN_ERR "atomic:device can open only once!");
atomic_inc(&(reg_devp->atc));
return -EBUSY;
}*/
spin_lock(&(reg_devp->lock));
if(OPEN_NUM<=reg_devp->open_num){
spin_unlock(&(reg_devp->lock));
printk(KERN_ERR "atomic:device can open over num!");
return -EBUSY;
}
reg_devp->open_num++;
spin_unlock(&(reg_devp->lock));
return 0;
}
/*關閉操作*/
static int chardevnode_release(struct inode *inode, struct file *file)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
printk(KERN_EMERG "chardevnode_release is success!\n");
//atomic_inc(&(reg_devp->atc));
/*spin_lock(&(reg_devp->lock));
reg_devp->open_num--;
spin_unlock(&(reg_devp->lock));*/
up(&(reg_devp->sem_open));
return 0;
}
/*IO操作*/
static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
switch(cmd)
{
case 0:
case 1:
if (arg > LED_NUM) {
return -EINVAL;
}
gpio_set_value(led_gpios[arg], cmd);
break;
default:
return -EINVAL;
}
printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %d \n",cmd,arg);
up(&(reg_devp->sem_read));
up(&(reg_devp->sem_write));
return 0;
}
ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
//down_interruptible(&(reg_devp->sem_read));
wait_event_interruptible(reg_devp->wqh,0!=reg_devp->led);
reg_devp->led=0;
printk(KERN_INFO"chardevnode_read success");
return 0;
}
ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
//down_interruptible(&(reg_devp->sem_write));
reg_devp->led=1;
wake_up_interruptible(&(reg_devp->wqh));
printk(KERN_INFO"chardevnode_write success");
return 0;
}
loff_t chardevnode_llseek(struct file *file, loff_t offset, int ence){
return 0;
}
struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = chardevnode_open,
.release = chardevnode_release,
.unlocked_ioctl = chardevnode_ioctl,
.read = chardevnode_read,
.write = chardevnode_write,
.llseek = chardevnode_llseek,
};
static void chardeviced_cdd_work_func(struct work_struct *work)
{
int i = 0,j,ledss;
struct reg_dev *pcdevp = container_of(work,struct reg_dev,cdd_work);
printk(KERN_INFO" CharDeviceDriver: Entry cdd_work");
printk(KERN_ALERT"CharDeviceDriver:CDD work name:%s \n",pcdevp->cdd_work_name);
for(i=0;i<4;i++)
{
if(led_num[i]>0)
{
j=i%2;
if(i>1)
ledss=1;
else
ledss=0;
gpio_set_value(led_gpios[j],ledss);
}
}
static void chardevicedriver_cdd_delayed_work_func(struct work_struct *work)
{
struct reg_dev *pcdevp = container_of(work,struct reg_dev,cdd_delayed_work);
printk(KERN_INFO" CharDeviceDriver: Entry chardeviceddriver_cdd_delayed_work_func");
printk(KERN_ALERT"CharDeviceDriver:CDD delayed work namename:%s \n",pcdevp->cdd_delayed_work_name);
}
static irq_handler_t chardevnode_irq(int irq,void *dev_id)
{
struct reg_dev *reg_devp = (struct reg_dev *)dev_id;
for(i=0;i<IRQ_NUM;i++){
s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0));
led_num[i] = gpio_get_value(irq_gpio[i]);
s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0));
}
tasklet_schedule(&(my_devices));
schedule_work(&(reg_devp->cdd_work));
queue_delayed_work(reg_devp->cdd_workqueue,&(reg_devp->cdd_delayed_work),3*HZ);
return IRQ_HANDLED;
}
/*設備注冊到系統*/
static void reg_init_cdev(struct reg_dev *dev,int index){
int err;
int devno = MKDEV(numdev_major,numdev_minor+index);
/*數據初始化*/
cdev_init(&dev->cdev,&my_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &my_fops;
/*注冊到系統*/
err = cdev_add(&dev->cdev,devno,1);
if(err){
printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err);
}
else{
printk(KERN_EMERG "cdev_add %d is success!\n",numdev_minor+index);
}
}
static int gpio_init(void){
int i=0,ret;
for(i=0;i<LED_NUM;i++){
ret = gpio_request(led_gpios[i], "LED");
if (ret) {
printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,i,ret);
return -1;
}
else{
s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
gpio_set_value(led_gpios[i], 1);
}
}
for(i=0;i<IRQ_NUM;i++){
ret = gpio_request(irq_gpio[i], "IRQ");
if (ret) {
printk("%s: request GPIO %d for IRQ failed, ret = %d\n", DEVICE_NAME,i,ret);
return -1;
}
else{
s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0F));
}
}
ret = request_irq(IRQ_EINT(9),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt0",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(9)");
goto FAIL_IRQ_EINT;
}
ret = request_irq(IRQ_EINT(10),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt1",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(10)");
}
ret = request_irq(IRQ_EINT(17),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt2",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(17)");
}
ret = request_irq(IRQ_EINT(18),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt3",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(17)");
}
printk(KERN_INFO"Chardevnode_irq:Success is IRQ_EINT");
return 0;
FAIL_IRQ_EINT:
free_irq(IRQ_EINT(9),my_devices);
free_irq(IRQ_EINT(10),my_devices);
free_irq(IRQ_EINT(17),my_devices);
free_irq(IRQ_EINT(18),my_devices);
for(i=0;i<LED_NUM;i++)
gpio_free(led_gpios[i]);
for(i=0;i<IRQ_NUM;i++){
ret = gpio_free(irq_gpio[i]);
}
static int __init scdev_init(void)
{
int ret = 0,i;
dev_t num_dev;
printk(KERN_EMERG "numdev_major is %d!\n",numdev_major);
printk(KERN_EMERG "numdev_minor is %d!\n",numdev_minor);
if(numdev_major){
num_dev = MKDEV(numdev_major,numdev_minor);
ret = register_chrdev_region(num_dev,DEVICE_MINOR_NUM,DEVICE_NAME);
}
else{
/*動態注冊設備號*/
ret = alloc_chrdev_region(&num_dev,numdev_minor,DEVICE_MINOR_NUM,DEVICE_NAME);
/*獲得主設備號*/
numdev_major = MAJOR(num_dev);
printk(KERN_EMERG "adev_region req %d !\n",numdev_major);
}
if(ret<0){
printk(KERN_EMERG "register_chrdev_region req %d is failed!\n",numdev_major);
}
myclass = class_create(THIS_MODULE,DEVICE_NAME);
my_devices = kmalloc(DEVICE_MINOR_NUM * sizeof(struct reg_dev),GFP_KERNEL);
if(!my_devices){
ret = -ENOMEM;
goto fail;
}
memset(my_devices,0,DEVICE_MINOR_NUM * sizeof(struct reg_dev));
/*設備初始化*/
for(i=0;i<DEVICE_MINOR_NUM;i++){
my_devices[i].data = kmalloc(REGDEV_SIZE,GFP_KERNEL);
memset(my_devices[i].data,0,REGDEV_SIZE);
/*設備注冊到系統*/
reg_init_cdev(&my_devices[i],i);
/*創建設備節點*/
device_create(myclass,NULL,MKDEV(numdev_major,numdev_minor+i),NULL,DEVICE_NAME"%d",i);
//atomic_inc(&(my_devices[i].atc));
/*spin_lock_init(&(my_devices[i].lock));*/
my_devices[i].open_num=0;
/*sema_init(&(my_devices[i].sem_open,2);
sema_init(&(my_devices[i].sem_read,1);
sema_init(&(my_devices[i].sem_write,0);*/
//init_waitqueue_head(&(my_devices[i].wqh));
//strcpy( &(my_devices[i].cdd_work_name),"cdd_work_name");
}
ret = gpio_init();
if(ret){
printk(KERN_EMERG "gpio_init failed!\n");
}
INIT_WORK(&(my_devices->cdd_work,chardeviced_cdd_work_func,NULL);
//my_devices[i].cdd_workqueue;
create_workqueue("cdd_workqueue");
if(IS_ERR(my_devices->cdd_workqueue)){
print(KERN_ERR"ChardeviceDriver: Failure to create work_queue!\n");
ret=PTR_ERR(my_devices->cdd_workqueue);
goto failure_creat_work;
}
printk(KERN_INFO"ChardeviceDriver:Success to create work_queue!\n");
strcpy( &(my_devices->cdd_work_name),"cdd_delayed_work_name");
INIT_DELAYED_WORK(my_devices->cdd_delayed_work,chardevicedriver_cdd_delayed_work_func);
printk(KERN_EMERG "scdev_init!\n");
/*打印信息,KERN_EMERG表示緊急信息*/
return 0;
failure_creat_work:
free_irq(IRQ_EINT(9),my_devices);
free_irq(IRQ_EINT(10),my_devices);
free_irq(IRQ_EINT(17),my_devices);
free_irq(IRQ_EINT(18),my_devices);
for(i=0;i<LED_NUM;i++)
gpio_free(led_gpios[i])
for(i=0;i<IRQ_NUM;i++){
ret = gpio_free(irq_gpio[i]);
fail:
/*注銷設備號*/
unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
printk(KERN_EMERG "kmalloc is fail!\n");
return ret;
}
static void __exit scdev_exit(void)
{
int i;
printk(KERN_EMERG "scdev_exit!\n");
free_irq(IRQ_EINT(9),my_devices);
free_irq(IRQ_EINT(10),my_devices);
free_irq(IRQ_EINT(17),my_devices);
free_irq(IRQ_EINT(18),my_devices);
/*除去字符設備*/
for(i=0;i<DEVICE_MINOR_NUM;i++){
cdev_del(&(my_devices[i].cdev));
/*摧毀設備節點函數d*/
device_destroy(myclass,MKDEV(numdev_major,numdev_minor+i));
}
/*釋放設備class*/
class_destroy(myclass);
/*釋放內存*/
kfree(my_devices);
/*釋放GPIO*/
for(i=0;i<LED_NUM;i++){
gpio_free(led_gpios[i]);
}
unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
}
module_init(scdev_init);
/*初始化函數*/
module_exit(scdev_exit);
/*卸載函數*/
1)申請I/O內存 #include <asm/io.h>
request_mem_region(start,n,name)物理地址
Start 待申請的起始地址(物理地址)
n,從start開始地址的字節數
Name,
2)映射ioremap(phys,addr,size)
phys_addr,等同于1)中的start
size等同于n
return value:映射后的虛擬地址 vir_addr
如:vir_addr=ioremap(0xE0200008,4)
ioread8 ioread16 ioread32
ioreite8 iowrite16 iowrite32
((volatile unsigned int *)vir_addr) =0xXXXX;
volatile
寄存器變量,硬件地址,中斷或者多線程中共享的全局變量,防止編譯器錯誤優化
4)取消映射
ioumap(*addr)
addr, ioremap返回的虛擬地址vir_addr
5)釋放I/O內存
release_mem_region(start,n)
回顧:內核競態與并發
什么情況下會產生競態
1)SMP
2)單CPU支持任務搶占
3)中斷和進程之間
4)中斷和中斷之間
解決競態的方法
1)中斷屏蔽
2)原子操作
位原子操作
×××原子操作 atomic{ int。。。。}
3)自旋鎖
優點:一旦可以獲取鎖,立即獲取
缺點:長時間獲取鎖不成功,會消耗CPU資源
它所保護的臨界資源(代碼段)通常比較短
4)信號量
down(。。。)會導致睡眠
等等看前一章中
自旋鎖只允許一個持有者,信號量可以有多個持有者
信號量保護的臨界資源(代碼段)通常比較長
2,等待隊列 #include<linux/sched.h>
Read() recv()
Wait_event_interrptible //阻塞
Wake_up_interruptible //喚醒
阻塞/非阻塞
實際上,應用程序并不關心驅動里面read/write具體實現,只管調用并獲取返回值
如果設備沒有準備好數據給應用程序讀或者沒有準備好接受用戶程序寫,驅動程序應當阻塞進程,使它進入睡眠,直到請求可以得到滿足
阻塞讀
在阻塞型驅動程序中,如果進程調用read設備操作,但是設備沒有數據或數據不足,進程應該被阻塞,當有新數據到達后,喚醒被阻塞進程
阻塞寫
在阻塞型驅動程序中,如果進程調用write設備操作,但是設備沒有足夠的空間供其寫入,進程該被阻塞,但設備中的數據讀走后,緩沖區中空出部分空間,應該喚醒被阻塞進程
應用程序非阻塞讀
阻塞方式是文件讀寫操作的默認方式
應用程序可以通過使用O_NONBLOCK標志來人為的設置讀寫操作為非阻塞方式
定義在<asm-generic/fcntl.h>
如果設置了O_NONBLOCK標志,read和write的處理行為相同
if(0==pcdevp->)&&(O_NONBLICK&file->f_flags){
printk(KERN_ALET “Char......”);
return -EAGAIN;
---------------------------------------------------------------
多路監聽偵測 select #include <linux/poll.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
Nfds 要監聽的文件描述符最大值+1
Readfds,要監聽的讀文件描述符集合
Writefds要監聽的寫文件描述符集合
Exceptfds 要監聽的異常文件描述集合
Timeout 監聽的超時時間
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
內核中要實現的函數
Unsigned int(*poll)()
Fd_set rfds;
Struct timeval tv
先
FD_ZERO()
加入集合FD_SET()
POLLIN
有數據可讀。
POLLRDNORM
有普通數據可讀。
POLLRDBAND
有優先數據可讀。
POLLPRI
有緊迫數據可讀。
POLLOUT
寫數據不會導致阻塞。
POLLWRNORM
寫普通數據不會導致阻塞。
POLLWRBAND
寫優先數據不會導致阻塞。
POLLMSG
SIGPOLL 消息可用。
此外,revents域中還可能返回下列事件:
POLLER
指定的文件描述符發生錯誤。
POLLHUP
指定的文件描述符掛起事件。
POLLNVAL
指定的文件描述符非法。
這些事件在events域中無意義,因為它們在合適的時候總是會從revents中返回。使用poll()和select()不一樣,你不需要顯式地請求異常情況報告。
POLLIN | POLLPRI等價于select()的讀事件,POLLOUT |POLLWRBAND等價于select()的寫事件。POLLIN等價于POLLRDNORM |POLLRDBAND,而POLLOUT則等價于POLLWRNORM。
中斷
驅動需要包含 #include <linux/gpio.h>
判斷一個IO是否合法:int gpio_is_valid(int number);
設置GPIO的方向,如果是輸出同時設置電平:
/* set as input or output, returning 0 or negative errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
獲取輸入引腳的電平:
/* GPIO INPUT: return zero or nonzero */
int gpio_get_value(unsigned gpio);
/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);
int gpio_cansleep(unsigned gpio);
To access such GPIOs, a different set of accessors is defined:
/* GPIO INPUT: return zero or nonzero, might sleep */
int gpio_get_value_cansleep(unsigned gpio);
/* GPIO OUTPUT, might sleep */
void gpio_set_value_cansleep(unsigned gpio, int value);
獲取一個GPIO并聲明標簽:
/* request GPIO, returning 0 or negative errno.
* non-null labels may be useful for diagnostics.
*/
int gpio_request(unsigned gpio, const char *label);
/* release previously-claimed GPIO */
void gpio_free(unsigned gpio);
將GPIO映射為IRQ中斷:
/* map GPIO numbers to IRQ numbers */
int gpio_to_irq(unsigned gpio);
/* map IRQ numbers to GPIO numbers (avoid using this) */
int irq_to_gpio(unsigned irq);
設置GPIO IRQ中斷類型:
if (!sw->both_edges) {
if (gpio_get_value(sw->gpio))
set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_FALLING);
else
set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_RISING);
在驅動中使用延時函數mdelay,需要包含<linux/delay.h>文件
對于硬件產生的變化,需要CPI進行處理,通過情況下CPU獲取該變化的方式有種
1)輪詢
2)中斷 #include <linux/interrup>
Request irq(...)
注冊中斷request_irq(...)
Irq 要注冊的中斷號 arm/mach-xxxxx/include/mach/irqs.h
Gpio_to_irq()
handle 中斷處理函數
Irqreturn_t xxxxx(int irq,void *dev_id)
Irqflags 中斷標志
Cat /proc/interrupts
IRQF_SHARED
flag定義 描述
IRQF_TRIGGER_XXX 描述該interrupt line觸發類型的flag
IRQF_DISABLED 首先要說明的是這是一個廢棄的flag,在新的內核中,該flag沒有任何的作用了。具體可以參考:Disabling IRQF_DISABLED
舊的內核(2.6.35版本之前)認為有兩種interrupt handler:slow handler和fast handle。在request irq的時候,對于fast handler,需要傳遞IRQF_DISABLED的參數,確保其中斷處理過程中是關閉CPU的中斷,因為是fast handler,執行很快,即便是關閉CPU中斷不會影響系統的性能。但是,并不是每一種外設中斷的handler都是那么快(例如磁盤),因此就有 slow handler的概念,說明其在中斷處理過程中會耗時比較長。對于這種情況,在執行interrupt handler的時候不能關閉CPU中斷,否則對系統的performance會有影響。
新的內核已經不區分slow handler和fast handle,都是fast handler,都是需要關閉CPU中斷的,那些需要后續處理的內容推到threaded interrupt handler中去執行。
IRQF_SHARED 這是flag用來描述一個interrupt line是否允許在多個設備中共享。如果中斷控制器可以支持足夠多的interrupt source,那么在兩個外設間共享一個interrupt request line是不推薦的,畢竟有一些額外的開銷(發生中斷的時候要逐個詢問是不是你的中斷,軟件上就是遍歷action list),因此外設的irq handler中最好是一開始就啟動判斷,看看是否是自己的中斷,如果不是,返回IRQ_NONE,表示這個中斷不歸我管。 早期PC時代,使用8259中斷控制器,級聯的8259最多支持15個外部中斷,但是PC外設那么多,因此需要irq share。現在,ARM平臺上的系統設計很少會采用外設共享IRQ方式,畢竟一般ARM SOC提供的有中斷功能的GPIO非常的多,足夠用的。 當然,如果確實需要兩個外設共享IRQ,那也只能如此設計了。對于HW,中斷控制器的一個interrupt source的引腳要接到兩個外設的interrupt request line上,怎么接?直接連接可以嗎?當然不行,對于低電平觸發的情況,我們可以考慮用與門連接中斷控制器和外設。
IRQF_PROBE_SHARED IRQF_SHARED用來表示該interrupt action descriptor是允許和其他device共享一個interrupt line(IRQ number),但是實際上是否能夠share還是需要其他條件:例如觸發方式必須相同。有些驅動程序可能有這樣的調用場景:我只是想scan一個irq table,看看哪一個是OK的,這時候,如果即便是不能和其他的驅動程序share這個interrupt line,我也沒有關系,我就是想scan看看情況。這時候,caller其實可以預見sharing mismatche的發生,因此,不需要內核打印“Flags mismatch irq……“這樣冗余的信息
IRQF_PERCPU 在SMP的架構下,中斷有兩種mode,一種中斷是在所有processor之間共享的,也就是global的,一旦中斷產生,interrupt controller可以把這個中斷送達多個處理器。當然,在具體實現的時候不會同時將中斷送達多個CPU,一般是軟件和硬件協同處理,將中斷送達一個CPU處理。但是一段時間內產生的中斷可以平均(或者按照既定的策略)分配到一組CPU上。這種interrupt mode下,interrupt controller針對該中斷的operational register是global的,所有的CPU看到的都是一套寄存器,一旦一個CPU ack了該中斷,那么其他的CPU看到的該interupt source的狀態也是已經ack的狀態。
和global對應的就是per cpu interrupt了,對于這種interrupt,不是processor之間共享的,而是特定屬于一個CPU的。例如GIC中interrupt ID等于30的中斷就是per cpu的(這個中斷event被用于各個CPU的local timer),這個中斷號雖然只有一個,但是,實際上控制該interrupt ID的寄存器有n組(如果系統中有n個processor),每個CPU看到的是不同的控制寄存器。在具體實現中,這些寄存器組有兩種形態,一種是banked,所有CPU操作同樣的寄存器地址,硬件系統會根據訪問的cpu定向到不同的寄存器,另外一種是non banked,也就是說,對于該interrupt source,每個cpu都有自己獨特的訪問地址。
IRQF_NOBALANCING 這也是和multi-processor相關的一個flag。對于那些可以在多個CPU之間共享的中斷,具體送達哪一個processor是有策略的,我們可以在多個CPU之間進行平衡。如果你不想讓你的中斷參與到irq balancing的過程中那么就設定這個flag
IRQF_IRQPOLL
IRQF_ONESHOT one shot本身的意思的只有一次的,結合到中斷這個場景,則表示中斷是一次性觸發的,不能嵌套。對于primary handler,當然是不會嵌套,但是對于threaded interrupt handler,我們有兩種選擇,一種是mask該interrupt source,另外一種是unmask該interrupt source。一旦mask住該interrupt source,那么該interrupt source的中斷在整個threaded interrupt handler處理過程中都是不會再次觸發的,也就是one shot了。這種handler不需要考慮重入問題。
具體是否要設定one shot的flag是和硬件系統有關的,我們舉一個例子,比如電池驅動,電池里面有一個電量計,是使用HDQ協議進行通信的,電池驅動會注冊一個threaded interrupt handler,在這個handler中,會通過HDQ協議和電量計進行通信。對于這個handler,通過HDQ進行通信是需要一個完整的HDQ交互過程,如果中間被打斷,整個通信過程會出問題,因此,這個handler就必須是one shot的。
IRQF_NO_SUSPEND 這個flag比較好理解,就是說在系統suspend的時候,不用disable這個中斷,如果disable,可能會導致系統不能正常的resume。
IRQF_FORCE_RESUME 在系統resume的過程中,強制必須進行enable的動作,即便是設定了IRQF_NO_SUSPEND這個flag。這是和特定的硬件行為相關的。
IRQF_NO_THREAD 有些low level的interrupt是不能線程化的(例如系統timer的中斷),這個flag就是起這個作用的。另外,有些級聯的interrupt controller對應的IRQ也是不能線程化的(例如secondary GIC對應的IRQ),它的線程化可能會影響一大批附屬于該interrupt controller的外設的中斷響應延遲。
IRQF_EARLY_RESUME
IRQF_TIMER
如何判斷哪個按鍵觸發的中斷
如何判斷怎么觸發(上升沿,下降沿,高電平,低電平)中斷的
使用管腳的輸入功能(配置成輸入功能)判斷電平
重新配置為外部中斷功能
如何去抖動
Linux內核中中斷處理程序的一般結構
頂半部:完成盡可能少的緊急功能,往往是簡單的讀取寄存器,清除中斷標志。登記底半部。
底半部:完成中斷處理程序中絕大部分工作,通常這部分都比較耗時。
1)軟中斷
2)tasklet(利用了軟中斷的機制)
struct tasklet_struct{
Void(*func)(unsigned long);//底半部完成函數
Unsigned long data;
。。。
}
tasklet_scedule(。。。)//完成底半部的登記
DECLARE_TASKLET 定義并初始化
3)工作者隊列
Struct work_struct{
}
INIT_WAORK//初始化work
Schrdiule_work //登記work
flush_work//
Tasklet和工作者隊列有啥區別
Asklet中的func函數工作于中斷上下文,func不允許睡眠的,work中的func工作于進程上下文
IO與內存:
統一編址(ARM):
MOV
獨立編址(X86):
MOV R0 [0X100]
IN/OUT 0X100
ARM PowePC MPIS 都是用統一編址
X86使用獨立編址
Linux編程使用到的都是虛擬地址,驅動開發時,從芯片手冊得到的物理地址,需要轉換成虛擬地址后再使用
/*包含初始化宏定義的頭文件,代碼中的module_init和module_exit在此文件中*/
#include <linux/init.h>
/*包含初始化加載模塊的頭文件,代碼中的MODULE_LICENSE在此頭文件中*/
#include <linux/module.h>
/*定義module_param module_param_array的頭文件*/
#include <linux/moduleparam.h>
/*定義module_param module_param_array中perm的頭文件*/
#include <linux/stat.h>
/*三個字符設備函數*/
#include <linux/fs.h>
/*MKDEV轉換設備號數據類型的宏定義*/
#include <linux/kdev_t.h>
/*定義字符設備的結構體*/
#include <linux/cdev.h>
/*分配內存空間函數頭文件*/
#include <linux/slab.h>
/*包含函數device_creatchar_driver_ledse 結構體class等頭文件*/
#include <linux/device.h>
#include <linux/wait.h>
/*自定義頭文件*/
#include "char_driver_leds.h"
#include <linux/sched.h>
//#include <stdio.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
/*Linux中申請GPIO的頭文件*/
#include <linux/gpio.h>
/*三星平臺的GPIO配置函數頭文件*/
/*三星平臺EXYNOS系列平臺,GPIO配置參數宏定義頭文件*/
#include <plat/gpio-cfg.h>
/*三星平臺4412平臺,GPIO宏定義頭文件*/
#include <mach/gpio-exynos4.h>
MODULE_LICENSE("Dual BSD/GPL");
/*聲明是開源的,沒有內核版本限制*/
MODULE_AUTHOR("songmao");
/*聲明作者*/
static int led_gpios[] = {
EXYNOS4_GPL2(0),EXYNOS4_GPK1(1),
};
#define LED_NUM ARRAY_SIZE(led_gpios)
static int irq_gpio[] ={
EXYNOS4_GPX1(1),EXYNOS4_GPX1(2),
EXYNOS4_GPX1(0),EXYNOS4_GPX1(1)
};
#define IRQ_NUM ARRAY_SIZE(irq_gpio)
int led_num[4];
int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;
/*輸入主設備號*/
module_param(numdev_major,int,S_IRUSR);
/*輸入次設備號*/
module_param(numdev_minor,int,S_IRUSR);
static struct class *myclass;
struct reg_dev *my_devices;
/*打開操作*/
static int chardevnode_open(struct inode *inode, struct file *file)
{
struct reg_dev *reg_devp =NULL;
reg_devp =container_of(inode->i_cdev,struct reg_dev,cdev);
file->private_data=reg_devp;
printk(KERN_EMERG "chardevnode_open is success!\n");
/* if(!atomic_dec_and_test(&(reg_devp->atc)))
{
printk(KERN_ERR "atomic:device can open only once!");
atomic_inc(&(reg_devp->atc));
return -EBUSY;
}*/
spin_lock(&(reg_devp->lock));
if(OPEN_NUM<=reg_devp->open_num){
spin_unlock(&(reg_devp->lock));
printk(KERN_ERR "atomic:device can open over num!");
return -EBUSY;
}
reg_devp->open_num++;
spin_unlock(&(reg_devp->lock));
return 0;
}
/*關閉操作*/
static int chardevnode_release(struct inode *inode, struct file *file)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
printk(KERN_EMERG "chardevnode_release is success!\n");
//atomic_inc(&(reg_devp->atc));
/*spin_lock(&(reg_devp->lock));
reg_devp->open_num--;
spin_unlock(&(reg_devp->lock));*/
up(&(reg_devp->sem_open));
return 0;
}
/*IO操作*/
static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
switch(cmd)
{
case 0:
case 1:
if (arg > LED_NUM) {
return -EINVAL;
}
gpio_set_value(led_gpios[arg], cmd);
break;
default:
return -EINVAL;
}
printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %d \n",cmd,arg);
up(&(reg_devp->sem_read));
up(&(reg_devp->sem_write));
return 0;
}
ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
//down_interruptible(&(reg_devp->sem_read));
wait_event_interruptible(reg_devp->wqh,0!=reg_devp->led);
reg_devp->led=0;
printk(KERN_INFO"chardevnode_read success");
return 0;
}
ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops)
{
struct reg_dev *reg_devp =NULL;
reg_devp=file->private_data;
//down_interruptible(&(reg_devp->sem_write));
reg_devp->led=1;
wake_up_interruptible(&(reg_devp->wqh));
printk(KERN_INFO"chardevnode_write success");
return 0;
}
loff_t chardevnode_llseek(struct file *file, loff_t offset, int ence){
return 0;
}
struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = chardevnode_open,
.release = chardevnode_release,
.unlocked_ioctl = chardevnode_ioctl,
.read = chardevnode_read,
.write = chardevnode_write,
.llseek = chardevnode_llseek,
};
static void chardeviced_cdd_work_func(struct work_struct *work)
{
int i = 0,j,ledss;
struct reg_dev *pcdevp = container_of(work,struct reg_dev,cdd_work);
printk(KERN_INFO" CharDeviceDriver: Entry cdd_work");
printk(KERN_ALERT"CharDeviceDriver:CDD work name:%s \n",pcdevp->cdd_work_name);
for(i=0;i<4;i++)
{
if(led_num[i]>0)
{
j=i%2;
if(i>1)
ledss=1;
else
ledss=0;
gpio_set_value(led_gpios[j],ledss);
}
}
static void chardevicedriver_cdd_delayed_work_func(struct work_struct *work)
{
struct reg_dev *pcdevp = container_of(work,struct reg_dev,cdd_delayed_work);
printk(KERN_INFO" CharDeviceDriver: Entry chardeviceddriver_cdd_delayed_work_func");
printk(KERN_ALERT"CharDeviceDriver:CDD delayed work namename:%s \n",pcdevp->cdd_delayed_work_name);
}
static irq_handler_t chardevnode_irq(int irq,void *dev_id)
{
struct reg_dev *reg_devp = (struct reg_dev *)dev_id;
for(i=0;i<IRQ_NUM;i++){
s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0));
led_num[i] = gpio_get_value(irq_gpio[i]);
s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0));
}
tasklet_schedule(&(my_devices));
schedule_work(&(reg_devp->cdd_work));
queue_delayed_work(reg_devp->cdd_workqueue,&(reg_devp->cdd_delayed_work),3*HZ);
return IRQ_HANDLED;
}
/*設備注冊到系統*/
static void reg_init_cdev(struct reg_dev *dev,int index){
int err;
int devno = MKDEV(numdev_major,numdev_minor+index);
/*數據初始化*/
cdev_init(&dev->cdev,&my_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &my_fops;
/*注冊到系統*/
err = cdev_add(&dev->cdev,devno,1);
if(err){
printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err);
}
else{
printk(KERN_EMERG "cdev_add %d is success!\n",numdev_minor+index);
}
}
static int gpio_init(void){
int i=0,ret;
for(i=0;i<LED_NUM;i++){
ret = gpio_request(led_gpios[i], "LED");
if (ret) {
printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,i,ret);
return -1;
}
else{
s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
gpio_set_value(led_gpios[i], 1);
}
}
for(i=0;i<IRQ_NUM;i++){
ret = gpio_request(irq_gpio[i], "IRQ");
if (ret) {
printk("%s: request GPIO %d for IRQ failed, ret = %d\n", DEVICE_NAME,i,ret);
return -1;
}
else{
s3c_gpio_cfgpin(irq_gpio[i], S3C_GPIO_SFN(0X0F));
}
}
ret = request_irq(IRQ_EINT(9),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt0",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(9)");
goto FAIL_IRQ_EINT;
}
ret = request_irq(IRQ_EINT(10),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt1",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(10)");
}
ret = request_irq(IRQ_EINT(17),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt2",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(17)");
}
ret = request_irq(IRQ_EINT(18),chardevnode_irq,IRQF_SAMPLE_RANDOM|IRQF_TRIGGER_LOW,"interrupt3",my_devices);
if(0>ret){
printk(KERN_ERR "Chardevnode_irq:Failure is IRQ_EINT(17)");
}
printk(KERN_INFO"Chardevnode_irq:Success is IRQ_EINT");
return 0;
FAIL_IRQ_EINT:
free_irq(IRQ_EINT(9),my_devices);
free_irq(IRQ_EINT(10),my_devices);
free_irq(IRQ_EINT(17),my_devices);
free_irq(IRQ_EINT(18),my_devices);
for(i=0;i<LED_NUM;i++)
gpio_free(led_gpios[i]);
for(i=0;i<IRQ_NUM;i++){
ret = gpio_free(irq_gpio[i]);
}
static int __init scdev_init(void)
{
int ret = 0,i;
dev_t num_dev;
printk(KERN_EMERG "numdev_major is %d!\n",numdev_major);
printk(KERN_EMERG "numdev_minor is %d!\n",numdev_minor);
if(numdev_major){
num_dev = MKDEV(numdev_major,numdev_minor);
ret = register_chrdev_region(num_dev,DEVICE_MINOR_NUM,DEVICE_NAME);
}
else{
/*動態注冊設備號*/
ret = alloc_chrdev_region(&num_dev,numdev_minor,DEVICE_MINOR_NUM,DEVICE_NAME);
/*獲得主設備號*/
numdev_major = MAJOR(num_dev);
printk(KERN_EMERG "adev_region req %d !\n",numdev_major);
}
if(ret<0){
printk(KERN_EMERG "register_chrdev_region req %d is failed!\n",numdev_major);
}
myclass = class_create(THIS_MODULE,DEVICE_NAME);
my_devices = kmalloc(DEVICE_MINOR_NUM * sizeof(struct reg_dev),GFP_KERNEL);
if(!my_devices){
ret = -ENOMEM;
goto fail;
}
memset(my_devices,0,DEVICE_MINOR_NUM * sizeof(struct reg_dev));
/*設備初始化*/
for(i=0;i<DEVICE_MINOR_NUM;i++){
my_devices[i].data = kmalloc(REGDEV_SIZE,GFP_KERNEL);
memset(my_devices[i].data,0,REGDEV_SIZE);
/*設備注冊到系統*/
reg_init_cdev(&my_devices[i],i);
/*創建設備節點*/
device_create(myclass,NULL,MKDEV(numdev_major,numdev_minor+i),NULL,DEVICE_NAME"%d",i);
//atomic_inc(&(my_devices[i].atc));
/*spin_lock_init(&(my_devices[i].lock));*/
my_devices[i].open_num=0;
/*sema_init(&(my_devices[i].sem_open,2);
sema_init(&(my_devices[i].sem_read,1);
sema_init(&(my_devices[i].sem_write,0);*/
//init_waitqueue_head(&(my_devices[i].wqh));
//strcpy( &(my_devices[i].cdd_work_name),"cdd_work_name");
}
ret = gpio_init();
if(ret){
printk(KERN_EMERG "gpio_init failed!\n");
}
INIT_WORK(&(my_devices->cdd_work,chardeviced_cdd_work_func,NULL);
//my_devices[i].cdd_workqueue;
create_workqueue("cdd_workqueue");
if(IS_ERR(my_devices->cdd_workqueue)){
print(KERN_ERR"ChardeviceDriver: Failure to create work_queue!\n");
ret=PTR_ERR(my_devices->cdd_workqueue);
goto failure_creat_work;
}
printk(KERN_INFO"ChardeviceDriver:Success to create work_queue!\n");
strcpy( &(my_devices->cdd_work_name),"cdd_delayed_work_name");
INIT_DELAYED_WORK(my_devices->cdd_delayed_work,chardevicedriver_cdd_delayed_work_func);
printk(KERN_EMERG "scdev_init!\n");
/*打印信息,KERN_EMERG表示緊急信息*/
return 0;
failure_creat_work:
free_irq(IRQ_EINT(9),my_devices);
free_irq(IRQ_EINT(10),my_devices);
free_irq(IRQ_EINT(17),my_devices);
free_irq(IRQ_EINT(18),my_devices);
for(i=0;i<LED_NUM;i++)
gpio_free(led_gpios[i])
for(i=0;i<IRQ_NUM;i++){
ret = gpio_free(irq_gpio[i]);
fail:
/*注銷設備號*/
unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
printk(KERN_EMERG "kmalloc is fail!\n");
return ret;
}
static void __exit scdev_exit(void)
{
int i;
printk(KERN_EMERG "scdev_exit!\n");
free_irq(IRQ_EINT(9),my_devices);
free_irq(IRQ_EINT(10),my_devices);
free_irq(IRQ_EINT(17),my_devices);
free_irq(IRQ_EINT(18),my_devices);
/*除去字符設備*/
for(i=0;i<DEVICE_MINOR_NUM;i++){
cdev_del(&(my_devices[i].cdev));
/*摧毀設備節點函數d*/
device_destroy(myclass,MKDEV(numdev_major,numdev_minor+i));
}
/*釋放設備class*/
class_destroy(myclass);
/*釋放內存*/
kfree(my_devices);
/*釋放GPIO*/
for(i=0;i<LED_NUM;i++){
gpio_free(led_gpios[i]);
}
unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
}
module_init(scdev_init);
/*初始化函數*/
module_exit(scdev_exit);
/*卸載函數*/
1)申請I/O內存 #include <asm/io.h>
request_mem_region(start,n,name)物理地址
Start 待申請的起始地址(物理地址)
n,從start開始地址的字節數
Name,
2)映射ioremap(phys,addr,size)
phys_addr,等同于1)中的start
size等同于n
return value:映射后的虛擬地址 vir_addr
如:vir_addr=ioremap(0xE0200008,4)
ioread8 ioread16 ioread32
ioreite8 iowrite16 iowrite32
((volatile unsigned int *)vir_addr) =0xXXXX;
volatile
寄存器變量,硬件地址,中斷或者多線程中共享的全局變量,防止編譯器錯誤優化
4)取消映射
ioumap(*addr)
addr, ioremap返回的虛擬地址vir_addr
5)釋放I/O內存
release_mem_region(start,n)
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。