up
minipcia simple CPLD-powered PCI card

A FreeBSD Kernel Driver

The hardest part of the whole project was getting my old Abit BP6 to boot from CDROM again (next time I'll just reset the CMOS settings to defaults!). After that was done it was a snap to install FreeBSD 5.2.1 and write a driver based on the one by Murray Stokely in the FreeBSD handbook. I extended his framework to connect the PCI device half of the driver to the character device half, making the memory window visible as /dev/minipci0.

pciconf Output

This shows the output of pciconf after the system has booted. The BIOS has already configured and activated a memory window at 0xc8000, as requested by the lower bits in the base address register. That 0x2 is requesting a base address below 1M, which with our 4k window leaves only 8 bits of base address for us to store and decode.

pci ~ # pciconf -l
...
minipci0@pci0:13:0:     class=0xff0000 card=0x0001bebe chip=0x9500106d rev=0x01 hdr=0x00
...
pci ~ # pciconf -r pci0:13:0 0:60
9500106d 00000002 ff000001 00000000
000c8002 00000000 00000000 00000000
00000000 00000000 00000000 0001bebe
00000000 00000000 00000000 00000000

Console Messages

pci ~ # kldload /sys/modules/minipci/minipci.ko
pci ~ # dmesg | tail -1
minipci0: <Experimental Xilinx XC95108 Card> mem 0xc8000-0xc8fff at device 13.0 on pci0

Dump of the Memory Space

The core uses a 6 bit address to address 32-bit words, so the total addressable memory space is only 256 bytes. To save on decoding effort, the card uses the PCI-specification-recommended minimum of 4096 bytes. The first 256 bytes are aliased over and over within that 4k. In this example the 4 bit LED register is mapped at location 0 and has one bit set:

pci ~ # od -A x -X /dev/minipci0
0000000          00000008        00000000        00000000        00000000
0000010          00000000        00000000        00000000        00000000
*
0000100          00000008        00000000        00000000        00000000
0000110          00000000        00000000        00000000        00000000
*
0000200          00000008        00000000        00000000        00000000
0000210          00000000        00000000        00000000        00000000
*
[repeats]

/sys/pci/minipci.c

/*
 * A driver for a mini PCI card based on a Xilinx XC95108 CPLD.  Supports
 * read/write/mmap access to one memory mapped space on the card.
 *
 * Ben Jackson <ben@ben.com> // Sep, 2004
 *
 * Based on the FreeBSD handbook "mypci" by Murray Stokely
 */

#include <sys/param.h>	/* defines used in kernel.h */
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/kernel.h> /* types used in module initialization */
#include <sys/conf.h>	/* cdevsw struct */
#include <sys/mman.h>	/* mmap flags */
#include <sys/uio.h>	/* uio struct */
#include <sys/malloc.h>
#include <sys/bus.h>  /* structs, prototypes for pci bus stuff */

#include <machine/bus.h>
#include <sys/rman.h>
#include <machine/resource.h>

#include <dev/pci/pcivar.h> /* For get_pci macros! */
#include <dev/pci/pcireg.h>

/* Function prototypes */
static d_open_t		minipci_open;
static d_close_t	minipci_close;
static d_read_t		minipci_read;
static d_write_t	minipci_write;
static d_mmap_t		minipci_mmap;

/* Character device entry points */

static struct cdevsw minipci_cdevsw = {
	.d_open =	minipci_open,
	.d_close =	minipci_close,
	.d_read =	minipci_read,
	.d_write =	minipci_write,
	.d_mmap =	minipci_mmap,
	.d_name =	"minipci",
};

static devclass_t minipci_devclass;

struct minipci_softc {
	bus_space_handle_t	mp_bhandle;
	void			*mp_virt;
	u_long			mp_phys;
	bus_space_tag_t		mp_btag;
	struct resource		*mp_res;
	dev_t			mp_dev;
	size_t			mp_size;
};

#ifdef DEBUG_MINIPCI
#define DPRINTF(format, args...)	printf(format , ## args);
#else
#define DPRINTF(format, args...)
#endif

/* We're more interested in probe/attach than with
   open/close/read/write at this point */

static int
minipci_open(dev_t dev, int oflags, int devtype, d_thread_t *td)
{
	struct minipci_softc *sc = devclass_get_softc(minipci_devclass,
					dev2unit(dev));

	if (!sc)
		return (ENXIO);

	dev->si_drv1 = sc;
	DPRINTF("Opened device \"minipci\" successfully.\n");
	return (0);
}

static int
minipci_close(dev_t dev, int fflag, int devtype, d_thread_t *td)
{
	int err = 0;

	DPRINTF("Closing device \"minipci.\"\n");
	return (err);
}

static int
minipci_read(dev_t dev, struct uio *uio, int ioflag)
{
	struct minipci_softc	*sc = dev->si_drv1;
	int			amt;

	DPRINTF("minipci read!\n");
	amt = sc->mp_size;
	amt -= uio->uio_offset;
	if (uio->uio_resid < amt)
		amt = uio->uio_resid;
	if (amt <= 0)
		return 0;
	return uiomove((caddr_t)sc->mp_virt + uio->uio_offset, amt, uio);
}

static int
minipci_write(dev_t dev, struct uio *uio, int ioflag)
{
	struct minipci_softc	*sc = dev->si_drv1;
	int			amt;

	DPRINTF("minipci write!\n");
	amt = sc->mp_size;
	amt -= uio->uio_offset;
	if (uio->uio_resid < amt)
		amt = uio->uio_resid;
	if (amt <= 0)
		return 0;
	return uiomove((caddr_t)sc->mp_virt + uio->uio_offset, amt, uio);
}

static int
minipci_mmap(dev_t dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot)
{
	struct minipci_softc	*sc = dev->si_drv1;

	DPRINTF("minipci mmap!\n");
	if (nprot & PROT_EXEC)
		return -1;
	if (offset >= sc->mp_size)
		return -1;
	*paddr = sc->mp_phys + offset;
	return 0;
}


/* PCI Support Functions */

/*
 * Return identification string if this is device is ours.
 */
static int
minipci_probe(device_t dev)
{
	DPRINTF(dev, "MiniPCI Probe\nVendor ID : 0x%x\nDevice ID : 0x%x\n",
		pci_get_vendor(dev), pci_get_device(dev));

	if (pci_get_vendor(dev) == 0x106d &&
	    pci_get_device(dev) == 0x9500) {
		device_set_desc(dev, "Experimental Xilinx XC95108 Card");
		DPRINTF("Minipci card in sight\n");
		return (0);
	}
	return (ENXIO);
}

/* Attach function is only called if the probe is successful */

static int
minipci_attach(device_t dev)
{
	struct minipci_softc	*sc = device_get_softc(dev);
	int			rid, unit;

	DPRINTF("MiniPCI Attach for : deviceID : 0x%x\n",pci_get_vendor(dev));

	unit = device_get_unit(dev);

	rid = PCIR_BAR(0);
	sc->mp_res = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid,
			0, ~0, 1, RF_ACTIVE);
	if (sc->mp_res == NULL) {
		device_printf(dev,
			"MiniPCI unable to allocate PCI base register 0!\n");
		return (ENXIO);
	}
	sc->mp_btag = rman_get_bustag(sc->mp_res);
	sc->mp_bhandle = rman_get_bushandle(sc->mp_res);
	sc->mp_virt = rman_get_virtual(sc->mp_res);
	sc->mp_phys = rman_get_start(sc->mp_res);
	sc->mp_size = rman_get_size(sc->mp_res);

	sc->mp_dev = make_dev(&minipci_cdevsw, 0, UID_ROOT,
			GID_WHEEL, 0600, "minipci%d", unit);

	DPRINTF("MiniPCI device loaded.\n");
	return (0);
}

