使用mmap读取寄存器时出现总线错误

时间:2016-04-01 13:52:51

标签: c linux linux-kernel kernel-module

我开发了一个内核模块,它分配一些内核内存并在用户软件打开设备时将其重新映射到FPGA的物理寄存器,它还写入硬件寄存器,触发中断,最终由探测功能使用。内核检测模块init的IRQ号,在我的情况下为61。 (我到目前为止阅读了来自O'Reilly的优秀LDD3书籍,但由于我是内核世界的新手,我在使我的驱动程序运行良好时遇到了麻烦)

因此,我使用一个名为“regedit”的小软件从内核本身和用户空间访问硬件寄存器。为了从内核访问寄存器,我使用了ioremap,并编写了mmap函数,允许regedit使用remap_pfn_range从用户空间访问寄存器。

我的第一个问题是,我认为有一种更好的方法,而不是单独调用ioremap和remap_pfn_range来做同样的事情,但我不知道如何分配内存,重新映射,从内核访问它,以及通过它时间将它提供给用户空间。

我的第二个问题是,当我安装模块时,我看到我的驱动程序能够使用ioremap读取和写入寄存器,因为我通过触发中断(通过在偏移0处写入寄存器)成功检测到IRQ编号,当打开设备时,我的irq处理程序被调用,并通过在寄存器中写入0来成功确认中断。但是,因为有一个但是,当我尝试使用我的软regedit读取相同的寄存器时,我得到一个总线错误。

我的猜测是只有三个寄存器是物理连接的(偏移0,4和8),也许当我认为我正在读取一个32位寄存器时,内核实际上正在读取一个更大的缓冲区(PAGE_SIZE对齐我认为)并访问禁区。 (为了证明问题来自我的驱动程序,我在我的regedit软件中使用/ dev / mem,并且工作正常)

我使用处理器ARM Cortex A9在Xilinx Zynq ZC702板上使用Linux内核3.12。

以下是我的驱动程序的代码:

驱动程序标头文件

#ifndef DRIVER_H_
#define DRIVER_H_

/* --------------------------------------------------------------
 *              External References
 * ------------------------------------------------------------*/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/moduleparam.h>

/* --------------------------------------------------------------
 *              Application Includes
 * ------------------------------------------------------------*/

/* --------------------------------------------------------------
 *              Constants Definition
 * ------------------------------------------------------------*/

#define MODULE_NAME             "mydriver"
#define DEFAULT_MAJOR_NUMBER    0           // If zero, major number will be automatically allocated
#define DEFAULT_MINOR_NUMBER    0
#define NB_DEVICES              1           // Number of devices to register

/*
 * Hardware defines
 */
#define NB_PAGES                256        // Number of pages of the memory mapping
#define REG_IRQ                 0x43C00000 // IRQ register address

/*
 * Modules params
 */
static unsigned int irq_param = 0;

/*
 * Kernel module information
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AwaX");
MODULE_VERSION("0.1");
MODULE_ALIAS(MODULE_NAME);
MODULE_DESCRIPTION("Kernel module which handles the hardware interrupts and process them");

module_param(irq_param, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(irq_param, "The IRQ line number to be used");

/* --------------------------------------------------------------
 *              Macros Definition
 * ------------------------------------------------------------*/

#define LOG(kernLvl, str, tag)      printk(kernLvl "%-6.6s %s() : "str"\n", tag, (char*) __func__);
#define LOGA(kernLvl, str, tag,...) printk(kernLvl "%-6.6s %s() : "str"\n", tag, (char*) __func__, __VA_ARGS__);

#define LOG_TRACE(str) LOG(KERN_DEBUG, str, "KTRACE")
#define LOG_DEBUG(str) LOG(KERN_INFO, str, "KDEBUG")
#define LOG_INFO(str)  LOG(KERN_NOTICE, str, "KINFO")
#define LOG_IT(str)    LOG(KERN_NOTICE, str, "IT")
#define LOG_WARN(str)  LOG(KERN_WARNING, str, "KWARN")
#define LOG_ERROR(str) LOG(KERN_ERR, str, "KERROR")
#define LOG_FATAL(str) LOG(KERN_ALERT, str, "KFATAL")

#define LOG_TRACE_(str,...) LOGA(KERN_DEBUG, str, "KTRACE", __VA_ARGS__)
#define LOG_DEBUG_(str,...) LOGA(KERN_INFO, str, "KDEBUG", __VA_ARGS__)
#define LOG_INFO_(str,...)  LOGA(KERN_NOTICE, str, "KINFO", __VA_ARGS__)
#define LOG_IT_(str,...)        LOGA(KERN_NOTICE, str, "IT", __VA_ARGS__)
#define LOG_WARN_(str,...)  LOGA(KERN_WARNING, str, "KWARN", __VA_ARGS__)
#define LOG_ERROR_(str,...) LOGA(KERN_ERR, str, "KERROR", __VA_ARGS__)
#define LOG_FATAL_(str,...) LOGA(KERN_ALERT, str, "KFATAL", __VA_ARGS__)

/* --------------------------------------------------------------
 *              Types Definition
 * ------------------------------------------------------------*/

/*
 * Internal data structure
 */
typedef struct _module_data {
    int major;              // Major device number
    int minor;              // Minor device number
    dev_t mmap_dev;         // Holds device numbers (major and minor)
    struct cdev mmap_cdev;  // Kernel internal struct representing the device

    int  *vmalloc_area;     // Pointer to the vmalloc'd area - always page aligned
    int  *kmalloc_area;     // Pointer to the kmalloc'd area, rounded up to a page boundary
    void *kmalloc_ptr;      // Original pointer for kmalloc'd area as returned by kmalloc

    // Mapping
    volatile int  *map_area; // Base address of the registers kernel memory
    volatile void *io_area;  // Base address of the registers i/o physical memory

    // Interrupts
    unsigned int irq; // Interrupt number
} module_data;

/* --------------------------------------------------------------
 *              Functions Definition
 * ------------------------------------------------------------*/

#endif /* DRIVER_H_ */

驱动程序源文件

/* --------------------------------------------------------------
 *              External References
 * ------------------------------------------------------------*/

#include "driver.h"

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/memory.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/types.h>
#include <linux/io.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <asm-generic/errno-base.h>

/* --------------------------------------------------------------
 *              Application Includes
 * ------------------------------------------------------------*/


/* --------------------------------------------------------------
 *              Static data
 * ------------------------------------------------------------*/

// Internal data
static module_data *_module;

/* --------------------------------------------------------------
 *              Local Functions Definition
 * ------------------------------------------------------------*/

/*
 * Module functions
 */
static int  __init  my_module_init     (void);
static void __exit  my_module_cleanup  (void);
static int          my_module_open     (struct inode *inode, struct file *filp);
static int          my_module_release  (struct inode *inode, struct file *filp);
static int          my_module_mmap     (struct file *filp, struct vm_area_struct *vma);

/*
 * Static functions
 */
static void         my_vma_open        (struct vm_area_struct *vma);
static void         my_vma_close       (struct vm_area_struct *vma);
static irqreturn_t  my_irq_handler     (int irq, void *dev_id, struct pt_regs *regs);
static int          my_allocate_device (void);
static int          my_register_device (void);
static unsigned int my_probe_irq       (void);
static int          my_mmap_kmem       (struct file *filp, struct vm_area_struct *vma);
static int          my_mmap_vmem       (struct file *filp, struct vm_area_struct *vma);

/*
 * Specifies the functions associated with the device operations.
 */
static struct file_operations _module_fops = {
        .owner = THIS_MODULE,
        .open = my_module_open,
        .release = my_module_release,
        .mmap = my_module_mmap,
};

/*
 * Specifies the functions associated with the remap operations.
 */
static struct vm_operations_struct _module_vmops = {
        .open = my_vma_open,
        .close = my_vma_close,
};

/* --------------------------------------------------------------
 *              Functions Implementation
 * ------------------------------------------------------------*/

/*****************************************************************************
 * Initialization function of the module which allocates the major and minor
 * numbers and registers the device to /proc/devices. The creation of the
 * device in /dev must be done by an external script.
 *
 * @return
 *      SUCCESS : 0
 *      FAILURE : Negative error code.
 *****************************************************************************/
static int __init my_module_init (void) {
    unsigned int irqprobe = 0;
    int err = 0;
    int i = 0;

    LOG_INFO_("Initializing module %s", MODULE_NAME);
    LOG_INFO_("Module param : irq_param = %u", irq_param);

    /*
     * Init internal data
     */
    _module = kmalloc(sizeof(module_data), GFP_KERNEL);
    memset(_module, 0, sizeof(module_data));
    if (_module == NULL) {
        goto out;
    }
    _module->major = DEFAULT_MAJOR_NUMBER;
    _module->minor = DEFAULT_MINOR_NUMBER;
    _module->map_area = NULL;
    _module->io_area  = NULL;
    _module->irq = irq_param;

    /*
     * Allocate kmalloc memory
     */
    _module->kmalloc_ptr = kmalloc((NB_PAGES + 2) * PAGE_SIZE, GFP_KERNEL);
    if (_module->kmalloc_ptr == NULL) {
        err = -ENOMEM;
        goto out_kfree;
    }
    // Round it up to the page bondary
    _module->kmalloc_area = (int *) ((((unsigned long) _module->kmalloc_ptr) + PAGE_SIZE - 1) & PAGE_MASK);

    // Use the kernel memory to access registers from the module
    _module->map_area = _module->kmalloc_area;

    /*
     * Allocate vmalloc memory
     */
    _module->vmalloc_area = (int *) vmalloc(NB_PAGES * PAGE_SIZE);
    if (_module->vmalloc_area == NULL) {
        err = -ENOMEM;
        goto out_vfree;
    }

    /*
     * Remap physical addresses
     */
    _module->io_area = ioremap(REG_IRQ, NB_PAGES * PAGE_SIZE);
    if (_module->io_area == NULL) {
        LOG_ERROR_("Physical memory remapping failed (base_addr=%#x, size=%#lx)", REG_IRQ, NB_PAGES * PAGE_SIZE);
        goto out_iofree;
    }

    /*
     * Allocates the device numbers
     */
    err = my_allocate_device();
    if (err) {
        LOG_ERROR_("Device allocation failed with code : %d", err);
        goto out_unalloc_region;
    }

    // If no IRQ number has been specified
    if (_module->irq <= 0) {
        // Probes for an IRQ line number
        LOG_INFO("Probing IRQ number...");
        irqprobe = my_probe_irq();
        if (irqprobe == 0) { // Probe failed
            LOG_ERROR("IRQ probing failed : cannot find IRQ number");
        } else if (irqprobe < 0) { // Probe error
            LOG_ERROR_("IRQ probing failed with error code : %d", err);
        } else {
            // If an irq number is found
            LOG_INFO_("IRQ number detected : %u", irqprobe);
            _module->irq = irqprobe;
        }
    } else { // If an irq number has been specified via a module parameter
        LOG_INFO_("IRQ number param specified : irq=%u", _module->irq);
    }

    // Registers the device making it live immediately
    err = my_register_device();
    if (err) {
        LOG_ERROR_("Device register failed with code : %d", err);
        goto out_unregister;
    }

    LOG_INFO_("Module %s initialized successfully !", MODULE_NAME);
    return 0;

    /*
     * Error fallbacks
     */
    out_unregister:
        LOG_DEBUG_("Deallocating chrdev for %s", MODULE_NAME);
        cdev_del(&_module->mmap_cdev);
        // Unreserve the pages
        LOG_DEBUG("Unreserving memory pages");
        for (i = 0; i < NB_PAGES * PAGE_SIZE; i += PAGE_SIZE) {
            SetPageReserved(vmalloc_to_page((void *) (((unsigned long) _module->vmalloc_area) + i)));
            SetPageReserved(virt_to_page(((unsigned long )_module->kmalloc_area) + i));
        }
    out_unalloc_region:
        LOG_DEBUG_("Unregistering device %s", MODULE_NAME);
        unregister_chrdev_region(_module->mmap_dev, NB_DEVICES);
    out_iofree:
        iounmap(_module->io_area);
    out_vfree:
        vfree(_module->vmalloc_area);
    out_kfree:
        kfree(_module->kmalloc_ptr);
    out:
    return err;
}

/*****************************************************************************
 * Cleanup function of the module which unregisters the major number and
 * removes the created device from the system.
 *
 * @return
 *      void
 *****************************************************************************/
static void __exit my_module_cleanup (void) {
    LOG_INFO_("Cleaning up module %s", MODULE_NAME);

    // Unregisters a range of device numbers.
    unregister_chrdev_region(_module->mmap_dev, NB_DEVICES);
    LOG_INFO_("Unregistered device %s", MODULE_NAME);

    // Free kernel memory
    vfree(_module->vmalloc_area);
    kfree(_module->kmalloc_ptr);
    kfree(_module);

    // Remove the cdev from the system, possibly freeing the structure itself
    cdev_del(&_module->mmap_cdev);
    LOG_INFO_("Deallocated chrdev for %s", MODULE_NAME);
}

/*****************************************************************************
 * Open function of the module which makes this module accessible from the
 * user space.
 *
 * @return
 *      SUCCESS : 0
 *      FAILURE : Negative error code.
 *****************************************************************************/
static int my_module_open (struct inode *inode, struct file *filp) {
    int err = 0;

    // If an interrupt line has been requested
    if (_module->irq > 0) {
        // Registers the interrupt handler to the kernel
        err = request_irq(_module->irq, (irq_handler_t) my_irq_handler, 0, MODULE_NAME, _module);
        if (err) {
            LOG_ERROR_("%s : Cannot get assigned irq %d, request_irq() failed, code=%d", MODULE_NAME, _module->irq, err);
            _module->irq = -1;
            return err;
        } else {
            LOG_INFO_("IRQ number %u assigned successfully to module %s", _module->irq, MODULE_NAME);
        }
    } else {
        LOG_ERROR("Invalid IRQ number : the device will not see the hardware interrupts");
    }
    LOG_INFO_("%s opened successfully", MODULE_NAME);
    return 0;
}

/*****************************************************************************
 * Close function of the module which releases the use of this module from
 * the user space.
 *
 * @return
 *      SUCCESS : 0
 *      FAILURE : Negative error code.
 *****************************************************************************/
static int my_module_release (struct inode *inode, struct file *filp) {
    LOG_INFO_("%s closing...", MODULE_NAME);
    // Removes the interrupt handler from kernel
    LOG_INFO_("Releasing irq number %u", _module->irq);
    free_irq(_module->irq, _module);
    LOG_INFO_("%s closed", MODULE_NAME);
    return 0;
}

/*****************************************************************************
 * Creates a new mapping in the virtual address space of the calling process.
 * It makes possible for a user process to access physical memory in the
 * kernel space.
 *
 * @param   filp
 *              The file or device.
 * @param   vma
 *              The virtual memory area into which the page range is being
 *              mapped.
 * @return
 *      SUCCESS : 0
 *      FAILURE : Negative error code.
 *****************************************************************************/
static int my_module_mmap (struct file *filp, struct vm_area_struct *vma) {
    unsigned long start     = vma->vm_start;        // Virtual address where remapping begins
    unsigned long end       = vma->vm_end;
    unsigned long length    = end - start;
    unsigned long maxLength = NB_PAGES * PAGE_SIZE;
    unsigned long pgoff     = vma->vm_pgoff;        // PFN of the physAddr to which vAddr is mapped

    // Checks length - do not allow larger mappings than the number of pages allocated
    if (length > maxLength) {
        LOG_ERROR_("Specified virtual memory area is too big : 0x%lx , 0x%lx", length, maxLength);
        return -EIO;
    }

    // At offset 0
    if (pgoff == 0) {
        // we map the vmalloc'd area
        LOG_DEBUG_("Allocating virtual memory, start=%#lx, length=%#lx, pgoff=%#lx", start, length, pgoff);
        return my_mmap_vmem(filp, vma);
    } else {
        // we map the kmalloc'd area
        LOG_DEBUG_("Allocating kernel memory, start=%#lx, length=%#lx, pgoff=%#lx", start, length, pgoff);
        return my_mmap_kmem(filp, vma);
    }
    return -EIO;
}

static void my_vma_open (struct vm_area_struct* vma) {
    LOG_INFO_("%s VMA open, virtAddr=%#lx, physAddr=%#lx", MODULE_NAME, vma->vm_start, vma->vm_pgoff << PAGE_SHIFT);
}

static void my_vma_close (struct vm_area_struct* vma) {
    LOG_INFO_("%s VMA close", MODULE_NAME);
}

/*****************************************************************************
 * Interrupt handler which :
 *      - Reads the registers to know the source of the IT,
 *      - Clears the IT and wakes up the handler task.
 *
 * @param irq
 *              The interrupt number requested.
 * @param dev_id
 *              Pointer to the device structure passed to the function
 *              request_irq() containing internal data (used when the driver
 *              manages several instances of the same device).
 * @param regs
 *              Used for debug. Holds a snapshot of the processor's context
 *              before the processor entered interrupted code.
 * @return
 *      SUCCESS : 0
 *      FAILURE : Negative error code.
 *****************************************************************************/
static irqreturn_t my_irq_handler (int irq, void *dev_id, struct pt_regs *regs) {
    module_data *moduleData = dev_id;
    LOG_IT_("%s interrupted, interrupt number = %d", MODULE_NAME, irq);
    if (moduleData != NULL) {
        // Resets irq
        iowrite32(0x0, _module->io_area);
        wmb();
    } else {
        LOG_ERROR("Device structure is NULL");
    }
    return IRQ_HANDLED;
}

/*****************************************************************************
 * Allocates major and minor numbers for this device.
 *
 * @return
 *      SUCCESS : 0
 *      FAILURE : Negative error code.
 *****************************************************************************/
static int my_allocate_device (void) {
    int err = 0;

    // If a non zero major number is specified
    if (_module->major) {
        // Updates the dev structure used as input for register_chrdev_region
        _module->mmap_dev = MKDEV(_module->major, _module->minor);
        // Registers a range of device numbers.
        err = register_chrdev_region(_module->mmap_dev, NB_DEVICES, MODULE_NAME);
    } else { // If major number is zero, then allocates it dynamically
        // Allocates a range of char device numbers chosen dynamically
        err = alloc_chrdev_region(&_module->mmap_dev, _module->minor, NB_DEVICES, MODULE_NAME);
        _module->major = MAJOR(_module->mmap_dev);
    }
    // Checks result
    if (err) {
        LOG_ERROR_("cannot get major number %d", _module->major);
        return err;
    } else {
        LOG_INFO_("Registered device %s : major=%d, minor=%d", MODULE_NAME, _module->major, _module->minor);
    }

    // Initializes cdev and file operations
    cdev_init(&_module->mmap_cdev, &_module_fops);
    _module->mmap_cdev.owner = THIS_MODULE;
    _module->mmap_cdev.ops = &_module_fops;

    return 0;
}

/*****************************************************************************
 * Registers the device, makes it live immediately, therefore all
 * initialization routines must be done before calling this function.
 *
 * @return
 *      SUCCESS : 0
 *      FAILURE : Negative error code.
 *****************************************************************************/
static int my_register_device (void) {
    int err = 0;

    // Adds the device to the system making it live immediately
    err = cdev_add(&_module->mmap_cdev, _module->mmap_dev, NB_DEVICES);
    if (err) {
        LOG_ERROR("Could not allocate chrdev");
        return err;
    } else {
        LOG_INFO_("Allocated chrdev for %s", MODULE_NAME);
    }
    return 0;
}

/*****************************************************************************
 * This function uses the probe functions of the kernel to find the irq
 * number of the hardware device.
 *
 * @return
 *      SUCCESS : 0
 *      FAILURE : Negative error code.
 *****************************************************************************/
static unsigned int my_probe_irq (void) {
    unsigned int irq = -1;
    int count = 0;
    do {
        volatile unsigned long mask;

        // Reset the interrupts
        iowrite32(0x0, _module->io_area);
        wmb(); // Memory barrier

        // Start kernel probing
        mask = probe_irq_on();

        // Trigger all interrupts
        iowrite32(0xffffffff, _module->io_area);
        wmb(); // Memory barrier

        // Wait for it
        ndelay(1000);

        // Try to find which interrupt occurred
        irq = probe_irq_off(mask);
    } while (irq < 0 && count++ < 5);
    return irq;
}

/*****************************************************************************
 * Helper function, mmap's the vmalloc'd area which is not physically
 * contiguous.
 *
 * @return
 *      SUCCESS : 0
 *      FAILURE : Negative error code.
 *****************************************************************************/
static int my_mmap_vmem (struct file *filp, struct vm_area_struct *vma) {
    unsigned long start = vma->vm_start;
    unsigned long end   = vma->vm_end;
    unsigned long pfn   = 0;
    long length         = end - start;
    int ret = 0;

    // Check length - do not allow larger mappings than the number of pages allocated
    if (length > NB_PAGES * PAGE_SIZE) {
        LOG_ERROR_("Specified length (%lu) is larger than the number of pages allocated", length);
        return -EIO;
    }

    // Loop over all pages, map it page individually
    while (length > 0) {
        pfn = vmalloc_to_pfn(_module->vmalloc_area);
        ret = remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED);
        if (ret < 0) {
            LOG_ERROR_("remap_pfn_range() failed with error %d, addr=%#lx, offset=%#lx", ret, start, pfn);
            return ret;
        }
        start += PAGE_SIZE;
        _module->vmalloc_area += PAGE_SIZE;
        length -= PAGE_SIZE;
    }
    vma->vm_ops = &_module_vmops; // Specifies open_vma() and close_vma() functions
    my_vma_open(vma); // Calls explicitely open_vma() as its not done by calling mmap()
    return 0;
}

