linux设备树文件夹,【图片】linux设备树详解【韦东山吧】_百度贴吧

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

第02节_字符设备驱动的传统写法

在上一节视频里我们介绍了三种编写驱动的方法,也对比了它们的优缺点,后面我们将使用比较快速的方法写出驱动程序,因为写驱动程序不是我们这套视频的重点,所以尽快的把驱动程序写出来,给大家展示一下。

这节视频我们使用传统的方法编写字符驱动程序,以最简单的点灯驱动程序为示例。

先回顾下写字符设备驱动的五个步骤:

1.2.3.分配/设置/注册file_operations

4.入口

5.出口

所谓分配file_operations,我们可以定义一个file_operations结构体,就不需要分配了。

static struct file_operations myled_oprs = {

.owner = THIS_MODULE, //表示这个模块本身

.open = led_open,

.write = led_write,

.release = led_release,

};

定义好了file_operations结构体,再去入口函数注册结构体。

static int myled_init(void)

{

major = register_chrdev(0, "myled", &myled_oprs); return 0;

}

第一个参数:主设备号写0,让系统为我们分配;

第二个参数:设置名字,没有特殊要求;

第三个参数:file_operations结构体;

对应的出口操作进行相反向操作:

static void myled_exit(void)

{

unregister_chrdev(major, "myled");

}

然后用宏module_init对入口、出口函数进行修饰,表示它们和普通函数不一样:

module_init(myled_init);

module_exit(myled_exit);

module_init(myled_init)实际就是int init_module(void) __attribute__((alias("myled_init"))),表示myled_init的别名是init_module,以后就可以使用init_module来引用myled_init。

此外,还要加上GPL协议:

MODULE_LICENSE("GPL");

写到这里,驱动程序的框架已经搭建起来了,接下来实现具体的硬件操作函数:led_open()和led_write()。

在led_open()里把对应的引脚配置为输出引脚,在led_write()根据应用程序传入的数据点灯,让其输出高电平或低电平。

为了让程序更具有扩展性,把GPIO的寄存器放在一个数组里:

static unsigned int gpio_base[] = {

0x56000000, /* GPACON */

0x56000010, /* GPBCON */

0x56000020, /* GPCCON */

0x56000030, /* GPDCON */

0x56000040, /* GPECON */

0x56000050, /* GPFCON */

0x56000060, /* GPGCON */

0x56000070, /* GPHCON */

0, /* GPICON */

0x560000D0, /* GPJCON */

};

定义好了引脚的组,还得确定使用该组的哪个引脚,使用宏来确定哪个引脚:

#define S3C2440_GPA(n) (0<<16 | n)

#define S3C2440_GPB(n) (1<<16 | n)

#define S3C2440_GPC(n) (2<<16 | n)

#define S3C2440_GPD(n) (3<<16 | n)

#define S3C2440_GPE(n) (4<<16 | n)

#define S3C2440_GPF(n) (5<<16 | n)

#define S3C2440_GPG(n) (6<<16 | n)

#define S3C2440_GPH(n) (7<<16 | n)

#define S3C2440_GPI(n) (8<<16 | n)

#define S3C2440_GPJ(n) (9<<16 | n)

后面就可以向对应宏传入对应位,得到对应组的对应引脚。

查看原理图,知道我们要使用的引脚是GPF5,因此定义 led_pin = s3c2440_GPF(5)。

static int led_open (struct inode *node, struct file *filp)

{

/* 把LED引脚配置为输出引脚 */

/* GPF5 - 0x56000050 */

int bank = led_pin >> 16;

int base = gpio_base[bank]; int pin = led_pin & 0xffff;

gpio_con = ioremap(base, 8);

if (gpio_con) {

printk("ioremap(0x%x) = 0x%x\n", base, gpio_con);

}

else {

return -EINVAL;

}

gpio_dat = gpio_con + 1; *gpio_con &= ~(3<

*gpio_con |= (1<

}

在Linux中,不能直接操作基地址,需要使用ioremap()映射。

对于基地址,定义全局指针来表示,gpio_con表示控制寄存器,gpio_dat表示数据寄存器。

这里将GPF5的第二个引脚先清空,再设置为1,表示输出引脚。

接下来是写函数:

static ssize_t led_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)

{

/* 根据APP传入的值来设置LED引脚 */

unsigned char val;

int pin = led_pin & 0xffff;

copy_from_user(&val, buf, 1); if (val)

{

/* 点灯 */

*gpio_dat &= ~(1<

}

else

{

/* 灭灯 */

*gpio_dat |= (1<

} return 1; /* 已写入1个数据 */

}

注意这里的__user宏起强调作用,告诉你buf来自应用空间,在内核里不能直接使用。

使用copy_from_user()将用户空间的数据拷贝到内核空间。

再根据传入的值,设置gpio_dat的值,来点亮或者熄灭pin所对应的灯。

至此,这个驱动程序已经具备操作硬件的功能,但我们还要增加一些内容,比如我们先注册驱动后,自动创建节点信息。

在入口函数里,使用class_create()创建class,并且使用device_create()创建设备。

static int myled_init(void)

{

major = register_chrdev(0, "myled", &myled_oprs); led_class = class_create(THIS_MODULE, "myled");

device_create(led_class, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */ return 0;

}

出口函数需要进行相反操作:

static void myled_exit(void)

{

unregister_chrdev(major, "myled");

device_destroy(led_class, MKDEV(major, 0));

class_destroy(led_class);

}

还有在release函数里,释放前面的iormap()的资源

static int led_release (struct inode *node, struct file *filp)

{

printk("iounmap(0x%x)\n", gpio_con);

iounmap(gpio_con);

return 0;

}

最后把以前的测试程序拷贝过来,简单修改一下,见网盘led_driver/001_led_drv_traditional/ledtest.c。

可以看出,这种传统写驱动程序的方法把硬件资源写在了代码里,换个LED,换个引脚,就得去修改 led_pin = s3c2440_GPF(5),然后重新编译,加载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值