实现平台:Ubuntu 14.04 + TQ2440
实现工具:
arm-linux-gcc + SecureCRT +
实现内容:
用户空间 内核空间 实现结果
open led_open led全亮
close led_close led全灭
read led_read 读取led状态(亮或灭)
write led_write 批量设置led状态
ioctl led_ioctl 逐个设置led状态
实现过程:
[1] 原材料:无非就是头文件了
-
#define _LED_H_
-
-
#include <linux/kernel.h>
-
#include <linux/module.h>
-
#include <linux/fs.h>
-
#include <linux/types.h>
-
#include <linux/cdev.h>
-
#include <linux/uaccess.h>
-
#include <linux/device.h>
-
#include <mach/regs-gpio.h>
-
#include <mach/hardware.h>
-
#include <linux/ioctl.h>
-
-
#define LED_ON 1 //亮
-
#define LED_OFF 0 //灭
-
int nMajor = 0; //主设备号
-
int nMinor = 0; //次设备号
-
-
//led 端口表
-
static unsigned long led_port_table[] =
-
{
-
S3C2410_GPB5,
-
S3C2410_GPB6,
-
S3C2410_GPB7,
-
S3C2410_GPB8,
-
};
-
-
//led 输出状态表
-
static unsigned int led_state_table[] =
-
{
-
S3C2410_GPB5_OUTP,
-
S3C2410_GPB6_OUTP,
-
S3C2410_GPB7_OUTP,
-
S3C2410_GPB8_OUTP,
-
};
-
-
//申请一个设备号
-
dev_t dev;
-
-
//申请一个class设备类
-
static struct class *led_class;
-
-
//申请一个cdev设备结构体
-
struct cdev *led_cdev = NULL;
-
-
/******************************************************************
-
*函数接口声明
-
******************************************************************/
-
static int led_open(struct inode *inode, struct file *filp);
-
-
static ssize_t led_read(struct file *filp,char __user *buf,
-
size_t nSize, loff_t * pos);
-
-
static ssize_t led_write(struct file *filp, const char __user *buf,
-
size_t size,loff_t *pos);
-
-
static int led_ioctl(struct inode *inode, struct file *file,
-
unsigned int cmd, unsigned long arg);
-
-
static int led_close(struct inode *inode, struct file *filp);
-
-
/******************************************************************
-
*led操作接口
-
******************************************************************/
-
struct file_operations led_fops =
-
{
-
.owner = THIS_MODULE,
-
.open = led_open,
-
.read = led_read,
-
.write = led_write,
-
.ioctl = led_ioctl,
-
.release = led_close,
-
};
-
-
#endif // _LED_H_
[2] 模块框架
-
;这个过程是必须要懂的,不会的请先学习基础,由于篇幅较长,就不贴出代码了
-
static int __init led_module_init()
-
{
-
/*
-
;初始化led
-
;申请设备号
-
;注册设备
-
;绑定设备文件操作接口
-
;添加设备类
-
;在设备类下创建设备节点
-
*/
-
}
-
-
static void __exit led_module_exit()
-
{
-
/*
-
;删除设备节点
-
;删除设备类
-
;释放设备结构
-
;释放设备号
-
*/
-
}
-
-
module_init(led_module_init);
-
module_exit(led_module_exit_;
-
-
MODULE_LICENSE("GPL");
-
MODULE_AUTHOR("Reyn");
-
MODULE_DESCRIPTION("testing for led_driver")
[3] 操作接口设计
需要的函数接口:
-
struct file_operations led_fops =
-
{
-
.owner = THIS_MODULE,
-
.open = led_open,
-
.read = led_read,
-
.write = led_write,
-
.ioctl = led_ioctl,
-
.release = led_close,
-
};
函数接口原型:
-
static int led_open(struct inode *inode, struct file *filp);
-
-
static ssize_t led_read(struct file *filp,char __user *buf, size_t nSize, loff_t *pos);
-
-
static ssize_t led_write(struct file *filp, const char __user *buf, size_t size, loff_t *pos);
-
-
static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
-
-
static int led_close(struct inode *inode, struct file *filp)
[4] 逐个说明:
[+] led_open() 就是点亮四盏led,很简单,直接看代码,就不解释了:
-
/******************************************************************
-
*函数名称:led_open
-
*函数描述:打开led灯,全亮
-
*返回值 :void
-
******************************************************************/
-
-
static int led_open(struct inode *inode, struct file *filp)
-
{
-
int nLedNb = 0;
-
for(; nLedNb<4; ++nLedNb)
-
{
-
s3c2410_gpio_setpin(led_port_table[nLedNb], ~(LED_ON)); //点亮一盏灯
-
printk(KERN_INFO "[Kernel] led%d on.\n", nLedNb);
-
}
-
-
return 0;
-
}
[+] led_close() 同样很简单,熄灭四盏灯即可,上代码:
-
/******************************************************************
-
*函数名称:led_close
-
*函数描述:关闭led灯,全灭
-
*返回值 :成功返回0,失败返回其他值
-
******************************************************************/
-
-
static int led_close(struct inode *inode, struct file *filp)
-
{
-
int nLedNb = 0;
-
for(nLedNb = 0; nLedNb < 4; ++nLedNb)
-
{
-
s3c2410_gpio_setpin(led_port_table[nLedNb], ~(LED_OFF));
-
}
-
-
printk(KERN_INFO "[Kernel] led all off.\n");
-
-
return 0;
-
}
[+] led_read() 需要读取led管脚状态,根据s3c2410_gpio_getpin()的返回值可以判断:若返回值为0,则led灯是亮的,否则为灭。同时,led_read()接口要求将led灯总体的亮灭状态传送给用户空间,使用copy_to_user()可以做到这一点。
-
/******************************************************************
-
*函数名称:led_read
-
*函数描述:读取led灯状态
-
*返回值 :返回复制长度
-
******************************************************************/
-
static ssize_t led_read(struct file *filp,char __user *buf,
-
size_t nSize, loff_t * pos)
-
{
-
int nStatus = 0; //存储LED状态
-
int nLedNb = 0; //LED灯编号
-
int nCopyLen = 0;//复制信息的长度
-
char status_buf[20] = {};
-
-
for(; nLedNb<4; ++nLedNb)
-
{
-
nStatus = s3c2410_gpio_getpin(led_port_table[nLedNb]);
-
if(nStatus == 0)
-
{
-
printk(KERN_INFO "[Kernel] led%d %s\n", nLedNb, "on");
-
strcat(status_buf, "on ");
-
}
-
else
-
{
-
printk(KERN_INFO "[Kernel] led%d %s\n", nLedNb, "off");
-
strcat(status_buf, "off ");
-
}
-
}
-
-
//将LED灯的状态传给用户空间
-
nCopyLen = strlen(status_buf);
-
copy_to_user(buf, status_buf, nCopyLen);
-
printk(KERN_INFO "[Kernel] copy to user:%d.\n", nCopyLen);
-
-
return nCopyLen;
-
}
[+] led_write() 稍复杂一些,需要从用户空间接收参数(见参数说明)以设置led灯的状态,同时返回写入的长度。
使用copy_from_user()可以接收参数,根据参数进行点灯操作。
-
/******************************************************************
-
*函数名称:led_write
-
*函数描述:批量设置led灯状态
-
* cStatus:[0/1/2/3/4]
-
* 0 - 1 - 2 - 3 - 4
-
* 全灭 1-on 1,2-on 1,2,3-on all-on
-
*返回值 :成功返回复制的长度,失败返回-1
-
******************************************************************/
-
static ssize_t led_write(struct file *filp, const char __user *buf,
-
size_t size,loff_t *pos)
-
{
-
char cStatus;
-
int nCopyLen = sizeof(char);
-
-
//从用户空间接收参数
-
nCopyLen = copy_from_user(&cStatus, buf, nCopyLen) ? (-1) : nCopyLen;
-
printk(KERN_INFO "[Kernel] copy from user:%d.\n", nCopyLen);
-
printk(KERN_INFO "[Kernel] write led status %c.\n", cStatus);
-
-
switch(cStatus)
-
{
-
case '0':
-
{
-
s3c2410_gpio_setpin(led_port_table[0], ~(LED_OFF));
-
s3c2410_gpio_setpin(led_port_table[1], ~(LED_OFF));
-
s3c2410_gpio_setpin(led_port_table[2], ~(LED_OFF));
-
s3c2410_gpio_setpin(led_port_table[3], ~(LED_OFF));
-
printk(KERN_INFO "[Kernel] led all off.\n");
-
}
-
break;
-
-
case '1':
-
{
-
s3c2410_gpio_setpin(led_port_table[0], ~(LED_ON));
-
s3c2410_gpio_setpin(led_port_table[1], ~(LED_OFF));
-
s3c2410_gpio_setpin(led_port_table[2], ~(LED_OFF));
-
s3c2410_gpio_setpin(led_port_table[3], ~(LED_OFF));
-
printk(KERN_INFO "[Kernel] led 1 on.\n");
-
}
-
break;
-
-
case '2':
-
{
-
s3c2410_gpio_setpin(led_port_table[0], ~(LED_ON));
-
s3c2410_gpio_setpin(led_port_table[1], ~(LED_ON));
-
s3c2410_gpio_setpin(led_port_table[2], ~(LED_OFF));
-
s3c2410_gpio_setpin(led_port_table[3], ~(LED_OFF));
-
printk(KERN_INFO "[Kernel] led 1,2 on.\n");
-
}
-
break;
-
-
case '3':
-
{
-
s3c2410_gpio_setpin(led_port_table[0], ~(LED_ON));
-
s3c2410_gpio_setpin(led_port_table[1], ~(LED_ON));
-
s3c2410_gpio_setpin(led_port_table[2], ~(LED_ON));
-
s3c2410_gpio_setpin(led_port_table[3], ~(LED_OFF));
-
printk(KERN_INFO "[Kernel] led 1,2,3 on.\n");
-
}
-
break;
-
-
case '4':
-
{
-
s3c2410_gpio_setpin(led_port_table[0], ~(LED_ON));
-
s3c2410_gpio_setpin(led_port_table[1], ~(LED_ON));
-
s3c2410_gpio_setpin(led_port_table[2], ~(LED_ON));
-
s3c2410_gpio_setpin(led_port_table[3], ~(LED_ON));
-
printk(KERN_INFO "[Kernel] led all on.\n");
-
}
-
break;
-
-
default:
-
printk(KERN_INFO "[Error] unrecognize status %c.\n", cStatus);
-
break;
-
}
-
-
return nCopyLen;
-
}
[+] led_ioctl() 同样根据用户空间的命令和参数进行操作,只不过不再使用copy_xxx_user()接口,因为不需要给用户空间传递信息,因此根据用户空间ioctl()传递进来的参数即可执行操作。
-
/******************************************************************
-
*函数名称:led_ioctl
-
*函数描述:逐个设置led灯的状态
-
*参数描述:cmd表示led状态,arg标识led编号
-
*返回值 :成功返回0,失败返回其他值
-
******************************************************************/
-
static int led_ioctl(struct inode *inode, struct file *file,
-
unsigned int cmd, unsigned long arg)
-
{
-
if(arg > 4 || arg < 0)
-
{
-
return -EINVAL;
-
}
-
-
switch(cmd)
-
{
-
case LED_OFF:
-
{
-
s3c2410_gpio_setpin(led_port_table[arg], 1);
-
printk(KERN_INFO "[Kernel] led%ld off.\n", arg);
-
return 0;
-
}
-
-
case LED_ON:
-
{
-
s3c2410_gpio_setpin(led_port_table[arg], 0);
-
printk(KERN_INFO "[Kernel] led%ld on.\n", arg);
-
return 0;
-
}
-
-
default:
-
return -EINVAL;
-
}
-
}
至此,全部操作接口设计完毕。
[4] 编译:
给模块写个Makefile:
-
# Led Module Makefile
-
OBJ := led.o
-
KERNDIR := /opt/EmbedSky/linux-2.6.30.4/
-
INSTALL := /home/reyn/share/out/
-
CC := arm-linux-gcc
-
obj-m := $(OBJ)
-
all:
-
$(MAKE) -C $(KERNDIR) M=$(PWD) modules
-
cp *.ko $(INSTALL)
-
modules:
-
$(MAKE) -C $(KERNDIR) M=$(PWD) modules
-
install:
-
cp *.ko $(INSTALL)
-
.PHONY:modules clean
-
clean:
-
rm *.mod.c *.o *.order *.ko *.symvers *~
-
rm $(INSTALL)*.ko
编译成模块:
-
reyn@ubuntu:led$ make
-
make -C /opt/EmbedSky/linux-2.6.30.4/ M=/home/reyn/share/drivers/led modules
-
make[1]: 正在进入目录 `/opt/EmbedSky/linux-2.6.30.4'
-
CC [M] /home/reyn/share/drivers/led/led.o
-
Building modules, stage 2.
-
MODPOST 1 modules
-
CC /home/reyn/share/drivers/led/led.mod.o
-
LD [M] /home/reyn/share/drivers/led/led.ko
-
make[1]:正在离开目录 `/opt/EmbedSky/linux-2.6.30.4'
-
cp *.ko /home/reyn/share/out/
-
-
编译成功的话,会生成一些中间文件和最重要的.ko内核模块文件
-
-
reyn@ubuntu:led$ ls
-
led.c led.ko led.mod.o Makefile Module.symvers
-
led.h led.mod.c led.o modules.order test
再写个应用程序test_led.c做测试:
-
#include <stdio.h>
-
#include <string.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
#include <unistd.h>
-
#include <malloc.h>
-
#include <sys/ioctl.h>
-
-
int main(int argc, char **argv)
-
{
-
int nFd = 0;
-
char status;
-
int nRet = 0;
-
char *led_buf;
-
int cmd = 1;
-
unsigned long arg = 0;
-
int ch = 0;
-
-
//open:
-
printf("[User] Open led, if succeed, all on.\n");
-
nFd = open("/dev/led_driver", O_RDWR);
-
if(nFd < 0)
-
{
-
printf("[User] Can't open %s\n", "led_driver");
-
return -1;
-
}
-
-
-
write:
-
printf("[User] Ready for writing. \n");
-
printf("[User] Please input [0/1/2/3/4]:\n");
-
while((status = getchar()) == '\n');
-
nRet = write(nFd, &status, sizeof(char));
-
if(nRet != 1)
-
{
-
printf("[User] Write error!\n");
-
return -1;
-
}
-
printf("[User] Write:%d.\n", nRet);
-
-
if(ch==0 || ch==1 || ch==2 || ch==3)
-
{
-
ch++;
-
goto write;
-
}
-
else
-
{
-
ch = 0;
-
}
-
-
-
//read:
-
printf("[User] Ready for reading.\n");
-
getchar();
-
led_buf = (char *)malloc(16 * sizeof(char));
-
nRet = read(nFd, led_buf, sizeof(char));
-
if(nRet < 0)
-
{
-
printf("[User] Read error.\n");
-
return -1;
-
}
-
printf("[User] Read led status: %s.\n", led_buf);
-
free(led_buf);
-
-
-
ioctl:
-
printf("[User] Ready for ioctl.\n");
-
for(arg=0; arg<4; arg++)
-
{
-
while((ch = getchar()) != '\n');
-
ioctl(nFd, cmd, arg);
-
printf("[User] ioctl%ld cmd:%d arg:%ld.\n", arg, cmd, arg);
-
}
-
cmd = !(cmd);
-
goto ioctl;
-
-
close(nFd);
-
-
return 0;
-
}
写个Makefile,也可以直接输入命令arm-linux-gcc test_led.c -o test_led:
-
CC:=arm-linux-gcc
-
-
all:
-
$(CC) test_led.c -o test_led -Wall -std=c99
-
cp test_led /home/reyn/share/out/
-
-
clean:
-
rm *~ write_led
-
rm /home/reyn/share/out/test_led
编译:
-
reyn@ubuntu:test$ make
-
arm-linux-gcc test_led.c -o test_led -Wall -std=c99
-
cp test_led /home/reyn/share/out/
-
-
编译成功的话,会生成可执行二进制文件test_led
-
-
reyn@ubuntu:test$ ls
-
Makefile test_led test_led.c
[4] 测试运行:
[+] 使用Samba方便快捷地将linux的上述生成的led.ko内核模块文件和test_led二进制文件文件传到windows;
[+] 把文件拷贝到开发板,此过程可以使用secureCRT的rz命令(需要安装一个lrzsz包,请自行Google之);
[+] 修改led.ko 和 test_led 的权限
chmod 777 led.ko test_led
{经测试,只要修改test_led的权限即可}
[+] 插入模块:insmod led.ko
-
[root@EmbedSky /]# insmod led.ko
-
[Kernel] led init done.
-
[Kernel] request dev number.
-
[Kernel] dev add done.
-
[major 252] [minor 0] led module inserted.
-
-
此时查看/proc/devices [查看已经加载的设备和分类] 和 /dev [查看所有外部设备的详细信息] ,
-
可以发现设备号申请成功,设备节点也创建起来了:
-
-
[root@EmbedSky /]# cat /proc/devices
-
Character devices:
-
1 mem
-
4 /dev/vc/0
-
4 tty
-
5 /dev/tty
-
5 /dev/console
-
5 /dev/ptmx
-
7 vcs
-
10 misc
-
13 input
-
14 sound
-
29 fb
-
81 video4linux
-
89 i2c
-
90 mtd
-
108 ppp
-
116 alsa
-
128 ptm
-
136 pts
-
180 usb
-
188 ttyUSB
-
189 usb_device
-
204 ttySAC
-
252 led_driver
-
253 usb_endpoint
-
254 rtc
-
-
Block devices:
-
259 blkext
-
7 loop
-
8 sd
-
31 mtdblock
-
65 sd
-
... ...
-
... ...
-
-
-
[root@EmbedSky /]# ls -l /dev
-
crw-rw---- 1 root root 10, 60 Mar 9 2014 adc
-
crw-rw---- 1 root root 14, 4 Mar 9 2014 audio
-
crw-rw---- 1 root root 10, 61 Mar 9 2014 beep
-
crw-rw---- 1 root root 10, 63 Mar 9 2014 bkl
-
crw-rw---- 1 root root 10, 59 Mar 9 2014 camera
-
crw-rw---- 1 root root 5, 1 Mar 9 13:31 console
-
crw-rw---- 1 root root 116, 0 Mar 9 2014 controlC0
-
crw-rw---- 1 root root 10, 58 Mar 9 2014 cpu_dma_latency
-
lrwxrwxrwx 1 root root 14 Mar 9 2014 dsp -> /dev/sound/dsp
-
crw-rw---- 1 root root 13, 64 Mar 9 2014 event0
-
crw-rw---- 1 root root 13, 65 Mar 9 2014 event1
-
crw-rw---- 1 root root 29, 0 Mar 9 2014 fb0
-
crw-rw---- 1 root root 1, 7 Mar 9 2014 full
-
crw-rw---- 1 root root 89, 0 Mar 9 2014 i2c-0
-
crw-rw---- 1 root root 1, 11 Mar 9 2014 kmsg
-
crw-rw---- 1 root root 10, 62 Mar 9 2014 led
-
crw-rw---- 1 root root 252, 0 Mar 9 13:29 led_driver
-
brw-rw---- 1 root root 7, 0 Mar 9 2014 loop0
-
brw-rw---- 1 root root 7, 1 Mar 9 2014 loop1
-
... ...
-
... ...
接着运行test_led:
-
[root@EmbedSky /]# ./test_led
-
[User] Open led, if succeed, all on. [打开文件节点]
-
[Kernel] led0 on.
-
[Kernel] led1 on.
-
[Kernel] led2 on.
-
[Kernel] led3 on.
-
[User] Ready for writing. [写文件节点]
-
[User] Please input [0/1/2/3/4]:
-
1
-
[Kernel] copy from user:1.
-
[Kernel] write led status 1.
-
[Kernel] led 1 on.
-
[User] Write:1.
-
[User] Ready for writing. [写文件节点]
-
[User] Please input [0/1/2/3/4]:
-
2
-
[Kernel] copy from user:1.
-
[Kernel] write led status 2.
-
[Kernel] led 1,2 on.
-
[User] Write:1.
-
[User] Ready for writing. [写文件节点]
-
[User] Please input [0/1/2/3/4]:
-
3
-
[Kernel] copy from user:1.
-
[Kernel] write led status 3.
-
[Kernel] led 1,2,3 on.
-
[User] Write:1.
-
[User] Ready for writing. [写文件节点]
-
[User] Please input [0/1/2/3/4]:
-
4
-
[Kernel] copy from user:1.
-
[Kernel] write led status 4.
-
[Kernel] led all on.
-
[U[Kernel] led0 on
-
[Kernel] led1 on
-
[Kernel] led2 on
-
[Kernel] led3 on
-
[Kernel] copy to user:12.
-
ser] Write:1.
-
[User] Ready for reading. [读文件节点]
-
[User] Read led status: on on on on .
-
[User] Ready for ioctl. [ioctl操作]
-
1
-
[Kernel] led0 on.
-
[User] ioctl0 cmd:1 arg:0.
-
-
[Kernel] led1 on.
-
[User] ioctl1 cmd:1 arg:1.
-
-
[Kernel] led2 on.
-
[User] ioctl2 cmd:1 arg:2.
-
-
[Kernel] led3 on.
-
[User] ioctl3 cmd:1 arg:3.
-
[User] Ready for ioctl.
-
-
[Kernel] led0 off.
-
[User] ioctl0 cmd:0 arg:0.
-
-
[Kernel] led1 off.
-
[User] ioctl1 cmd:0 arg:1.
-
-
[Kernel] led2 off.
-
[User] ioctl2 cmd:0 arg:2.
-
-
[Kernel] led3 off.
-
[User] ioctl3 cmd:0 arg:3.
-
[User] Ready for ioctl.
-
-
[Kernel] led0 on.
-
[User] ioctl0 cmd:1 arg:0.
-
^C[Kernel] led all off. [关闭文件节点]
[6] 总结:
[+] 有些时候,开发板默认开启了led,这时进行测试就会失败,因为看不到效果啊。
所以,就需要手动终止开发板的led进程了。
-
[root@EmbedSky /]# ps
-
PID USER VSZ STAT COMMAND
-
1 root 2096 S init
-
2 root 0 SW< [kthreadd]
-
3 root 0 SW< [ksoftirqd/0]
-
4 root 0 SW< [events/0]
-
5 root 0 SW< [khelper]
-
11 root 0 SW< [async/mgr]
-
242 root 0 SW< [kblockd/0]
-
252 root 0 SW< [khubd]
-
255 root 0 SW< [kseriod]
-
261 root 0 SW< [kmmcd]
-
286 root 0 SW [pdflush]
-
287 root 0 SW [pdflush]
-
288 root 0 SW< [kswapd0]
-
333 root 0 SW< [aio/0]
-
337 root 0 SW< [nfsiod]
-
341 root 0 SW< [crypto/0]
-
460 root 0 SW< [mtdblockd]
-
580 root 0 SW< [usbhid_resumer]
-
596 root 0 SW< [rpciod/0]
-
613 root 1508 S EmbedSky_wdg
-
614 root 2100 S /bin/sh /bin/qtopia
-
616 root 18552 S qpe
-
619 root 0 SW< [zd1211rw]
-
632 root 1512 S led-player
-
637 root 4756 S /usr/sbin/inetd
-
642 root 2064 S /sbin/boa
-
648 root 2100 S -/bin/sh
-
658 root 9192 S < /opt/Qtopia/bin/qss
-
659 root 12748 S N /opt/Qtopia/bin/quicklauncher
-
668 root 2100 R ps
kill 632,终止即可。
[+] getchar() 输入缓存区问题:
一开始,使用getchar()调试时会造成两次输出(第一次输出正常,第二次不正常),鼓捣了许久,笔者确认代码区没有问题。
于是把重点放在getchar()上,根据笔者的经验,输入输出存在缓存区的问题,现在遇到的清空很可能就是没有清空缓存区的原因。
然后,笔者试着使用Windows下经常使用的fflush(stdin)对标准输入缓存区进行清空,结果发现还是一样的.
百思不得其解,只好Google之。
于是惊奇地发现fflush()在Linux下没有清空输入缓存区的效果,于是你知道的,差点破罐破摔了。
最后总结出来,使用getchar()进行调试的最保险的做法是使用一个while循环对输入的字符进行检测,
如果不是回车符,则继续输入。同理,该方法也适用于scanf()等。
-
while((ch = getchar()) != '\n')
本文原创,如须转载,请注明出处:http://blog.chinaunix.net/blog/post/id/4279550.html
阅读(8580) | 评论(0) | 转发(0) |