Beaglebone Black GPIO 设备驱动程序
这篇文章将简要介绍 Linux 设备驱动程序,实际上是关于一个控制 Beaglebone Black 的 GPIO(通用输入/输出)端口的字符设备驱动程序。
引言
这篇文章将简要介绍 Linux 设备驱动程序,实际上是关于一个控制 Beaglebone Black 的 GPIO(通用输入/输出)端口的字符设备驱动程序。
背景
前段时间,我参与了一个 Beaglebone Black 上的项目,该项目需要控制一些 IO,UART 端口,还需要做出一些决策并通过套接字与 PC 通信。那个项目中比较有趣的部分是时间限制,软件必须在指定的时间内做出决策或等待输入。例如,它必须以 1 KHz 的频率将输出端口的状态从 0 更改为 1,它必须每毫秒更改一次值。我通过使用代码优化技术和编写一些设备驱动程序设法满足了时间限制。
使用代码
该驱动程序使用 IOCTL 执行端口的基本设置,例如导出引脚、设置方向输入或输出、使用捕获事件,例如上升沿、下降沿等。在接下来的段落中,我将介绍一些函数的源代码,以及如何使用此设备驱动程序的示例。
数据类型和 IOCTL 选项
为了在用户空间和内核空间之间传输数据以及 IOCTL,我使用了一个结构体,该结构体包含用于读写操作的两个缓冲区、被控制的引脚号以及中断号(如果使用中断读取数据)。
struct bbbgpio_ioctl_struct
{
u16 gpio_number;
u8 write_buffer;
u8 read_buffer;
int irq_number;
};
在读取和写入数据之前应进行的设置、驱动程序的工作模式等,都是使用 IOCTL 实现的。为此设备驱动程序定义的所有请求都是
#define IOCBBBGPIORP _IOW(_IOCTL_MAGIC,1,struct bbbgpio_ioctl*) /*IOCTL used for registering PIN*/
#define IOCBBBGPIOUP _IOW(_IOCTL_MAGIC,2,struct bbbgpio_ioctl*) /*IOCTL used for unregistering PIN*/
#define IOCBBBGPIOWR _IOW(_IOCTL_MAGIC,3,struct bbbgpio_ioctl*) /*write data to register*/
#define IOCBBBGPIORD _IOR(_IOCTL_MAGIC,4,struct bbbgpio_ioctl*) /*read from register*/
#define IOCBBBGPIOSD _IOW(_IOCTL_MAGIC,5,struct bbbgpio_ioctl*) /*set direction*/
#define IOCBBBGPIOSL0 _IOW(_IOCTL_MAGIC,6,struct bbbgpio_ioctl*) /*set low detect*/
#define IOCBBBGPIOSH1 _IOW(_IOCTL_MAGIC,7,struct bbbgpio_ioctl*) /*set high detect*/
#define IOCBBBGPIOSRE _IOW(_IOCTL_MAGIC,8,struct bbbgpio_ioctl*) /*set rising edge*/
#define IOCBBBGPIOSFE _IOW(_IOCTL_MAGIC,9,struct bbbgpio_ioctl*) /*set falling edge*/
#define IOCBBBGPIOSIN _IOW(_IOCTL_MAGIC,10,struct bbbgpio_ioctl*) /*enable gpio interrupt*/
#define IOCBBBGPIOSBW _IOW(_IOCTL_MAGIC,11,struct bbbgpio_ioctl*) /*enable gpio busy wait mode*/
写入数据
在这个设备驱动程序中,最容易实现的部分是设置端口输出状态的函数,该函数应该从用户空间获取一个值并将其写入端口的寄存器。
static ssize_t
bbbgpio_write(struct file *filp, const char __user *buffer, size_t length, loff_t *offset)
{
if (mutex_trylock(&bbbgpiodev_Ptr->io_mutex) == 0) {
driver_err("%s:Mutex not free!\n",DEVICE_NAME);
return -EBUSY;
}
if (copy_from_user(&ioctl_buffer,buffer,sizeof(struct bbbgpio_ioctl_struct)) != 0) {
driver_err("%s:Could not copy data from userspace!\n",DEVICE_NAME);
mutex_unlock(&bbbgpiodev_Ptr->io_mutex);
return -EINVAL;
}
gpio_set_value(ioctl_buffer.gpio_number,ioctl_buffer.write_buffer);
mutex_unlock(&bbbgpiodev_Ptr->io_mutex);
return 0;
}
此函数将尝试锁定一个互斥锁,以防止对共享数据的并发访问,如果失败,将返回 EBUSY。如果 mutex_trylock 返回的值为 0,则表示成功,将从用户空间复制缓冲区的的内容,如果操作成功,则输出值将使用 write_buffer 中的值进行更新。
读取数据
一个更具挑战性的部分是实现读取功能,因为设备驱动程序必须以忙等待模式或使用中断工作。
忙等待模式
在忙等待模式下,驱动程序将读取端口的值并将其发送到用户空间。此模式旨在用于在不使用任何触发器的情况下连续捕获数据。
static ssize_t bbbgpio_read(struct file *filp,char __user *buffer,size_t length,loff_t *offset) { if (mutex_trylock(&bbbgpiodev_Ptr->io_mutex) == 0) { driver_err("%s:Mutex not free!\n",DEVICE_NAME); return -EBUSY; } if (copy_from_user(&ioctl_buffer,buffer,sizeof(struct bbbgpio_ioctl_struct)) != 0) { driver_err("%s:Could not copy data from userspace!\n",DEVICE_NAME); mutex_unlock(&bbbgpiodev_Ptr->io_mutex); return -EINVAL; } ioctl_buffer.read_buffer=gpio_get_value(ioctl_buffer.gpio_number); if (copy_to_user(buffer,&ioctl_buffer,sizeof(struct bbbgpio_ioctl_struct)) !=0 ) { driver_err("\t%s:Cout not write values to user!\n",DEVICE_NAME); mutex_unlock(&bbbgpiodev_Ptr->io_mutex); return -EINVAL; } mutex_unlock(&bbbgpiodev_Ptr->io_mutex); return 0; }
读取函数与写入函数有些相似。首先,它将尝试锁定一个互斥锁,如果操作成功,它将从用户空间复制缓冲区的值。此操作是必需的,因为我想知道我要读取哪个引脚。接下来,它将读取引脚的值,然后更新缓冲区并将其复制回用户空间。
中断模式
中断模式的设计是因为我希望驱动程序对事件做出反应,并且仅在检测到从 HIGH 到 LOW 的转换或上升/下降沿时才开始捕获数据。数据在 ISR(中断服务例程)中捕获,保存在一个环形缓冲区中,并且仅在调用带有 IOCBBBGPIORD 请求的 ioctl 时,才会将其传输到用户空间。
static irq_handler_t irq_handler(int irq,void *dev_id,struct pt_regs *regs) { u16 io_number=(u16)dev_id; struct bbb_data_content content; if (mutex_trylock(&bbbgpiodev_Ptr->io_mutex) == 0) { driver_err("%s:Mutex not free!\n",DEVICE_NAME); goto exit_interrupt; } content.data=gpio_get_value(io_number); content.dev_id=io_number; bbb_buffer_push(&bbb_data_buffer,content); mutex_unlock(&bbbgpiodev_Ptr->io_mutex); driver_info("Interrup handler executed!\n"); exit_interrupt:{ return (irq_handler_t) IRQ_HANDLED; } }
当调用中断服务例程时,将锁定互斥锁,将从引脚读取数据并将其保存在环形缓冲区 bbb_data_buffer 中。
IOCBBBGPIORD ioctl 请求在函数 static long bbbgpio_ioctl(struct file *file, unsigned int ioctl_num ,unsigned long ioctl_param) 中处理,在该函数中,会检查环形缓冲区是否为空,如果不是,则将从其中获取数据并将其复制到用户空间。
示例应用程序
如何使用此设备驱动程序的示例可以在 这里 找到。
在项目的 Makefile 中,我还添加了一些安装和卸载驱动程序的步骤。
FILE=bbbgpio
obj-m += $(FILE).o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
install:
sudo insmod ./$(FILE).ko
cp bbbgpio_ioctl.h /usr/include/
run:
dmesg
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
sudo rmmod $(FILE)
关注点
这个项目给我带来了很多乐趣,一两个不眠之夜,以及一点沮丧,因为我不想使用 linux/gpio.h 而是直接将值写入寄存器。 我通过直接访问寄存器实现了读写操作,但我卡在了检测中断号上,我无法探测和注册用于边沿检测的中断。
完整的源代码可以在 这里 找到。
历史
- 初始提交
- 工作版本