当通过内核模块访问GPIO2和GPIO3时,为什么我会在Beaglebone Black上出现分段错误?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了当通过内核模块访问GPIO2和GPIO3时,为什么我会在Beaglebone Black上出现分段错误?相关的知识,希望对你有一定的参考价值。

我一直试图通过内核模块访问beaglebone black上的GPIO2和GPIO3但没有成功。每次我尝试为GPIO 2和3分配输出值时,都会出现分段错误。

完全相同的代码(具有适当的引脚分配)适用于GPIO0和GPIO1。

我尝试了与GPIO2和GPIO3相关的P8和P9上的各种引脚都没有成功。另一方面,相同的精确代码适用于GPIO0和GPIO1,并具有适当的引脚分配。

对于引脚值,我使用的是官方BBB手册。为了获得适当的I / O GPIO可用性,我将从beagleboard.com查看此图:http://beagleboard.org/support/bone101 65 possible digital I/O

#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
");

    printk(KERN_NOTICE "Module: Map GPIO
");
    gpio_addr = ioremap(GPIO3_START_ADDR,SIZE);

    printk(KERN_NOTICE "Module: Set oe_addr
");
    oe_addr = gpio_addr + GPIO_OE;

    printk(KERN_NOTICE "Module: Set dataout_addr
");
    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
");
    *oe_addr &= (0xFFFFFFFF ^ (1<<19));

    printk(KERN_NOTICE "Module: Set pin output to HIGH
");
    *dataout_addr |= (1<<19);

    return 0;
}

static void hello_exit(void)
{
    printk(KERN_INFO "Exit module.
");
}

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时,为什么会出现分段错误?

答案

经过大量研究后,我发现了一些有用的链接,如this onethis 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工作。

如果我找到通过内存控制正确启用时钟的方法,我将更新此答案。

编辑:

经过更多的讨论和研究后,我得到了正常运行的代码。我把它写成一个单独的模块,在插入问题上发布的模块之前插入它:

#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
");

    cm_per = ioremap(CM_PER_ADDR, CM_PER_SIZE);
        if(!cm_per){
            printk (KERN_ERR "Error: Failed to map GM_PER.
");
            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...
", (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
", (i+1), *(cm_per_gpio[i]));
    }


    return 0;
}

static void hello_exit(void)
{
    printk(KERN_INFO "Module: Exit module.
"); //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寄存器是如何组织的(其中#代表我们正在查看的GPIO库):

表8-60。 CM_PER_GPIO2_CLKCTRL注册字段描述Table 8-60. CM_PER_GPIO2_CLKCTRL Register Field Descriptions

它还告诉我们寄存器的复位(默认)值是30000h,这意味着CLOCK DISABLED,意味着模块被禁用。

另一答案

关于为什么你的代码出现分段错误的答案实际上是无关紧要的,因为作为内核模块,它被误导,需要被抛出,你需要重写它。您的模块完全没有尝试直接访问“GPIO(控制)寄存器”,这些寄存器已经由引脚控制(pinctrl)子系统拥有。

GPIO引脚是内核管理的(通用)资源。你会编写一个只为其缓冲区开始使用任意内存块的驱动程序吗? 希望不是,因为内存是由内核管理的(另一种)资源。 但是你的模块只是随心所欲地使用GPIO引脚!

有关您正在使用的确切Linux内核版本,请参阅相应的GPIO文档: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()的返回值,它可能为null,然后可能导致分段错误。)

以上是关于当通过内核模块访问GPIO2和GPIO3时,为什么我会在Beaglebone Black上出现分段错误?的主要内容,如果未能解决你的问题,请参考以下文章

通过网络控制ESP8266端口LED

Linux内核模块编程-proc文件系统

[Mini2440] 最简单的内核模块

Socket与系统调用深度分析

内核栈溢出

C2内核模块,分配设备号,字符驱动,/设备节点,设备读写,/同步和互斥,ioctl函数,进程休眠,时间和延时,延缓,/proc文件系统,内存分配,数据类型,/内核中断,通过IO内存访问外设