Hi All! I have developed a module that maps a devices memory into user space. The module is very simple (see the source code attached). It just defined the file operations open, release, ioctl and mmap. I'm having a problem in porting this module to the 2.6 series. Apparently, everything is working fine but I just can't access the device's memory (even tought I got a valid point from the mmap system call). Can anyone help me? Thanks in advance.
#include <linux/init.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/version.h> #include <linux/config.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/fs.h> /* for file_operations */ #include <linux/pci.h> /* for pci_find_device */ #include <linux/slab.h> /* for kmalloc */ #include <asm/segment.h> /* for memcpy */ #include <asm/io.h> /* for virt_to_phys */ #include <linux/byteorder/little_endian.h> // TODO: Deveria ser definido no Makefile #define DBLEV 31 #include "debug.h" #include "pcimap.h" MODULE_AUTHOR ("Thiago Robert"); MODULE_DESCRIPTION ("PCI Device Mapper"); MODULE_LICENSE ("GPL"); /* MODULE PARAMETERS */ int dev = PCIMAP_MAJOR; int n_devs = PCIMAP_N_DEVS; int vendor_id = VENDOR_ID; int device_id = DEVICE_ID; module_param (dev, int, 0); // PCI Map major device number module_param (n_devs, int, 0); // Number of PCI Map devices module_param (vendor_id, int, 0); // PCI vendor id module_param (device_id, int, 0); // PCI device id /* Offset for PCI Regions */ static const int pciregs[PCIMAP_N_REGS] = { PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_2, PCI_BASE_ADDRESS_3, PCI_BASE_ADDRESS_4, PCI_BASE_ADDRESS_5 }; /* Mapped PCI devices */ static struct PCI_Device *pcidevs; static int pcimap_open (struct inode *inode, struct file *filp) { int unit = 0, reg = 0; unit = iminor (inode) >> 4; reg = iminor (inode) & 0x0f; debug (DB_TRACE, "PCIMAP: pcimap_open(unit=%d,reg=%d)\n", unit, reg); if ((unit >= n_devs) || (!pcidevs[unit].found) || reg >= PCIMAP_N_REGS) return -ENODEV; if (!pcidevs[unit].reg[reg].size) return -ENODEV; /* PCI Map devices are not sharable */ if (pcidevs[unit].reg[reg].busy) return -EBUSY; pcidevs[unit].reg[reg].busy = 1; return 0; } static int pcimap_close (struct inode *inode, struct file *filp) { int unit = 0, reg = 0; unit = iminor (inode) >> 4; reg = iminor (inode) & 0x0f; debug (DB_TRACE, "PCIMAP: pcimap_close(unit=%d,reg=%d)\n", unit, reg); pcidevs[unit].reg[reg].busy = 0; return 0; } static int pcimap_mmap2 (struct file *filp, struct vm_area_struct *vma) { int unit, reg; unsigned long size, phy_addr; unit = MINOR(filp->f_dentry->d_inode->i_rdev) >> 4; reg = MINOR(filp->f_dentry->d_inode->i_rdev) & 0x0f; size = vma->vm_end - vma->vm_start; debug (DB_TRACE, "DEBUG: pcimap_mmap(unit=%d,reg=%d,start=%lx,size=%lx,off=%lx)\n", unit, reg, vma->vm_start, size, vma->vm_pgoff * PAGE_SIZE); /* Mapping must be page aligned and not larger than the region size */ if ((size + vma->vm_pgoff * PAGE_SIZE) > pcidevs[unit].reg[reg].size) { return -EINVAL; } /* Get device's memory physical address for mapping */ phy_addr = pcidevs[unit].reg[reg].phy_addr + vma->vm_pgoff * PAGE_SIZE; /* Isn't it necessary to call ioremap_nocache? how? */ // vma->vm_start = (unsigned int) ioremap_nocache(phy_addr, size); /* Map device's memory in the requested address space range */ if (remap_page_range (vma, phy_addr, 0, size, vma->vm_page_prot)) { return -EAGAIN; } filp->f_dentry->d_inode->i_count.counter++; return 0; } static int pcimap_mmap (struct file *filp, struct vm_area_struct *vma) { // /lib/modules/2.6.5-1.358/build/include/linux/mm.h --> definicao vm_area_stuct int unit, reg; unsigned long size, phy_addr; unit = MINOR(filp->f_dentry->d_inode->i_rdev) >> 4; reg = MINOR(filp->f_dentry->d_inode->i_rdev) & 0x0f; size = vma->vm_end - vma->vm_start; debug (DB_TRACE, "PCIMAP: pcimap_mmap(unit=%d,reg=%d,start=%lx,size=%lx,off=%lx)\n", unit, reg, vma->vm_start, size, vma->vm_pgoff * PAGE_SIZE); debug (DB_TRACE, "DEBUG: pcimap_mmap(vm_end=%lx, vm_start=%lx, vm_pgoff=%lx, PAGE_SIZE=%lx)\n", vma->vm_end, vma->vm_start, vma->vm_pgoff, PAGE_SIZE); /* Mapping must be page aligned and not larger than the region size */ if ((size + vma->vm_pgoff * PAGE_SIZE) > pcidevs[unit].reg[reg].size) { return -EINVAL; } /* Get device's memory physical address for mapping */ phy_addr = pcidevs[unit].reg[reg].phy_addr + vma->vm_pgoff * PAGE_SIZE; debug (DB_TRACE, "DEBUG: pcimap_mmap(phy=%lx, phy2=%x)\n", phy_addr, pcidevs[unit].reg[reg].phy_addr); /* Isn't it necessary to call ioremap_nocache? how? */ // vma->vm_start = (unsigned int) ioremap_nocache(phy_addr, size); /* Map device's memory in the requested address space range */ if (remap_page_range (vma, phy_addr, 0, size, vma->vm_page_prot)) { return -EAGAIN; } filp->f_dentry->d_inode->i_count.counter++; debug (DB_TRACE, "DEBUG: pcimap_mmap(counter=%d)\n", filp->f_dentry->d_inode->i_count.counter); return 0; } static int pcimap_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { int unit = 0, reg = 0; unit = iminor (inode) >> 4; reg = iminor (inode) & 0x0f; debug (DB_TRACE, "PCIMAP: pcimap_ioctl(unit=%d,reg=%d,cmd=%x,arg=%lx)\n", unit, reg, cmd, arg); switch (cmd) { case PCIMAP_GET_SIZE: /* Return DMA buffer's size */ return pcidevs[unit].reg[reg].size; break; case PCIMAP_GET_PHY_ADDR: /* Return DMA buffer's physical address */ return pcidevs[unit].reg[reg].phy_addr; break; default: return -EINVAL; } return 0; } /* PCIMAP FILE OPERATIONS */ struct file_operations pcimap_fops = { .mmap = pcimap_mmap, /* mmap */ .ioctl = pcimap_ioctl, /* ioctl */ .open = pcimap_open, /* open */ .release = pcimap_close, /* close */ }; int pcimap_init (void) { int i, j, err; struct pci_dev *pci_dev; debug (DB_TRACE, "PCIMAP: Loading PCI Map(dev=%d,n_devs=%d,vendor_id=%x,device_id=%x)\n", dev, n_devs, vendor_id, device_id); /* Check for mandatory module parameters */ if ((vendor_id == -1) || (device_id == -1)) { printk (KERN_WARNING "PCI vendor and device id must be specified!\n"); return -EINVAL; } /* Allocate the control structure for PCI Maps */ pcidevs = kmalloc (n_devs * sizeof (struct PCI_Device), GFP_KERNEL); if (!pcidevs) { return -ENOMEM; } memset (pcidevs, 0, n_devs * sizeof (struct PCI_Device)); i = 0; pci_dev = NULL; do { /* Look for our PCI device */ pci_dev = pci_find_device (vendor_id, device_id, pci_dev); if (!pci_dev) { break; } /* (Re)configure device for bus mastering */ pci_read_config_word (pci_dev, PCI_COMMAND, &pcidevs[i].cmd); if (!(pcidevs[i].cmd & PCI_COMMAND_MEMORY)) { pcidevs[i].cmd |= PCI_COMMAND_MEMORY; debug (DB_WARNING, "PCIMAP: PCI_COMMAND_MEMORY not set for device!\n"); } if (!(pcidevs[i].cmd & PCI_COMMAND_MASTER)) { pcidevs[i].cmd |= PCI_COMMAND_MASTER; debug (DB_WARNING, "PCIMAP: PCI_COMMAND_MASTER not set for device!\n"); } if (!(pcidevs[i].cmd & PCI_COMMAND_INVALIDATE)) { pcidevs[i].cmd |= PCI_COMMAND_INVALIDATE; debug (DB_WARNING, "PCIMAP: PCI_COMMAND_INVALIDATE not set for device!\n"); } pci_write_config_word (pci_dev, PCI_COMMAND, pcidevs[i].cmd); /* Get device's configuration */ pci_read_config_word (pci_dev, PCI_COMMAND, &pcidevs[i].cmd); pci_read_config_word (pci_dev, PCI_STATUS, &pcidevs[i].stat); pci_read_config_byte (pci_dev, PCI_REVISION_ID, &pcidevs[i].rev); /* Get regions */ for (j = 0; j < PCIMAP_N_REGS; j++) { /* Get reagion info */ pci_read_config_dword (pci_dev, pciregs[j], &pcidevs[i].reg[j].phy_addr); pci_write_config_dword (pci_dev, pciregs[j], ~0); pci_read_config_dword (pci_dev, pciregs[j], &pcidevs[i].reg[j].size); pci_write_config_dword (pci_dev, pciregs[j], pcidevs[i].reg[j].phy_addr); /* Check for memory region */ if (!pcidevs[i].reg[j].size || (pcidevs[i].reg[j]. phy_addr & PCI_BASE_ADDRESS_SPACE_IO)) { pcidevs[i].reg[j].phy_addr = 0; pcidevs[i].reg[j].size = 0; continue; } /* We only handle 32 bit addressed memory above 1 Mb */ if ((pcidevs[i].reg[j]. phy_addr & PCI_BASE_ADDRESS_MEM_TYPE_MASK) & (PCI_BASE_ADDRESS_MEM_TYPE_1M | PCI_BASE_ADDRESS_MEM_TYPE_64)) { pcidevs[i].reg[j].phy_addr = 0; pcidevs[i].reg[j].size = 0; continue; } /* Adjust address and size */ pcidevs[i].reg[j].phy_addr &= PCI_BASE_ADDRESS_MEM_MASK; pcidevs[i].reg[j].size = ~(pcidevs[i].reg[j]. size & PCI_BASE_ADDRESS_MEM_MASK) + 1; pcidevs[i].regs |= 1 << j; } if (pcidevs[i].regs) { pcidevs[i].found = 1; } /* debug (DB_INFO, "PCIMAP: debug\n"); debug (DB_INFO, "pcidev[%d]->ctrl=%x\n", i, pcidevs[i].cmd); debug (DB_INFO, "pcidev[%d]->stat=%x\n", i, pcidevs[i].stat); debug (DB_INFO, "pcidev[%d]->rev=%x\n", i, pcidevs[i].rev); debug (DB_INFO, "pcidev[%d]->regs=%x\n", i, pcidevs[i].regs); for (j = 0; j < PCIMAP_N_REGS; j++) { if (pcidevs[i].reg[j].size) { debug (DB_INFO, "pcidev[%d]->reg[%d]->phy_addr=%x\n", i, j, pcidevs[i].reg[j].phy_addr); debug (DB_INFO, "pcidev[%d]->reg[%d]->size=%d\n", i, j, pcidevs[i].reg[j].size); } } */ } while ((++i < n_devs) && pci_dev); if (i == 0) { kfree (pcidevs); return -ENODEV; } /* Register the device driver */ err = register_chrdev (dev, "pcimap", &pcimap_fops); if (err < 0) { return err; } return 0; } void pcimap_exit (void) { debug (DB_TRACE, "PCIMAP: Unloading PCI Map\n"); /* Unregister the device driver */ unregister_chrdev (dev, "pcimap"); /* Release PCI Maps control structure */ kfree (pcidevs); } module_init (pcimap_init); module_exit (pcimap_exit);
#ifndef PCIMAP_H #define PCIMAP_H #include <linux/ioctl.h> #define PCIMAP_MAJOR 125 #define PCIMAP_N_DEVS 2 #define PCIMAP_N_REGS 6 #define VENDOR_ID 0x14C1 #define DEVICE_ID 0x8043 #define PCIMAP_GET_SIZE _IOR(PCIMAP_MAJOR, 0, unsigned long) #define PCIMAP_GET_PHY_ADDR _IOR(PCIMAP_MAJOR, 1, unsigned long) struct PCI_Region { int busy; unsigned int phy_addr; unsigned int size; }; struct PCI_Device { int found; unsigned short cmd; unsigned short stat; unsigned char rev; int regs; struct PCI_Region reg[PCIMAP_N_REGS]; }; #endif /* PCIMAP_H */
typedef enum { DB_ABORT, DB_ERROR, DB_WARNING, DB_INFO, DB_TRACE } Debug_Level; void debug (Debug_Level level, const char *format, ...); #ifdef DBLEV extern char *debug_levels[]; #define debug(level, format, args...) \ if((1 << level) & (DBLEV))\ printk(KERN_INFO format, ## args); #else /* DBLEV */ #define debug(level, format, args...) #endif /* DBLEV */
Attachment:
Makefile
Description: Binary data
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/ioctl.h> #include "pcimap.h" int main (int argc, char *argv[]) { int i, fd0; char * ptr = NULL; unsigned long phy_addr, size; if (argc != 4) { fprintf (stderr, "Usage: %s device offset size!\n", argv[0]); return -1; } fd0 = open (argv[1], O_RDWR); if (fd0 < 0) { perror ("open"); return -1; } size = ioctl (fd0, PCIMAP_GET_SIZE, NULL); if (size < 0) { perror ("ioctl"); return -1; } printf ("PCI device memory size: %ld\n", size); phy_addr = ioctl (fd0, PCIMAP_GET_PHY_ADDR, NULL); if (phy_addr < 0) { perror ("ioctl"); return -1; } printf ("PCI device memory physical address: %lx\n", phy_addr); ptr = (char *) mmap (0, atoi (argv[3]), PROT_READ | PROT_WRITE, MAP_SHARED, fd0, atoi (argv[2])); if ((int) ptr == -1) { printf ("Error code: %d\n", (int) ptr); perror ("mmap"); return -1; } else { printf ("PCI device memory logical address: %p\n", ptr); // strcpy (ptr, "PCIMAP test OK!"); printf ("PCI device memory contents: "); for (i = atoi (argv[2]); i < atoi (argv[3]); i++) { // printf("%c", ptr[i]); putchar (ptr[i]); } putchar ('\n'); } close (fd0); return 0; }