本片文章教大家如何入门一个简单的驱动程序。我们都知道驱动程序是用来操作设备来完成相应的功能的,所以它应该是由内核来调用的。
我们先看如何从用户空间来调用一个驱动程序,它的具体流程是怎样的?
我们先来认识一下这两个函数:
module_init(s3c_led_init);
module_exit(s3c_led_exit);
这两个函数我们一般放在驱动程序的末尾,当我们调用insmod注册一个驱动程序的时候,内核会先执行module_init这个函数,因为这个函数会把相应的操作放在文本段的前面,所以里面 的参数是我们初始化的代码。调用rmmod就会调用module_exit(s3c_led_exit);来释放设备号和设备,不详述。
static int __init s3c_led_init(void)
{
int result;
dev_t devno;
if (0 != dev_major)
{
devno = MKDEV(dev_major, 0);
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);
}
if (result < 0)
{
printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);
}
printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);
if(NULL == (led_cdev=cdev_alloc()) )
{
printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
unregister_chrdev_region(devno, dev_count);
}
led_cdev->owner = THIS_MODULE;
cdev_init(led_cdev, &led_fops);
result = cdev_add(led_cdev, devno, dev_count);
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]installed successfully!\n",DEV_NAME, dev_major);
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;
}
register_chrdev_region函数表示注册一个设备号(主动注册你想要的设备号,注册前要注意不能与已有的设备号重复),它会向内核申请一个空闲的设备号,因为在内核中我们的驱动程序是通过设备号和与相应的设备联系起来。
alloc_chrdev_region表示动态申请一个设备号,申请过程由内核完成。
我们申请了一个设备号,就会涉及到释放它,此时用
unregister_chrdev_region函数
申请完设备号后,我们就要申请与之对应的字符设备,cdev_alloc()函数返回一个字符设备结构cdev的指针,然后我们要对这个设备进行初始化用函数 cdev_init(led_cdev, &led_fops);&led_fop是用来初始化函数的具体操作的地址,点灯就可以通过这个函数接口来具体执行。接下来告诉内核这个设备的信息cdev_add(led_cdev, devno, dev_count);如果一切顺利则注册成功。
然后我们要跳到led_fop这个函数去看一下怎样去操作设备。
static struct file_operations led_fops={
.owner = THIS_MODULE,
.open = s3c_led_open,
.write = s3c_led_write,
};
static int s3c_led_open(struct inode *inode, struct file *file)
{
//printk("open the led\n");
GPBCON = ioremap(S3C_GPB_BASE, S3C_GPB_LEN);
GPBDAT = GPBCON + 1;
*GPBCON &= ~((0x3<<(5*2)) | (0x3<<(6*2)) | (0x3<<(8*2)) | (0x3<<(10*2)));
*GPBCON |= ((0x1<<(5*2)) | (0x1<<(6*2)) | (0x1<<(8*2)) | (0x1<<(10*2)));
return 0;
}
static ssize_t s3c_led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int val;
//printk("write the led\n");
copy_from_user(&val, buf, count);
if (val==1)
{
*GPBDAT &= ~((0x1<<5) | (0x1<<6) | (0x1<<8) | (0x1<<10));
}
else
{
*GPBDAT |= ((0x1<<5) | (0x1<<6) | (0x1<<8) | (0x1<<10));
}
return 0;
}
这上面有两个具体操作的函数s3c_led_open和s3c_led_write。
在s3c_led_open中我完成了LED相应寄存器物理地址到虚拟地址的映射以及把GPBCON相应引脚设置输出模式。然后我又在
s3c_led_write中通过copy_from_user(&val, buf, count);来完成用户空间缓冲区数据到内核中的拷贝,然后通过传进来的数值来控制LED灯的亮灭。
int main (int argc, char **argv)
{
int fd;
int val = 1;
fd = open("dev/led", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
if (argc != 2)
{
printf("usag :\n");
printf("%s on/off\n", argv[1]);
}
if (strcmp(argv[1],"on") == 0)
{
val = 1;
}
else{
val =0;
}
write(fd, &val, 4);
return 0;
}
用户程序打开相应的设备文件,然后用argv[1] “on/off”来完成值的控制,从而把值写进缓冲区。
然后insmod相应的驱动程序,在创建设备节点,最后用应用程序点灯就行了。