一、字符设备驱动1-点灯

概念

U-boot:启动内核

内核:启动应用

应用:只调用open, read, write...等标准接口操作硬件,不去关心硬件相关的具体操作

驱动:以led为例,与应用接口对应包含led_open, led_read, led_write...

框架

1. 写出led_openled_write函数。

2. 如何告诉内核?

A. 定义一个file_operations结构体,并填充它。

B. 调用register_chardev向内核注册这个结构体。

   B1. 谁来调用register_chardev-> 驱动的入口函数:led_init

   B2. 为什么led_init是入口函数? -> 通过module_init来修饰led_init

      B2.1 module_init是什么?一个宏,定义一个结构体,里边有个函数指针指向led_init,当加载驱动时,内核会自动找到这个结构体,然后调用这个指针指向的入口函数。

3. 卸载驱动函数:

   出口函数调用unregister_chardev,用module_exit来修饰这个函数,以向内核表明这是一个出口函数。

4. 应用程序调用open如何找到相应的file_operations结构体?

   根据设备类型,主设备号查找。由此,可引发对register_chardev简单实现方式的推断:

   内核中有一个chardev[]数组,register_chardev根据传入的major填充这个数组中的相应元素。

5. 自动创建设备节点:udev机制,对于busybox来说是mdev

   使用mdev机制:根据系统信息自动创建设备节点。

   怎么提供系统信息?

   入口函数中调用:class_create ,  class_device_create

   出口函数中调用:class_device_unregister,  class_destroy

   这会导致在/sys/目录下生成相应的信息,mdev即可根据这些信息自动创建设备节点。

 

杂项

cat  /proc/devices 查看内核当前支持的设备,打印信息的第一列是主设备号,第二列是名字

mknod   /dev/xxx   c  major   minor  手工创建设备节点

驱动程序中不能直接操作物理地址,需要用ioremap映射到虚拟地址,再去操作。

问:应用如何找到对应的驱动?

答:根据打开文件的属性。以字符设备为例,设备文件属性有:字符设备、主设备号等。根据这两点,就可以在前述的chardev[]数组中找到,之后的read, write, ioctl就会使用这个数组项中的成员函数。

exec 5</dev/buttons    打开/dev/buttons定位到5,可用ls -l /proc/771/fd查看;771表示shPID,可用ps命令获得

exec 5<&-    关闭上面打开的/dev/buttons


源码

驱动:

