为什么我在通过内核模块访问 GPIO2 和 GPIO3 时在 Beaglebone Black 上出现分段错误?
Why am I getting segmentation fault on Beaglebone Black when accessing GPIO2 and GPIO3 through kernel module?
我一直在尝试通过内核模块访问 beaglebone black 上的 GPIO2 和 GPIO3,但没有成功。每次我尝试将输出值分配给 GPIO 2 和 3 时,我都会遇到分段错误。
完全相同的代码(具有适当的引脚分配)适用于 GPIO0 和 GPIO1。
我尝试了 P8 和 P9 上与 GPIO2 和 GPIO3 相关的各种引脚,但没有成功。另一方面,相同的代码适用于具有适当引脚分配的 GPIO0 和 GPIO1。
引脚值我使用的是BBB官方手册。为了获得适当的 I/O GPIO 可用性,我正在检查来自 beagleboard.com 的图表:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <net/tcp.h>
//Macros
#define GPIO1_START_ADDR 0x4804C000
#define GPIO2_START_ADDR 0x481AC000
#define GPIO2_END_ADDR 0x481ACFFF
#define GPIO3_START_ADDR 0x481AE000
#define SIZE (GPIO2_END_ADDR - GPIO2_START_ADDR)
#define GPIO_OE 0x134
#define GPIO_DATAOUT 0x13C
//A couple of standard descriptions
MODULE_LICENSE("GPL");
static int hello_init(void)
{
volatile void *gpio_addr;
volatile unsigned int *oe_addr;
volatile unsigned int *dataout_addr;
printk(KERN_NOTICE "Module: Initializing module\n");
printk(KERN_NOTICE "Module: Map GPIO\n");
gpio_addr = ioremap(GPIO3_START_ADDR,SIZE);
printk(KERN_NOTICE "Module: Set oe_addr\n");
oe_addr = gpio_addr + GPIO_OE;
printk(KERN_NOTICE "Module: Set dataout_addr\n");
dataout_addr = gpio_addr + GPIO_DATAOUT;
//Code will work up to here for any GPIO.
//It crashes on the following for GPIO2 and GPIO3:
printk(KERN_NOTICE "Module: Set pin to OUTPUT\n");
*oe_addr &= (0xFFFFFFFF ^ (1<<19));
printk(KERN_NOTICE "Module: Set pin output to HIGH\n");
*dataout_addr |= (1<<19);
return 0;
}
static void hello_exit(void)
{
printk(KERN_INFO "Exit module.\n");
}
module_init(hello_init);
module_exit(hello_exit);
如果我屏蔽掉这两行
*oe_addr &= (0xFFFFFFFF ^ (1<<19));
和
*dataout_addr |= (1<<19);
,程序运行所有 GPIO 没有毛刺。
$uname -a: Linux beaglebone 3.8.13-bone79
为什么访问 GPIO2 和 GPIO3 时出现段错误?
你的代码为什么会出现segmentation fault这个答案其实是无关紧要的,因为作为一个内核模块,它是被误导的,需要折腾,需要重写。你的模块绝对没有试图直接访问 "GPIO (control) registers" 的业务,它已经被 pin-control (pinctrl) 子系统所拥有。
GPIO 引脚是内核管理的(通用)资源。您会编写一个驱动程序,它只是开始使用任意内存块作为其缓冲区吗?
希望不会,因为内存是由内核管理的(另一种)资源。
但是你的模块只是随意随意使用GPIO管脚!
请参阅正确的 GPIO 文档以了解您正在使用的确切 Linux 内核版本:Documentation/gpio.txt for version 3.8.13。
您的模块可以使用的可用例程包括:
gpio_request()
gpio_free()
gpio_direction_input()
gpio_direction_output()
gpio_get_value()
gpio_set_value()
(顺便说一句,您的代码忽略了检查 ioremap() 的 return 值,该值可能为空,然后可能导致分段错误。)
经过大量研究,我发现了一些有用的链接,例如 this one and this one。
指出 GPIO 寄存器 1、2 和 3 的默认设置是 时钟禁用,因此在尝试访问寄存器时出现分段错误。当系统请求导出 GPIO 时,它会启用时钟并且 GPIO 寄存器可供使用。
要解决此问题,我们需要手动启用这些 GPIO 的时钟。我无法使用链接中的代码示例执行此操作。
但是通过使用
echo 5 > /sys/class/gpio/export
echo 65 > /sys/class/gpio/export
echo 105 > /sys/class/gpio/export
在 运行 插入 mod 之前,我发现一些东西可以正常工作。通过监控每个 GPIO 上的时钟值,我发现该值从某个值变为“2”。但是,将 2 手动输入到这些值中不足以让 GPIO 工作。
如果我找到通过内存控制正确启用时钟的方法,我将更新此答案。
编辑:
经过更多的关注和研究,我的代码可以正常工作。我把它写成一个单独的 module 并且它是在插入问题上发布的 module 之前插入的:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <net/tcp.h>
#define CM_PER_ADDR 0x44E00000
#define CM_PER_SIZE 0x3FF
#define CM_PER_GPIO1_ADDR 0xAC
#define CM_PER_GPIO2_ADDR 0xB0
#define CM_PER_GPIO3_ADDR 0xB4
#define GPIO_COUNT 3
//A couple of standard descriptions
MODULE_LICENSE("GPL");
static int hello_init(void)
{
static volatile void* cm_per;
static volatile unsigned int* cm_per_gpio[GPIO_COUNT];
static volatile int cm_per_addr[GPIO_COUNT] = {CM_PER_GPIO1_ADDR, CM_PER_GPIO2_ADDR, CM_PER_GPIO3_ADDR};
static int i = 0;
printk(KERN_NOTICE "Module2: Initializing module\n");
cm_per = ioremap(CM_PER_ADDR, CM_PER_SIZE);
if(!cm_per){
printk (KERN_ERR "Error: Failed to map GM_PER.\n");
return -1; //Break to avoid segfault
}
for(i = 0; i < GPIO_COUNT; i++){
cm_per_gpio[i] = cm_per + cm_per_addr[i];
//Check if clock is disabled
if(*cm_per_gpio[i] != 0x2){
printk(KERN_NOTICE "Enabling clock on GPIO[%d] bank...\n", (i+1));
*cm_per_gpio[i] = 0x2; //Enable clock
//Wait for enabled clock to be set
while(*cm_per_gpio[i] != 0x2){}
}
//Print hex value of clock
printk(KERN_NOTICE "cm_per_gpio[%d]: %04x\n", (i+1), *(cm_per_gpio[i]));
}
return 0;
}
static void hello_exit(void)
{
printk(KERN_INFO "Module: Exit module.\n"); //Print exit notice and exit without exploding anythin
}
module_init(hello_init);
module_exit(hello_exit);
从AM335x and AMIC110 Sitara™ ProcessorsTechnical Reference Manual,我们可以看到CM_PER_GPIO#_CLKCTRL Register是如何组织的(#代表我们正在查看的GPIO bank):
Table 8-60。 CM_PER_GPIO2_CLKCTRL 寄存器字段说明
也告诉我们寄存器的复位(默认)值为30000h,意思是CLOCK DISABLED,意思是module disabled.
我一直在尝试通过内核模块访问 beaglebone black 上的 GPIO2 和 GPIO3,但没有成功。每次我尝试将输出值分配给 GPIO 2 和 3 时,我都会遇到分段错误。
完全相同的代码(具有适当的引脚分配)适用于 GPIO0 和 GPIO1。
我尝试了 P8 和 P9 上与 GPIO2 和 GPIO3 相关的各种引脚,但没有成功。另一方面,相同的代码适用于具有适当引脚分配的 GPIO0 和 GPIO1。
引脚值我使用的是BBB官方手册。为了获得适当的 I/O GPIO 可用性,我正在检查来自 beagleboard.com 的图表:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <net/tcp.h>
//Macros
#define GPIO1_START_ADDR 0x4804C000
#define GPIO2_START_ADDR 0x481AC000
#define GPIO2_END_ADDR 0x481ACFFF
#define GPIO3_START_ADDR 0x481AE000
#define SIZE (GPIO2_END_ADDR - GPIO2_START_ADDR)
#define GPIO_OE 0x134
#define GPIO_DATAOUT 0x13C
//A couple of standard descriptions
MODULE_LICENSE("GPL");
static int hello_init(void)
{
volatile void *gpio_addr;
volatile unsigned int *oe_addr;
volatile unsigned int *dataout_addr;
printk(KERN_NOTICE "Module: Initializing module\n");
printk(KERN_NOTICE "Module: Map GPIO\n");
gpio_addr = ioremap(GPIO3_START_ADDR,SIZE);
printk(KERN_NOTICE "Module: Set oe_addr\n");
oe_addr = gpio_addr + GPIO_OE;
printk(KERN_NOTICE "Module: Set dataout_addr\n");
dataout_addr = gpio_addr + GPIO_DATAOUT;
//Code will work up to here for any GPIO.
//It crashes on the following for GPIO2 and GPIO3:
printk(KERN_NOTICE "Module: Set pin to OUTPUT\n");
*oe_addr &= (0xFFFFFFFF ^ (1<<19));
printk(KERN_NOTICE "Module: Set pin output to HIGH\n");
*dataout_addr |= (1<<19);
return 0;
}
static void hello_exit(void)
{
printk(KERN_INFO "Exit module.\n");
}
module_init(hello_init);
module_exit(hello_exit);
如果我屏蔽掉这两行
*oe_addr &= (0xFFFFFFFF ^ (1<<19));
和
*dataout_addr |= (1<<19);
,程序运行所有 GPIO 没有毛刺。
$uname -a: Linux beaglebone 3.8.13-bone79
为什么访问 GPIO2 和 GPIO3 时出现段错误?
你的代码为什么会出现segmentation fault这个答案其实是无关紧要的,因为作为一个内核模块,它是被误导的,需要折腾,需要重写。你的模块绝对没有试图直接访问 "GPIO (control) registers" 的业务,它已经被 pin-control (pinctrl) 子系统所拥有。
GPIO 引脚是内核管理的(通用)资源。您会编写一个驱动程序,它只是开始使用任意内存块作为其缓冲区吗?
希望不会,因为内存是由内核管理的(另一种)资源。
但是你的模块只是随意随意使用GPIO管脚!
请参阅正确的 GPIO 文档以了解您正在使用的确切 Linux 内核版本:Documentation/gpio.txt for version 3.8.13。
您的模块可以使用的可用例程包括:
gpio_request()
gpio_free()
gpio_direction_input()
gpio_direction_output()
gpio_get_value()
gpio_set_value()
(顺便说一句,您的代码忽略了检查 ioremap() 的 return 值,该值可能为空,然后可能导致分段错误。)
经过大量研究,我发现了一些有用的链接,例如 this one and this one。
指出 GPIO 寄存器 1、2 和 3 的默认设置是 时钟禁用,因此在尝试访问寄存器时出现分段错误。当系统请求导出 GPIO 时,它会启用时钟并且 GPIO 寄存器可供使用。
要解决此问题,我们需要手动启用这些 GPIO 的时钟。我无法使用链接中的代码示例执行此操作。
但是通过使用
echo 5 > /sys/class/gpio/export
echo 65 > /sys/class/gpio/export
echo 105 > /sys/class/gpio/export
在 运行 插入 mod 之前,我发现一些东西可以正常工作。通过监控每个 GPIO 上的时钟值,我发现该值从某个值变为“2”。但是,将 2 手动输入到这些值中不足以让 GPIO 工作。
如果我找到通过内存控制正确启用时钟的方法,我将更新此答案。
编辑:
经过更多的关注和研究,我的代码可以正常工作。我把它写成一个单独的 module 并且它是在插入问题上发布的 module 之前插入的:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <net/tcp.h>
#define CM_PER_ADDR 0x44E00000
#define CM_PER_SIZE 0x3FF
#define CM_PER_GPIO1_ADDR 0xAC
#define CM_PER_GPIO2_ADDR 0xB0
#define CM_PER_GPIO3_ADDR 0xB4
#define GPIO_COUNT 3
//A couple of standard descriptions
MODULE_LICENSE("GPL");
static int hello_init(void)
{
static volatile void* cm_per;
static volatile unsigned int* cm_per_gpio[GPIO_COUNT];
static volatile int cm_per_addr[GPIO_COUNT] = {CM_PER_GPIO1_ADDR, CM_PER_GPIO2_ADDR, CM_PER_GPIO3_ADDR};
static int i = 0;
printk(KERN_NOTICE "Module2: Initializing module\n");
cm_per = ioremap(CM_PER_ADDR, CM_PER_SIZE);
if(!cm_per){
printk (KERN_ERR "Error: Failed to map GM_PER.\n");
return -1; //Break to avoid segfault
}
for(i = 0; i < GPIO_COUNT; i++){
cm_per_gpio[i] = cm_per + cm_per_addr[i];
//Check if clock is disabled
if(*cm_per_gpio[i] != 0x2){
printk(KERN_NOTICE "Enabling clock on GPIO[%d] bank...\n", (i+1));
*cm_per_gpio[i] = 0x2; //Enable clock
//Wait for enabled clock to be set
while(*cm_per_gpio[i] != 0x2){}
}
//Print hex value of clock
printk(KERN_NOTICE "cm_per_gpio[%d]: %04x\n", (i+1), *(cm_per_gpio[i]));
}
return 0;
}
static void hello_exit(void)
{
printk(KERN_INFO "Module: Exit module.\n"); //Print exit notice and exit without exploding anythin
}
module_init(hello_init);
module_exit(hello_exit);
从AM335x and AMIC110 Sitara™ ProcessorsTechnical Reference Manual,我们可以看到CM_PER_GPIO#_CLKCTRL Register是如何组织的(#代表我们正在查看的GPIO bank):
Table 8-60。 CM_PER_GPIO2_CLKCTRL 寄存器字段说明
也告诉我们寄存器的复位(默认)值为30000h,意思是CLOCK DISABLED,意思是module disabled.