首先要明白字符设备驱动注册的基本流程:
当我们调用insomd命令加载驱动后,驱动程序从module_init函数开始执行:硬件初始化 -> 申请主次设备号 -> 定义fops(file_operations)结构体 -> 申请cdev结构体并把fops结构体嵌入cdev结构体中与之绑定 -> cdev字符设备的注册。
有一点需要明确的是,在Linux内核中,所有的设备都是以文件。我们对设备的操作即是对Linux内核中文件的操作。设备都在/dev目录下。而inode则是设备索引节点,每个文件产生后都会有相应的inode来标示。
驱动加载完成后,file_operations结构体中的成员可以为应用程序提供对设备进行各种操作的函数指针:open,read,write等。
cdev结构体是用来描述字符设备的,内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义:
一个 cdev 一般它有两种定义初始化方式:静态的和动态的。
静态内存定义初始化:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
动态内存定义初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;
两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。
当我们调用insomd命令加载驱动后,驱动程序从module_init函数开始执行:硬件初始化 -> 申请主次设备号 -> 定义fops(file_operations)结构体 -> 申请cdev结构体并把fops结构体嵌入cdev结构体中与之绑定 -> cdev字符设备的注册。
有一点需要明确的是,在Linux内核中,所有的设备都是以文件。我们对设备的操作即是对Linux内核中文件的操作。设备都在/dev目录下。而inode则是设备索引节点,每个文件产生后都会有相应的inode来标示。
驱动加载完成后,file_operations结构体中的成员可以为应用程序提供对设备进行各种操作的函数指针:open,read,write等。
cdev结构体是用来描述字符设备的,内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义:
1 linux-2.6.22/include/linux/cdev.h
2 struct cdev {
3 struct kobject kobj; // 每个 cdev 都是一个 kobject
4 struct module *owner; // 指向实现驱动的模块
5 const struct file_operations *ops; // 操纵这个字符设备文件的方法
6 struct list_head list; // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
7 dev_t dev; // 起始设备编号
8 unsigned int count; // 设备范围号大小
9 };
一个 cdev 一般它有两种定义初始化方式:静态的和动态的。
静态内存定义初始化:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
动态内存定义初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;
两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。
s3c_led驱动代码:
//http://blog.csdn.net/dreaming_my_dreams/article/details/7745148,部分头文件简介及路径分析
#include <linux/module.h> /* Every Linux kernel module must include this head 写内核驱动的时候 必须加载这个头文件,作用是动态的将模块加载到内核中去
*常用的宏定义如 MODULE_LICESENCE(),MODULE_AUTHOR(),等在此文件中,而且 kobject,kset结构体题及其操作函数也在这个结构体中
*http://blog.csdn.net/kokodudu/article/details/17358001/ 源代码 */
#include <linux/init.h> /* Every Linux kernel module must include this head ,在init.h头文件中包含了模块的初始化的宏定义 以及一些其他函数的初始化函数
http://blog.csdn.net/kokodudu/article/details/17361161源代码 */
#include <linux/kernel.h> /* printk() 包含了内核打印函数 printk函数 等,驱动要写入内核,与内核相关的头文件 */
#include <linux/fs.h> /* struct fops file_operations 包含了文件操作相关struct的定义 */
#include <linux/errno.h> /* error codes 包含了对返回值的宏定义,这样用户程序可以用perror输出错误信息。*/
#include <linux/cdev.h> /* cdev_alloc() 对字符设备结构cdev以及一系列的操作函数的定义,包含了cdev 结构及相关函数的定义。
详细了解cdev,主次设备号。http://blog.csdn.net/zqixiao_09/article/details/50839042 */
#include <asm/io.h> /*【 ioremap()将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问 I/O头文件】,该头文件以宏的嵌入汇编程序形式定义对I/O端口操作的函数 */
#include <linux/ioport.h> /* request_mem_region() Linux设计了一个通用的数据结构resource来描述各种I/O资源(如:I/O端口、外设内存、DMA和IRQ等)。该结构定义在include/linux/ioport.h头文件中。
http://blog.csdn.net/skyflying2012/article/details/8672011
http://blog.csdn.net/os_boy/article/details/12164009*/
#include <asm/ioctl.h> /* Linux kernel space head file for macro _IO() to generate ioctl command 不是特别懂,只是觉得和io端口有关系
ioctl方法详解 http://blog.csdn.net/huiguixian/article/details/24410885 */
#ifndef __KERNEL__
#include <sys/ioctl.h> /* User space head file for macro _IO() to generate ioctl command
ioctl()是I/O操作的杂货箱,很多事情都要依靠它来完成。除了摄像头,视频采集卡之类的与video相关的操作其实也都与此类似*/
#endif
//#include <linux/printk.h> /* Define log level KERN_DEBUG, no need include here 可以用于内核调试http://blog.csdn.net/itsenlin/article/details/43205983*/
#define DRV_AUTHOR "fanmaolin <fanmaolinn@gmail.com>"
//作者
#define DRV_DESC "S3C24XX LED driver" //作用说明
#define DEV_NAME "led" //名字
#define LED_NUM 4 //灯的数目
/* Set the LED dev major number */
//#define LED_MAJOR 79 程序中出现 LED_MAJOR 代表79这个数值 下面类似
#ifndef LED_MAJOR
#define LED_MAJOR 0 // 0 定义默认的主设备号为0,一般这个定义的设备号是固定不可用的,但是这为自动分配主设备号的逻辑提供了方便。
#endif
#define DRV_MAJOR_VER 1 //设置版本号方便维护,版本号包括主设备号、次设备号、修订版本号
#define DRV_MINOR_VER 0
#define DRV_REVER_VER 0
#define DISABLE 0 //禁用某个特性的宏
#define ENABLE 1 //使能某个特性的宏
#define GPIO_INPUT 0x00 //宏定义 GPIO输入模式用00代替
#define GPIO_OUTPUT 0x01 //宏定义 GPIO输出模式用01代替
#define PLATDRV_MAGIC 0x60 //定义了一个魔数,关于魔数的分析http://blog.csdn.net/leechar_linux/article/details/39545235
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19) /* 魔术字有着特殊的功能,定义一个系统未用的魔术字,然后让魔术字生成我们定义的LED_OFF与LED_ON,
这样我们的定义就不会和系统中别的宏定义相同了 */
#define S3C_GPB_BASE 0x56000010 //定义GPB管脚控制寄存器的基址地址
#define GPBCON_OFFSET 0 //定义GPBCON的偏移地址,用来选定引脚并设置输入输出模式 这里应该是输入
#define GPBDAT_OFFSET 4 //定义GPBDAT偏移地址(GPBDAT用于读写引脚数据。输入模式时,读此寄存器可知相应引脚的电平是低还是高;输出模式写此引脚可设置输出高低电平)
#define GPBUP_OFFSET 8 // 定义GPBUP偏移地址
/*GPBUP的某位为1时,相应引脚无内部上拉电阻;为0时相应引脚使用内部上拉电阻。
上拉电阻的作用是当GPIO引脚出去第三态时,即既不是输出高电平也不是输出低电平,
而是呈现高阻态,相当于没有接芯片。它的电平状态由上拉电阻下拉电阻决定。*/
#define S3C_GPB_LEN 0x10 /* 0x56000010~0x56000020 GPB寄存器的内存地址总长度*/
int led[LED_NUM] = {5,6,8,10}; /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */
static void __iomem *s3c_gpb_membase;
//定义s3c_gpb_membase为void __iomem*类型指针 ,在后面用来保存映射后的虚拟空间基地址。Iomem在linux下的作用 http://blog.csdn.net/gongmin856/article/details/8470540
#define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase) //将val的值写入reg地址中
#define s3c_gpio_read(reg) __raw_readl((reg)+s3c_gpb_membase) //读取当前虚拟地址寄存器的值
int dev_count = ARRAY_SIZE(led); //将设备体中设备的个数赋值给dev_count 43、44行
int dev_major = LED_MAJOR; //主设备号赋值给dev_major
int dev_minor = 0; //次设备号赋值为0
int debug = DISABLE; //出错定义赋值
static struct cdev *led_cdev; //定义一个cdev类型的结构体指针,cdev是内核中表示字符设备的一个结构体,见23行
static int s3c_hw_init(void) //硬件初始化函数
{
int i;
volatile unsigned long gpb_con, gpb_dat, gpb_up;;//分别定义gob_con ;gpb_dat ;gpb_up变量,因为是保存寄存器地址的所以要用volatile类型,防止被优化。
/*#define __IO volatile 的作用就是指示编译器不要因优化而省略此指令,必须每次都直接读写其值。
volatile 详细作用 http://blog.csdn.net/daa20/article/details/42339695*/
if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))//申请一段IO内存空间 见26行
/*#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))
其中,参数start是I/O内存资源的起始物理地址(是CPU在RAM物理地址空间中的物理地址),
参数n指定I/O内存资源的大小。在请求IO内存资源成功后,开始用ioremap进行映射操作.*/
{
return -EBUSY;
}
//判断是否申请失败,如果硬件资源被占据着则返回-EBUSY.成功则往下执行
if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) ) //ioremap(参考25行)用来将IO资源的物理地址映射到内核虚拟地址空间保存到s3c_gpb_membase
{
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);//映射失败则释放申请的IO内存空间
return -ENOMEM; //并返回-ENOMEM
}
//内存申请成功且地址映射成功则进行对各个寄存器的操作
for(i=0; i<dev_count; i++)
{
/* Set GPBCON register, set correspond GPIO port as input or output mode */
gpb_con = s3c_gpio_read(GPBCON_OFFSET); //读取GPBCON的虚拟地址(在63行)并保存在gpb_con中
gpb_con &= ~(0x3<<(2*led[i])); /* Clear the currespond LED GPIO configure register 先将相应的led的gpio引脚复位清零*/
gpb_con |= GPIO_OUTPUT<<(2*led[i]); /* Set the currespond LED GPIO as output mode 然后将相应的led的gpio引脚设置为输出模式*/
s3c_gpio_write(gpb_con, GPBCON_OFFSET); //再将设置好的GPBCON引脚信息写入寄存器中
/* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable */
gpb_up = s3c_gpio_read(GPBUP_OFFSET); //读取GPBUP的虚拟地址并保存在gpb_up中
//gpb_up &= ~(0x1<<led[i]); /* Enable pull up resister 使能相应led的gpbio引脚的上拉电位 */
gpb_up |= (0x1<<led[i]); /* Disable pull up resister 禁用相应led的gpbio引脚的上拉电位*/
s3c_gpio_write(gpb_up, GPBUP_OFFSET); //将设置好的GPBUP引脚信息写入寄存器中
/* Set GPBDAT register, set correspond GPIO port power level as high level or low level */
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET); //读取GPBDAT的虚拟地址并保存在gpb_dat中
//gpb_dat &= ~(0x1<<led[i]); /* This port set to low level, then turn LED on 将这个引脚的电平置为低,灯亮 */
gpb_dat |= (0x1<<led[i]); /* This port set to high level, then turn LED off 将这个引脚的电平置为高,灯灭*/
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET); //将设置好的GPBDAT引脚信息写入寄存器中
}
return 0;
}
static void turn_led(int which, unsigned int cmd) //由ioctl调用,控制单个LED的亮灭
{
volatile unsigned long gpb_dat; //参考97行
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET); //读取GPBDAT的虚拟地址并保存在gpb_dat中
if(LED_ON == cmd)
{
gpb_dat &= ~(0x1<<led[which]); /* Turn LED On */
}
else if(LED_OFF == cmd)
{
gpb_dat |= (0x1<<led[which]); /* Turn LED off */
}
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET); //将设置好的GPBDAT引脚信息写入寄存器中
}
static void s3c_hw_term(void) //调用LED结束后释放内存所占用的资源及设置相应的GPIO引脚关闭LED
{
int i;
volatile unsigned long gpb_dat;
for(i=0; i<dev_count; i++)
{
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
gpb_dat |= (0x1<<led[i]); /* Turn LED off */
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
//设置相应的GPBDAT引脚信息,使LED不通
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); //释放指定的IO内存资源
iounmap(s3c_gpb_membase); //iounmap函数用于取消ioremap()所做的映射
}
static int led_open(struct inode *inode, struct file *file)//驱动功能函数open
{
int minor = iminor(inode); //通过索引节点获取次设备号
file->private_data = (void *)minor; //将次设备号保存到private_data中,方便在函数中调用,private_data在系统调用期间保存各种状态信息
printk(KERN_DEBUG "/dev/led%d opened.\n", minor);//打印成功信息
return 0;
}
static int led_release(struct inode *inode, struct file *file)//驱动功能函数release ,关闭Led
{
printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));
return 0;
}
/*内核中用inode结构表示具体的文件,而用file结构表示打开的文件描述符。
struct file代表一个打开的文件,在执行file_operation中的open操作时被创建,
这里需要注意的是与用户空间file指针的区别,一个在内核,而file指针在用户空间,由c库来定义。
struct inode被内核用来代表一个文件,注意和struct file的区别,struct inode一个是代表文件,struct file一个是代表打开的文件*/
static void print_help(void)
{
printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME);
//printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG);
printk("Turn LED on command : %u\n", LED_ON);
printk("Turn LED off command : %u\n", LED_OFF);
return;
}
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) //led 中断控制函数
{
int which = (int)file->private_data; //接受次设备号
switch (cmd) //判断参数要实现的功能
{
case LED_ON:
turn_led(which, LED_ON);
break;
case LED_OFF:
turn_led(which, LED_OFF);
break;
//如果传进来的既不是ON也不是OFF则打印出错并打印HELP结构体
default:
printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
print_help();
break;
}
return 0;
}//实现具体第几盏灯亮或灭
static struct file_operations led_fops = //结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针
{
.owner = THIS_MODULE, // 指向拥有该结构体模块的指针,避免正在操作时被卸载,一般为初始化为THIS_MODULES
.open = led_open, //传递led_open函数打开设备
.release = led_release, //传递led_release函数关闭设备
.unlocked_ioctl = led_ioctl, //传递中断函数,不使用BLK的文件系统,将使用此种函数的指针代替ioctl
};
static int __init s3c_led_init(void) //传递中断函数,不使用BLK的文件系统,将使用此种函数的指针代替ioctl
{
int result;
dev_t devno;
if( 0 != s3c_hw_init() ) //初始化led硬件并判断是否初始化成功
{
printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
return -ENODEV;
}
/* Alloc the device for driver */
if (0 != dev_major) /* Static 已知主设备号,静态获得设备编号并注册 */
{
devno = MKDEV(dev_major, 0); //通过主设备号与次设备号构建32位设备号
result = register_chrdev_region (devno, dev_count, DEV_NAME);//已知主设备号,静态获得设备号并注册
}
else
{
result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);//设备号未知,动态分配设备号并注册
dev_major = MAJOR(devno); //根据设备号devno获得主设备号
}
/* Alloc for device major failure */
if (result < 0)
{
printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);//设备号分配失败打印错误
return -ENODEV;
}
printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);//设备号分配成功打印主设备号
if(NULL == (led_cdev=cdev_alloc()) ) //创建一个cdev结构体,一个字符设备用一个cdev结构体来描述
{
printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
unregister_chrdev_region(devno, dev_count);
return -ENOMEM;
}
//cdev_alloc它主要完成了空间的申请和简单的初始化操作,分配一个cdev(在内核中代表字符设备),如果失败了则打印出错并释放分配的设备号
led_cdev->owner = THIS_MODULE; //指明设备所属模块,这里永远指向THIS_MODULE
cdev_init(led_cdev, &led_fops); //初始化cdev的file_operations,使字符设备与操作的函数绑定,led_fops结构体中的成员指向驱动提供的功能函数
result = cdev_add(led_cdev, devno, dev_count); //cdev_add为注册设备函数,通常发生在驱动模块的加载函数中并将返回值传递给result,为0则表示成功。
if (0 != result)
{
printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result);
goto ERROR; //注册失败则打印错误并跳转到出错处理
}
printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n",
DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);//注册成功则打印设备信息
return 0;
ERROR:
printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
cdev_del(led_cdev); //注销设备,通常发生在驱动模块的卸载函数中
unregister_chrdev_region(devno, dev_count); //释放注册的设备号
return result;
}
static void __exit s3c_led_exit(void) //卸载驱动模块函数
{
dev_t devno = MKDEV(dev_major, dev_minor);
s3c_hw_term();
cdev_del(led_cdev);
unregister_chrdev_region(devno, dev_count);
printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n",
DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);
return ;
}
/* These two functions defined in <linux/init.h> */
module_init(s3c_led_init); //insmod加载内核模块定义的宏
module_exit(s3c_led_exit); //rmmod卸载模块定义的宏,退出内核模块
module_param(debug, int, S_IRUGO); /*传递命令行参数module_param使用了3个参数:变量名,它的类型,以及一个权限掩码用来做一个辅助的sysfs入口。
这个宏定义应当放在任何函数之外,典型地是出现在源文件的前面*/
module_param(dev_major, int, S_IRUGO);
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC); // 对这个模块作一个简单的描述,这个描述是"human-readable"的
MODULE_LICENSE("GPL"); // "GPL" 是指明了 这是GNU General Public License的任意版本 http://blog.csdn.net/lihaoweiv/article/details/6602261
vim Makefile
Makefile
1 C=/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc
2 KDIR?=/home/fanmaolin/fl2440/kernel/linux-3.0
3 obj-m:=s3c_led.o
4
5 default:
6 $(MAKE) -C $(KDIR) M=`pwd` modules
7 make clean
8
9 clean:
10 rm -f *.o *mod.c *.order *.symvers
~
~
~
~
make
生成.ko文件
编写测试程序(一次跑马灯):
vi testled.c
1 /*********************************************************************************
2 * Copyright: (C) 2017 fanmaolin<fanmaolinn@gmail.com>
3 * All rights reserved.
4 *
5 * Filename: testled.c
6 * Description: This file test led driver
7 *
8 * Version: 1.0.0(03/25/2017)
9 * Author: fanmaolin <fanmaolinn@gmail.com>
10 * ChangeLog: 1, Release initial version on "03/25/2017 09:36:47 AM"
11 *
12 ********************************************************************************/
13 #include <sys/ioctl.h>
14 #include <sys/stat.h>
15 #include <unistd.h>
16 #include <stdarg.h>
17 #include <errno.h> //自己这些头文件要看自己用哪些函数,用man命令查看
18 #include <sys/types.h>
19 #include <fcntl.h>
20 #include <string.h>
21 #include <stdio.h>
22
23 #define LED_NUM 4 //
1 /*********************************************************************************
2 * Copyright: (C) 2017 fanmaolin<fanmaolinn@gmail.com>
3 * All rights reserved.
4 *
5 * Filename: testled.c
6 * Description: This file test led driver
7 *
8 * Version: 1.0.0(03/25/2017)
9 * Author: fanmaolin <fanmaolinn@gmail.com>
10 * ChangeLog: 1, Release initial version on "03/25/2017 09:36:47 AM"
11 *
12 ********************************************************************************/
13 #include <sys/ioctl.h>
14 #include <sys/stat.h>
15 #include <unistd.h>
16 #include <stdarg.h>
17 #include <errno.h>
18 #include <sys/types.h>
19 #include <fcntl.h>
20 #include <string.h>
21 #include <stdio.h>
22
23 #define LED_NUM 4 //定义LED_NUM为4
24 #define DEVNAME_LEN 10 //定义DEVNAME_LEN 为10 这样做是方便以后修改这些参数时不用在代码里一个个修改,可以直接在定义这里修改
25
26 #define PLATDRV_MAGIC 0x60 //魔数,避免冲突
27 #define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
28 #define LED_ON _IO (PLATDRV_MAGIC, 0x19)
29
30 int main(int argc , char **argv)
31 {
32 int fd[LED_NUM];
33 int i;
34 int j;
35
36 char devname[DEVNAME_LEN]={0}; //字符串数组为0
37
38 for(i=0;i<LED_NUM;i++)
39 {
40 snprintf(devname, sizeof(devname), "/dev/led%i",i);//snprint函数,打印设备名
41 fd[i]= open(devname, O_RDWR,755);//依次打开设备,并设置为读写模式,给权限755,r为4,w为2,x为1
42 if(fd< 0) //如果出错,打印出错信息,并释放占用的空间,返回-1,
43 {
44 printf("Can not open %s: %s", devname, strerror(errno));
45 for(j=0;j<LED_NUM;j++)
46 {
47 close(fd[j]);
48
49 }
50 return -1;
51 }
52 }
53
54 for(i=0;i<LED_NUM;i++) //循环一次,一次灯亮,如果想一直循环下去加一个while语句就可以,条件设置为1
55 {
56 ioctl(fd[i], LED_ON);
57 sleep(1); //延时1s
58 ioctl(fd[i], LED_OFF);
59
60 }
61
62 for(i=0;i<LED_NUM;i++) //释放空间
63 {
64 close(fd[i]);
65
66 }
67
68 return 0;
69
70
71 }
[fanmaolin@Centeros led]$ /opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-g
arm-linux-g++ arm-linux-gcc arm-linux-gcc-4.5.4 arm-linux-gccbug arm-linux-gcov arm-linux-gprof
[fanmaolin@Centeros led]$ /opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc testled.c
[fanmaolin@Centeros led]$ ls
1 a.out led.c Makefile s3c_led.c s3c_led.ko testled.c
[fanmaolin@Centeros led]$ sz a.out
rz
Starting zmodem transfer. Press Ctrl+C to cancel.
Transferring a.out...
100% 5 KB 5 KB/s 00:00:01 0 Errors
arm-linux-g++ arm-linux-gcc arm-linux-gcc-4.5.4 arm-linux-gccbug arm-linux-gcov arm-linux-gprof
[fanmaolin@Centeros led]$ /opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc testled.c
[fanmaolin@Centeros led]$ ls
1 a.out led.c Makefile s3c_led.c s3c_led.ko testled.c
[fanmaolin@Centeros led]$ sz a.out
rz
Starting zmodem transfer. Press Ctrl+C to cancel.
Transferring a.out...
100% 5 KB 5 KB/s 00:00:01 0 Errors
在开发板上执行:
: tftp -gr a.out 192.168.1.2
a.out 100% |*******************************| 5613 0:00:00 ETA
>: ls
a.out data info linuxrc root sys var
apps dev init mnt s3c_led.ko tmp
bin etc lib proc sbin usr
>: insmod s3c_led.ko
S3C led driver[major=250] version 1.0.0 installed successfully!
a.out 100% |*******************************| 5613 0:00:00 ETA
>: ls
a.out data info linuxrc root sys var
apps dev init mnt s3c_led.ko tmp
bin etc lib proc sbin usr
>: insmod s3c_led.ko
S3C led driver[major=250] version 1.0.0 installed successfully!
>: mknod /dev/led0 c 250 0
>: mknod /dev/led1 c 250 1
>: mknod /dev/led2 c 250 2
>: mknod /dev/led3 c 250 3
>: ls -l dev/led*
crw-r--r-- 1 root root 250, 0 Dec 31 17:34 dev/led0
crw-r--r-- 1 root root 250, 1 Dec 31 17:34 dev/led1
crw-r--r-- 1 root root 250, 2 Dec 31 17:34 dev/led2
crw-r--r-- 1 root root 250, 3 Dec 31 17:35 dev/led3
>: mknod /dev/led1 c 250 1
>: mknod /dev/led2 c 250 2
>: mknod /dev/led3 c 250 3
>: ls -l dev/led*
crw-r--r-- 1 root root 250, 0 Dec 31 17:34 dev/led0
crw-r--r-- 1 root root 250, 1 Dec 31 17:34 dev/led1
crw-r--r-- 1 root root 250, 2 Dec 31 17:34 dev/led2
crw-r--r-- 1 root root 250, 3 Dec 31 17:35 dev/led3
>: ll a.out
-rw-r--r-- 1 root root 5613 Dec 31 17:02 a.out
>: chmod 777 a.out
>: ll a.out
-rwxrwxrwx 1 root root 5613 Dec 31 17:02 a.out
>: ./a.out
>: ./a.out
>: lsmod
s3c_led 2103 0 - Live 0xbf000000
-rw-r--r-- 1 root root 5613 Dec 31 17:02 a.out
>: chmod 777 a.out
>: ll a.out
-rwxrwxrwx 1 root root 5613 Dec 31 17:02 a.out
>: ./a.out
>: ./a.out
>: lsmod
s3c_led 2103 0 - Live 0xbf000000
就可以看到灯依次亮一次了。。。
问题总结分析:
1、
>: ./a.out
Can not open /dev/led0: No such device>: ./a.out
>: insmod s3c_led.ko
S3C led driver[major=250] version 1.0.0 installed successfully!
注意这里的设备号,mknod后面的设备号修改后正常
要先用rm -rf filename 删除在/dev下之前创建的错误的节点
2、
在编程的时候要注意编程规范,还有关于snprintf()
int snprintf(char *restrict buf, size_t n, const char * restrict format, ...);
函数说明:最多从源串中拷贝n-1个字符到目标串中,然后再在后面加一个0。所以如果目标串的大小为n 的话,将不会溢出。
函数返回值:若成功则返回欲写入的字符串长度,若出错则返回负值。