一.任務要求
完成一個字元IO口驅動,在開發闆上該IO口對應LED燈。該驅動程式通過控制IO口的高低電平來控制亮滅。同時要寫一個應用層的測試程式,用來測試驅動程式。我的測試程式為myled_test.c,要求在shell下能夠通過該測試程式來控制LED燈的亮滅。如:
./myled_test
on 表示燈全亮;
./myled_test
off 表示燈全滅;
二.流程圖設計
圖1.應用層通路裝置的流程圖
三. 字元IO口驅動程式的設計流程
1)Linux核心的子產品機制
在Linux下,驅動程式都是以子產品存在的,子產品是向核心動态的增加功能,每個子產品都包括module_init和module_exit兩個函數,分别在向系統插入子產品和移除子產品時被調用。架構如下:
#include
#include
static int
hellomodule_init(void)
{
printk("hello word\n");
return
0;
}
static void
hellomodule_exit(void)
{
printk("goodbye word\n");
return;
}
module_init(hellomodule_init);
module_exit(hellomodule_exit);
MODULE_LICENSE("GPL");
2)Linux字元IO驅動設計
步驟如下:
1.定義描述字元IO裝置的結構體
在Linux中,每個裝置都有一個結構體來描述的,該結構體包含了該裝置的所有資訊。如下:
struct cdev
{
struct
kobject kobj;
struct module
*owner;
const struct
file_operations *ops;
struct
list_head list;
dev_t
dev;
unsigned int
count;
};
2.定義裝置結構體變量
用裝置結構體來定義一個變量,在核心中該變量就代表對應的裝置。如下:
struct cdev
myled_cdev;
3.定義裝置的操作接口函數
裝置都是有一些操作的,應用程式就通過這些接口操作函數來使用驅動程式對裝置的控制。如下:
static struct
file_operations led_ops={
.open =
myled_open,
.ioctl =
myled_ioctl,
.release =
myled_close,
};
4.裝置的操作函數
根據裝置的操作接口函數完成操作函數,如下:
int
myled_open(struct inode *inode,struct file *file)
{
int value =
0;
value =
(1<<10)|(1<<12)|(1<<16)|(1<<20);
writel(value,S3C2410_GPBCON);
value = 0x0;
writel(value,S3C2410_GPBUP);
value =
0xfffffffe;
writel(value,S3C2410_GPBDAT);
return 0;
}
int
myled_ioctl(struct inode *inode,struct file *file,unsigned int
cmd,unsigned long arg)
{
switch(cmd)
{
case
LED_ON:
writel(0x0,S3C2410_GPBDAT);
break;
case
LED_OFF:
writel(0xfffffffe,S3C2410_GPBDAT);
break;
default:
break;
}
return 0;
}
int
myled_close(struct inode *inode,struct file *file)
{
printk("bye");
return 0;
}
5.字元驅動子產品的初始化和退出函數
這兩個函數是子產品的架構,定義如下:
static int __init
LED_INIT(void)
{
int re = 0;
int ret = 0;
myled_no =
MKDEV(MAINLEDNO,MINLEDNO);
ret =
register_chrdev_region(myled_no,1,"myled");
if(ret<0)
{
printk("cant
regist");
return
ret;
}
printk("reg
ok");
cdev_init(&myled_cdev,&led_ops);
myled_cdev.owner =
THIS_MODULE;
re =
cdev_add(&myled_cdev,myled_no,1);
if(re<0)
{
printk("add
error");
return
re;
}
printk("add
ok");
return 0;
}
static void __exit
LED_EXIT(void)
{
cdev_del(&myled_cdev);
unregister_chrdev_region(myled_no,1);
printk("exit
ok");
}
和unregister_chrdev_region> 。
最後向核心申明myled_init和myled_exit函數以及LICENSE。如下:
module_init(LED_INIT);
module_exit(LED_EXIT);
MODULE_LICENSE("GPL");
6.裝置驅動的編譯
Linux驅動子產品的編譯是通過Linux頂層下的Makefile檔案來實作的,如下:
obj-m:=myled.o
KDIR=/home/neo/linux-2.6.30.9-EL-20101126
all:
make -C $(KDIR)
SUBDIRS=$(shell pwd) modules
arm-linux-gcc -o
myled_test myled_test.c
四. 字元IO驅動測試程式設計流程
為了測試IO驅動是否正常,在應用層編寫一個LED燈的程式,主要完成打開,關閉功能。如下:
int main(int
argc,char **argv)
{
int
fd;
fd =
open("/dev/ledS0",0);
if(!strcmp(argv[1],"on"))
{
ioctl(fd,LED_ON);
}
else
if(!strcmp(argv[1],"off"))
{
ioctl(fd,LED_OFF);
}
return 0;
}
五. 編譯及下載下傳流程
1.動态加載
a) 将myled.c和myled_test.c用make進行編譯,得到子產品myled.ko和執行檔案myled_test
b) 更新S3C2440的核心和檔案系統。啟動開發闆的LINUX系統。
c) 往開發闆核心中添加驅動子產品
在shell下執行insmod
myled.ko
d) 建立裝置檔案節點
在shell下執行mknod
/dev/ledS0 c 108 0
108:主裝置号
0:次裝置号>
e) 将執行檔案myled_test添加到開發闆上,并執行:
./myled_test
on
./myled_test
off
2.靜态加載
a) 打開核心源碼檔案包,将myled.c檔案拷到driver檔案夾下,打開Kconfig檔案,在其中添加如下代碼:
config
LINETECH_LED
bool "config
myled"
default y
help
myled driver
test
b) 同時打開Makefile檔案,添加如下代碼:
obj-$(CONFIG_LINETECH_LED) +=myled.o
c) 在終端輸入make
menuconfig進行核心的配置,如圖:
最後一個選項即為我要選得myled配置。
d)
将核心編譯得到zImage,下載下傳到開發闆中,建立裝置檔案節點
在shell下執行mknod
/dev/ledS0 c 108 0
108:主裝置号
0:次裝置号>\
e)
将執行檔案myled_test添加到開發闆上,并執行:
./myled_test
on
./myled_test
off
三. 總結
通過這次對LINUX字元驅動LED燈的設計讓我獲益匪淺,該實驗讓我對驅動程式的設計有了大概的了解。首先,要知道驅動程式必須要有架構,即初始化和退出。然後,每個裝置都有與之對應的結構體,而應用層要使用驅動程式,其中必須要有接口操作函數,特别注意下我在myled_open中将LED的寄存器進行配置,而不是在初始化函數中設定,是因為:雖然加載了子產品,但是這個子產品卻不一定會被用到,是以在使用時才去設定。最後在初始化函數中要對cdev結構體變量進行初始化,并把該結構體注冊到核心中。
概括為:應用程式在核心的字元裝置數組中能夠找到主裝置号,根據裝置号找到該裝置的結構體cdev,通路結構體中的變量*ops,而*ops指向file_operation結構體,該結構體中有被應用層調用的函數。
四. 代碼
//myled.c
#include
#include
#include
#include
#include
#include
#include
#define MAINLEDNO
108
#define MINLEDNO
#define LED_ON
0x01
#define LED_OFF
0x02
dev_t myled_no =
0;
struct cdev
myled_cdev;
````````int
myled_open(struct inode *inode,struct file *file)
{
int value =
0;
value =
(1<<10)|(1<<12)|(1<<16)|(1<<20);
writel(value,S3C2410_GPBCON);
value = 0x0;
writel(value,S3C2410_GPBUP);
value =
0xfffffffe;
writel(value,S3C2410_GPBDAT);
return 0;
}
int
myled_ioctl(struct inode *inode,struct file *file,unsigned int
cmd,unsigned long arg)
{
switch(cmd)
{
case LED_ON:
writel(0x0,S3C2410_GPBDAT);
break;
case LED_OFF:
writel(0xfffffffe,S3C2410_GPBDAT);
break;
default:
break;
}
return 0;
}
int
myled_close(struct inode *inode,struct file *file)
{
printk("bye");
return 0;
}
static struct
file_operations led_ops={
.open =
myled_open,
.ioctl =
myled_ioctl,
.release =
myled_close,
};
static int __init
LED_INIT(void)
{
int re = 0;
int ret = 0;
myled_no =
MKDEV(MAINLEDNO,MINLEDNO);
ret =
register_chrdev_region(myled_no,1,"myled");
if(ret<0)
{
printk("cant
regist");
return ret;
}
printk("reg
ok");
cdev_init(&myled_cdev,&led_ops);
myled_cdev.owner =
THIS_MODULE;
re =
cdev_add(&myled_cdev,myled_no,1);
if(re<0)
{
printk("add
error");
return re;
}
printk("add
ok");
return 0;
}
static void __exit
LED_EXIT(void)
{
cdev_del(&myled_cdev);
unregister_chrdev_region(myled_no,1);
printk("exit
ok");
}
module_init(LED_INIT);
module_exit(LED_EXIT);
MODULE_LICENSE("GPL");
//myled_test.c
#include
#include
#include
#include
#include
#define LED_ON
0x01
#define LED_OFF
0x02
int main(int
argc,char **argv)
{
int fd;
fd =
open("/dev/ledS0",0);
if(!strcmp(argv[1],"on"))
{
ioctl(fd,LED_ON);
}
else
if(!strcmp(argv[1],"off"))
{
ioctl(fd,LED_OFF);
}
return 0;
}
Makefile
obj-m:=myled.o
KDIR=/home/neo/linux-2.6.30.9-EL-20101126
all:
make -C $(KDIR)
SUBDIRS=$(shell pwd) modules
arm-linux-gcc -o
myled_test myled_test.c