/*
 * 引脚:底板 - PH6, PH7
 *       核心板 - PB4
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <asm/io.h>


static int major;

static struct class *leds_class;
static struct device *led_device[4];

static volatile unsigned int *gpio_base = NULL;
static volatile unsigned int *gpiob_dir, *gpiob_dataout;
static volatile unsigned int *gpioh_dir, *gpioh_dataout;

static volatile unsigned int *sys_base = NULL;
static volatile unsigned int *sys_gpb_mfpl;
static volatile unsigned int *sys_gph_mfpl;


static int leds_open(struct inode *inode, struct file *filp)
{
	int minor = MINOR(inode->i_rdev);

	switch (minor)
	{
		case 0:
		{
			*sys_gpb_mfpl &= ~(0xf << (4 * 4));
			*sys_gph_mfpl &= ~((0xf << (6 * 4)) | (0xf << (7 * 4)));

			*gpiob_dir |= 1 << 4;
			*gpioh_dir |= (1 << 6) | (1 << 7);
			break;
		}
		case 1:
		{
			*sys_gpb_mfpl &= ~(0xf << (4 * 4));
			*gpiob_dir |= 1 << 4;
			break;
		}
		case 2:
		{
			*sys_gph_mfpl &= ~(0xf << (6 * 4));
			*gpioh_dir |= (1 << 6);
			break;
		}
		case 3:
		{
			*sys_gph_mfpl &= ~(0xf << (7 * 4));
			*gpioh_dir |= (1 << 7);
			break;
		}
		default:
		{
			return -EINVAL;
		}
	}

	return 0;
}

static ssize_t leds_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
	int val;
	int minor = MINOR(filp->f_dentry->d_inode->i_rdev);

	copy_from_user(&val, buf, count);

	switch (minor)
	{
		case 0:
		{
			if (val == 0)
			{
				*gpiob_dataout |= (1 << 4);
				*gpioh_dataout |= (1 << 6) | (1 << 7);
			}
			else 
			{
				*gpiob_dataout &= ~(1 << 4);
				*gpioh_dataout &= ~((1 << 6) | (1 << 7));
			}
			break;
		}
		case 1:
		{
			if (val == 0)
			{
				*gpiob_dataout |= (1 << 4);
			}
			else 
			{
				*gpiob_dataout &= ~(1 << 4);
			}
			break;
		}
		case 2:
		{
			if (val == 0)
			{
				*gpioh_dataout |= (1 << 6);
			}
			else 
			{
				*gpioh_dataout &= ~(1 << 6);
			}
			break;
		}
		case 3:
		{
			if (val == 0)
			{
				*gpioh_dataout |= (1 << 7);
			}
			else 
			{
				*gpioh_dataout &= ~(1 << 7);
			}
			break;
		}
		default:
		{
			return -EINVAL;
		}
	}

	return 0;
}

static struct file_operations leds_fops = {
	.owner = THIS_MODULE,
	.open  = leds_open,
	.write = leds_write,
};

static int leds_init(void)
{
	int i;
	
	major = register_chrdev(0, "leds", &leds_fops);
	leds_class = class_create(THIS_MODULE, "leds");
	led_device[0] = device_create(leds_class, NULL, MKDEV(major, 0), NULL, "leds");
	for (i = 1; i < 4; i++)
	{
		led_device[0] = device_create(leds_class, NULL, MKDEV(major, i), NULL, "led%d", i);
	}

	gpio_base = (volatile unsigned int *)ioremap(0xb8003000, 0x400);
	gpiob_dir = gpio_base + 0x40 / 4;
	gpiob_dataout = gpio_base + 0x44 / 4;
	gpioh_dir = gpio_base + 0x1c0 / 4;
	gpioh_dataout = gpio_base + 0x1c4 / 4;

	sys_base = (volatile unsigned int *)ioremap(0xb0000000, 0x200);
	sys_gpb_mfpl = sys_base + 0x78 / 4;
	sys_gph_mfpl = sys_base + 0xa8 / 4;
	
	return 0;
}

static void leds_exit(void)
{
	int i; 
	
	iounmap(gpio_base);
	iounmap(sys_base);
	
	for (i = 0; i < 4; i++)
	{
		device_destroy(leds_class, MKDEV(major, i));
	}
	class_destroy(leds_class);
	
	unregister_chrdev(major, "leds");
}

module_init(leds_init);
module_exit(leds_exit);
MODULE_LICENSE("GPL");


测试程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

/* ./ledstest </dev/led*> <on|off|blink> */
int main(int argc, char *argv[])
{
	int fd;
	int val = 0;

	if (argc != 3)
	{
		printf("Usage:\n");
		printf("%s </dev/led*> <on|off>\n", argv[0]);
		return -1;
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("Can't open %s\n", argv[1]);
		return -1;
	}

	if (strcmp(argv[2], "on") == 0)
	{
		val = 1;
	}
	else if (strcmp(argv[2], "off") == 0)
	{
		val = 0;
	}
	else if (strcmp(argv[2], "blink") == 0)
	{
		val = 1;
		while (1)
		{
			write(fd, &val, sizeof(val));
			if (val)
			{
				usleep(50000);
			}
			else 
			{
				sleep(2);
			}
			val ^= 0x1;
		}
	}
	else 
	{
		printf("Usage:\n");
		printf("%s </dev/led*> <on|off>\n", argv[0]);
		return -1;
	}

	write(fd, &val, sizeof(val));

	return 0;
}

猜你喜欢

转载自blog.csdn.net/zhen7620/article/details/80394210
今日推荐