移植嵌入式Linux到ARM處理器S3C2410:設備驅動

admin @ 2014-03-25 , reply:0

          設備驅動程序是操作系統內核和機器硬體之間的介面,它為應用程序屏蔽硬體的細節,一般來說,Linux的設備驅動程序需要完成如下功能:

  ·設備初始化、釋放;

  ·提供各類設備服務;

  ·負責內核和設備之間的數據交換;

  ·檢測和處理設備工作過程中出現的錯誤。

  Linux下的設備驅動程序被組織為一組完成不同任務的函數的集合,通過這些函數使得Windows的設備操作猶如文件一般。在應用程序看來,硬體設備只是一個設備文件,應用程序可以象操作普通文件一樣對硬體設備進行操作,如open ()、close ()、read ()、write () 等。

  Linux主要將設備分為二類:字元設備和塊設備。字元設備是指設備發送和接收數據以字元的形式進行;而塊設備則以整個數據緩衝區的形式進行。在對字元設備發出讀/寫請求時,實際的硬體I/O一般就緊接著發生了;而塊設備則不然,它利用一塊系統內存作緩衝區,當用戶進程對設備請求能滿足用戶的要求,就返回請求的數據,如果不能,就調用請求函數來進行實際的I/O操作。塊設備主要針對磁碟等慢速設備。

  1.內存分配

  由於Linux驅動程序在內核中運行,因此在設備驅動程序需要申請/釋放內存時,不能使用用戶級的malloc/free函數,而需由內核級的函數kmalloc/kfree () 來實現,kmalloc()函數的原型為:

void kmalloc (size_t size ,int priority);

  參數size為申請分配內存的位元組數,kmalloc最多只能開闢128k的內存;參數priority說明若kmalloc()不能馬上分配內存時用戶進程要採用的動作:GFP_KERNEL 表示等待,即等kmalloc()函數將一些內存安排到交換區來滿足你的內存需要,GFP_ATOMIC 表示不等待,如不能立即分配到內存則返回0 值;函數的返回值指向已分配內存的起始地址,出錯時,返回0。

  kmalloc ()分配的內存需用kfree()函數來釋放,kfree ()被定義為:

# define kfree (n) kfree_s( (n) ,0)

  其中kfree_s () 函數原型為:

void kfree_s (void * ptr ,int size);

  參數ptr為kmalloc()返回的已分配內存的指針,size是要釋放內存的位元組數,若為0 時,由內核自動確定內存的大小。

  2.中斷

  許多設備涉及到中斷操作,因此,在這樣的設備的驅動程序中需要對硬體產生的中斷請求提供中斷服務程序。與註冊基本入口點一樣,驅動程序也要請求內核將特定的中斷請求和中斷服務程序聯繫在一起。在Linux中,用request_irq()函數來實現請求:

int request_irq (unsigned int irq ,void( * handler) int ,unsigned long type ,char * name);

   參數irq為要中斷請求號,參數handler為指向中斷服務程序的指針,參數type 用來確定是正常中斷還是快速中斷(正常中斷指中斷服務子程序返回后,內核可以執行調度程序來確定將運行哪一個進程;而快速中斷是指中斷服務子程序返回后,立即執行被中斷程序,正常中斷type 取值為0 ,快速中斷type 取值為SA_INTERRUPT),參數name是設備驅動程序的名稱。

        3.字元設備驅動

  我們必須為字元設備提供一個初始化函數,該函數用來完成對所控設備的初始化工作,並調用register_chrdev() 函數註冊字元設備。假設有一字元設備"exampledev",則其init 函數為:

void exampledev_init(void)
{
if (register_chrdev(MAJOR_NUM, " exampledev ", &exampledev_fops))
TRACE_TXT("Device exampledev driver registered error");
else
TRACE_TXT("Device exampledev driver registered successfully");
…//設備初始化
}

  其中,register_chrdev函數中的參數MAJOR_NUM為主設備號,"exampledev"為設備名,exampledev_fops 為包含基本函數入口點的結構體,類型為file_operations。當執行exampledev_init時,它將調用內核函數 register_chrdev,把驅動程序的基本入口點指針存放在內核的字元設備地址表中,在用戶進程對該設備執行系統調用時提供入口地址。

  較早版本內核的file_operations結構體定義為(代碼及圖示):

struct file_operations
{
 int (*lseek)();
 int (*read)();
 int (*write)();
 int (*readdir)();
 int (*select)();
 int (*ioctl)();
 int (*mmap)();
 int (*open)();
 void(*release)();
 int (*fsync)();
 int (*fasync)();
 int (*check_media_change)();
 void(*revalidate)();
}; 

 

  隨著內核功能的加強,file_operations結構體也變得更加龐大。但是大多數的驅動程序只是利用了其中的一部分,對於驅動程序中無需提供的功能,只需要把相應位置的值設為NULL。對於字元設備來說,要提供的主要入口有:open ()、release ()、read ()、write ()、ioctl ()等。

  open()函數 對設備特殊文件進行open()系統調用時,將調用驅動程序的open () 函數:

int (*open)(struct inode * inode,struct file *filp);

   其中參數inode為設備特殊文件的inode (索引結點) 結構的指針,參數filp是指向這一設備的文件結構的指針。open()的主要任務是確定硬體處在就緒狀態、驗證次設備號的合法性(次設備號可以用 MINOR(inode-> i_rdev) 取得)、控制使用設備的進程數、根據執行情況返回狀態碼(0表示成功,負數表示存在錯誤) 等;

  release()函數 當最後一個打開設備的用戶進程執行close ()系統調用時,內核將調用驅動程序的release () 函數:

