PCI mapper

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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;
}

[Index of Archives]     [Newbies FAQ]     [Linux Kernel Mentors]     [Linux Kernel Development]     [IETF Annouce]     [Git]     [Networking]     [Security]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux SCSI]     [Linux ACPI]
  Powered by Linux