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>