void (*release) (struct inode * inode,struct file *filp) ;

  release 函數的主要任務是清理未結束的輸入/輸出操作、釋放資源、用戶自定義排他標誌的複位等。

  read()函數 當對設備特殊文件進行read() 系統調用時,將調用驅動程序read() 函數:

ssize_t (*read) (struct file * filp, char * buf, size_t count, loff_t * offp);

  參數buf是指向用戶空間緩衝區的指針,由用戶進程給出,count 為用戶進程要求讀取的位元組數,也由用戶給出。

  read() 函數的功能就是從硬設備或內核內存中讀取或複製count個位元組到buf 指定的緩衝區中。在複製數據時要注意,驅動程序運行在內核中,而buf指定的緩衝區在用戶內存區中,是不能直接在內核中訪問使用的,因此,必須使用特殊的複製函數來完成複製工作,這些函數在include/asm/uaccess.h中被聲明:

unsigned long copy_to_user (void * to, void * from, unsigned long len);

  此外,put_user()函數用於內核空間和用戶空間的單值交互(如char、int、long)。

  write( ) 函數 當設備特殊文件進行write () 系統調用時,將調用驅動程序的write () 函數:

ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

  write ()的功能是將參數buf 指定的緩衝區中的count 個位元組內容複製到硬體或內核內存中,和read() 一樣,複製工作也需要由特殊函數來完成:

unsigned long copy_from_user(void *to, const void *from, unsigned long n);

  此外,get_user()函數用於內核空間和用戶空間的單值交互(如char、int、long)。

  ioctl() 函數 該函數是特殊的控制函數,可以通過它向設備傳遞控制信息或從設備取得狀態信息,函數原型為:

int (*ioctl) (struct inode * inode,struct file * filp,unsigned int cmd,unsigned long arg);

  參數cmd為設備驅動程序要執行的命令的代碼,由用戶自定義,參數arg 為相應的命令提供參數,類型可以是整型、指針等。

  同樣,在驅動程序中,這些函數的定義也必須符合命名規則,按照本文約定,設備"exampledev"的驅動程序的這些函數應分別命名為 exampledev_open、exampledev_ release、exampledev_read、exampledev_write、exampledev_ioctl,因此設備 "exampledev"的基本入口點結構變數exampledev_fops 賦值如下(對較早版本的內核):

struct file_operations exampledev_fops {
 NULL ,
 exampledev_read ,
 exampledev_write ,
 NULL ,
 NULL ,
 exampledev_ioctl ,
 NULL ,
 exampledev_open ,
 exampledev_release ,
 NULL ,
 NULL ,
 NULL ,
 NULL
} ;

  就目前而言,由於file_operations結構體已經很龐大,我們更適合用GNU擴展的C語法來初始化exampledev_fops:

struct file_operations exampledev_fops = {
 read: exampledev _read,
 write: exampledev _write,
 ioctl: exampledev_ioctl ,
 open: exampledev_open ,
 release : exampledev_release ,
};

  看看第一章電路板硬體原理圖,板上包含四個用戶可編程的發光二極體(LED),這些LED連接在ARM處理器的可編程I/O口(GPIO)上,現在來編寫這些LED的驅動:

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <asm/hardware.h>
#define DEVICE_NAME "leds" /*定義led 設備的名字*/
#define LED_MAJOR 231 /*定義led 設備的主設備號*/
static unsigned long led_table[] =
{
 /*I/O 方式led 設備對應的硬體資源*/
 GPIO_B10, GPIO_B8, GPIO_B5, GPIO_B6,
};
/*使用ioctl 控制led*/
static int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
 switch (cmd)
 {
  case 0:
  case 1:
   if (arg > 4)
   {
    return -EINVAL;
   }
   write_gpio_bit(led_table[arg], !cmd);
  default:
   return -EINVAL;
 }
}
static struct file_operations leds_fops =
{
 owner: THIS_MODULE, ioctl: leds_ioctl,
};
static devfs_handle_t devfs_handle;
static int __init leds_init(void)
{
 int ret;
 int i;
 /*在內核中註冊設備*/
 ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &leds_fops);
 if (ret < 0)
 {
  printk(DEVICE_NAME " can't register major number\n");
  return ret;
 }
 devfs_handle = devfs_register(NULL, DEVICE_NAME, DEVFS_FL_DEFAULT, LED_MAJOR,
0, S_IFCHR | S_IRUSR | S_IWUSR, &leds_fops, NULL);
 /*使用宏進行埠初始化,set_gpio_ctrl 和write_gpio_bit 均為宏定義*/
 for (i = 0; i < 8; i++)
 {
  set_gpio_ctrl(led_table[i] | GPIO_PULLUP_EN | GPIO_MODE_OUT);
  write_gpio_bit(led_table[i], 1);
 }
 printk(DEVICE_NAME " initialized\n");
 return 0;
}

static void __exit leds_exit(void)
{
 devfs_unregister(devfs_handle);
 unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}

module_init(leds_init);
module_exit(leds_exit);

  使用命令方式編譯led 驅動模塊:

#arm-linux-gcc -D__KERNEL__ -I/arm/kernel/include
-DKBUILD_BASENAME=leds -DMODULE -c -o leds.o leds.c

  以上命令將生成leds.o 文件,把該文件複製到板子的/lib目錄下,使用以下命令就可以安裝leds驅動模塊:

#insmod /lib/ leds.o

  刪除該模塊的命令是:

#rmmod leds



[admin via 研發互助社區 ] 移植嵌入式Linux到ARM處理器S3C2410:設備驅動已經有1270次圍觀

http://cocdig.com/docs/show-post-42604.html