/* Detach device. */

static int
minipci_detach(device_t dev)
{
	struct minipci_softc	*sc = device_get_softc(dev);

	DPRINTF("MiniPCI detach!\n");

	if (sc->mp_res) {
		bus_release_resource(dev, SYS_RES_MEMORY,
					PCIR_BAR(0), sc->mp_res);
		destroy_dev(sc->mp_dev);
	}

	return (0);
}

/* Called during system shutdown after sync. */

static int
minipci_shutdown(device_t dev)
{

	DPRINTF("MiniPCI shutdown!\n");
	return (0);
}

/*
 * Device suspend routine.
 */
static int
minipci_suspend(device_t dev)
{

	DPRINTF("MiniPCI suspend!\n");
	return (0);
}

/*
 * Device resume routine.
 */

static int
minipci_resume(device_t dev)
{

	DPRINTF("MiniPCI resume!\n");
	return (0);
}

static device_method_t minipci_methods[] = {
	/* Device interface */
	DEVMETHOD(device_probe,		minipci_probe),
	DEVMETHOD(device_attach,	minipci_attach),
	DEVMETHOD(device_detach,	minipci_detach),
	DEVMETHOD(device_shutdown,	minipci_shutdown),
	DEVMETHOD(device_suspend,	minipci_suspend),
	DEVMETHOD(device_resume,	minipci_resume),

	{ 0, 0 }
};

static driver_t minipci_driver = {
	"minipci",
	minipci_methods,
	sizeof(struct minipci_softc),
};

DRIVER_MODULE(minipci, pci, minipci_driver, minipci_devclass, 0, 0);

/sys/modules/minipci/Makefile

.PATH: ${.CURDIR}/../../pci

KMOD=	minipci
SRCS=	minipci.c
SRCS+=	device_if.h bus_if.h pci_if.h

.include <bsd.kmod.mk>
project topBen Jackson