/*****************************************************************************
 * Helper function, mmap's the kmalloc'd area which is physically contiguous.
 *
 * @return
 *      SUCCESS : 0
 *      FAILURE : Negative error code.
 *****************************************************************************/
static int my_mmap_kmem (struct file *filp, struct vm_area_struct *vma) {
    unsigned long start = vma->vm_start;
    unsigned long end   = vma->vm_end;
    long length         = end - start;
    int ret = 0;

    // Check length - do not allow larger mappings than the number of pages allocated
    if (length > NB_PAGES * PAGE_SIZE) {
        LOG_ERROR_("Specified length (%lu) is larger than the number of pages allocated", length);
        return -EIO;
    }

    // Map the whole physically contiguous area in one piece
    //pfn = virt_to_phys((void *) _module->kmalloc_area) >> PAGE_SHIFT;
    ret = remap_pfn_range(vma, start, vma->vm_pgoff, length, vma->vm_page_prot);
    if (ret < 0) {
        return ret;
    }
    vma->vm_ops = &_module_vmops; // Specifies open_vma() and close_vma() functions
    my_vma_open(vma); // Calls explicitely open_vma() as its not done by calling mmap()
    return 0;
}

/*
 * MANDATORY
 *
 * Used by kernel to load this module and specifies its entry points.
 */
module_init(my_module_init);
module_exit(my_module_cleanup);

0 个答案:

没有答案