代码之家  ›  专栏  ›  技术社区  ›  UndergroundCoding

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

  •  1
  • UndergroundCoding  · 技术社区  · 7 年前

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

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

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

    对于pin值,我使用的是BBB官方手册。为了获得适当的I/O GPIO可用性,我正在从beagleboard检查此图。通用域名格式: 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\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时出现分段错误?

    2 回复  |  直到 7 年前
        1
  •  1
  •   UndergroundCoding    7 年前

    经过大量研究,我发现了一些有用的链接,如 this one this one .

    需要指出的是,GPIOs寄存器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\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\U PER\U GPIO#\U CLKCTRL寄存器 组织(其中#代表我们正在查看的GPIO银行):

    表8-60:。CM\U PER\U GPIO2\U CLKCTRL寄存器字段描述 Table 8-60. CM_PER_GPIO2_CLKCTRL Register Field Descriptions

    它还告诉我们寄存器的重置(默认)值为 30000小时 ,表示时钟已禁用,表示 模块已禁用 .

        2
  •  0
  •   sawdust    7 年前

    关于代码为什么会出现分段错误的答案实际上无关紧要,因为作为一个内核模块,它被误导了,需要抛出,您需要重写它。您的模块绝对无权尝试直接访问“GPIO(控制)寄存器”,这些寄存器已经由pin控制所有( 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() ,可能为空,然后可能导致分段错误。)

    推荐文章