注意参考内核例程。
字符设备驱动程序框架。
1.确定主设备号;
2.定义自己的file_operations结构体;
3.实现对应的open、read、write等函数,填入file_operations结构体;
4.把file_operations告诉(注册)内核;register_chrdev;
5.入口函数,用来注册驱动程序,安装驱动程序时,会调用这个函数;
6.有入口函数就有出口函数;卸载驱动时,出口函数调用unregister_chrdev;
7.其他完善,提供设备信息,自动创建设备节点:class_create,device_create;
一、驱动
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
static int major;
static struct class *led_class;
/* 寄存器 */
//RCC PLL4 Control Register (RCC_PLL4CR) 0x50000000+0x894 //使能PLL4时钟
static volatile unsigned int *RCC_PLL4CR;
//RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28 //使能GPIO
static volatile unsigned int *RCC_MP_AHB4ENSETR;
//GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01, //PA10输出
static volatile unsigned int *GPIOA_MODER;
//GPIOA_ODR地址: 0x50002000 + 0x14 //读寄存、修改值、写回去(低效)
static volatile unsigned int *GPIOA_ODR;
//GPIOA_BSRR地址: 0x50002000 + 0x18 // 直接写寄存器,一次操作即可,高效
static volatile unsigned int *GPIOA_BSRR;
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
printk("%s %s %d\n",__FILE__, __FUNCTION__,__LINE__);
char val;
/* copy_from_user: 从应用程序中获取数据*/
copy_from_user(&val, buf, 1);
/* 设置数据,设置gpio输出,0/1 */
if(val)
{
//灯亮
*GPIOA_BSRR = (1<<26); //输出低电平 GPIOA10
}
else
{
//灯灭
*GPIOA_BSRR = (1<<10); //输出高电平
}
return 1;
}
static int led_open(struct inode *inode, struct file *filp)
{
/* 配置gpio,使能、配置为gpio,配置输入输出 */
//配置时钟
*RCC_PLL4CR |= (1<<0);
while((*RCC_PLL4CR & (1<<1)) == 0);
//使能GPIOA
*RCC_MP_AHB4ENSETR |= (1<<0);
//设置PA10用作输出,先清零,在设置为输出
*GPIOA_MODER &= ~(3<<20);
*GPIOA_MODER |= (0x01<<20);
return 0;
}
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
//.read = led_read,
.write = led_write,
.open = led_open,
//.release = led_close,
//.poll = led_poll,
//.llseek = no_llseek,
};
/* 入口函数 */
static int __init led_init(void)
{
major = register_chrdev(0, "tanzhenwen_led", &led_fops); //在/proc/devices会显示
if (major < 0) {
printk("could not get major number\n");
class_destroy(led_class);
return major;
}
led_class = class_create(THIS_MODULE, "led_calss");
//if (IS_ERR(led_class))
// return PTR_ERR(led_class);
device_create(led_class, NULL, MKDEV(major, 0), NULL, "led_drv"); // /dev/led_drv
/* ioremap :使用虚拟地址,后期可以用虚拟地址来访问寄存器 */
//ioremap(mem_addr, mem_len);
//RCC PLL4 Control Register (RCC_PLL4CR) 0x50000000+0x894 //使能PLL4时钟
RCC_PLL4CR = ioremap(0x50000000+0x894, 4);
//RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28 //使能GPIO
RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);
//GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01, //PA10输出
GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);
//GPIOA_ODR地址: 0x50002000 + 0x14 //读寄存、修改值、写回去(低效)
GPIOA_ODR = ioremap(0x50002000 + 0x14, 4);;
//GPIOA_BSRR地址: 0x50002000 + 0x18 // 直接写寄存器,一次操作即可,高效
GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);
return 0;
}
static void __exit led_exit(void)
{
iounmap(RCC_PLL4CR);
iounmap(RCC_MP_AHB4ENSETR);
iounmap(GPIOA_MODER);
iounmap(GPIOA_ODR);
iounmap(GPIOA_BSRR);
unregister_chrdev(major, "tanzhenwen_led");
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
二、测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
//ledtest /dev/led_drv on
//ledtest /dev/led_drv off
int main(int argc, char *argv[])
{
int fd;
char status = 0;
if(argc != 3 )
{
printf("Usage: %s <dev> <on|off>\n",argv[0]);
printf(" eg: %s /dev/led_drv on\n");
printf(" eg: %s /dev/led_drv off\n");
}
//open
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("can not open %s\n",argv[1]);
return -1;
}
//write
if(strcmp(argv[2],"on") == 0)
{
status = 1;
}
else
{
status = 0;
}
write(fd,&status,1);
return 0;
}
三、测试
让心跳灯不闪烁
echo none > /sys/class/leds/heartbeat/trigger
cat /proc/devices可以看见tanzhenwen_led字符设备节点
执行ls /dev/led_drv -al,可以看见具有led_drv的设备。
执行./led_test /dev/led_drv off以及./led_test /dev/led_drv on可以观察到LED灯的亮灭。