Captain's Universe Home
Captain's Universe Home
Cosmic Ray Muon DetectorTeleGarden Pages
Time on MarsBryophyllum Plants
Jupiter Radio AstronomyAncient Pages
Salzburg Tourist GuideEarth Magnetometer
  H O M E     AJAX & MORE     LINUX & MORE     RTAI     XENOMAI     ADEOS IPIPE      
    JAVA & BROWSERS     *NIX     ELECTRONICS     REVIEWS     ARTEMIA     FAIRY SHRIMP      


FreeBSD Parallel Port Interrupt Device Driver Template/Skeleton (Kernel Module Example)


UPDATE: 27/JUN/2005: Major cleanup of the module source code.

This is a FreeBSD 5.3 / FreeBSD 6.0 Device Driver Template/Skeleton which implements parallel port interrupt handling and blocking read. The driver creates a device entry in /dev/, which can be used to communicate with the kernel module (read, write, ioctl).

  • Make a parallel port "null-modem": connect any data pin (pin 2-9) with the interrupt (ACK) pin (pin 10)
  • Compile the kernel module with "make" (Makefile)
  • Load the module with "load.sh"
  • Run "devtest" and generate an interrupt with "toggle"

"devtest" does an ioctl (IO CONTROL) (look at the kernel log in /var/log/messages). Furthermore it writes a string to kernel space and reads it back. The read operation on the device node blocks until an interrupt is received. "toggle" generates this interrupt.

Compilation is done with "gcc" - it is installed by default with the FreeBSD CD set.

Note on read-blocking:
In Linux we do sleeping and waking up in the kernel module with interruptible_sleep_on and wake_up_interruptible (see Linux Device Driver Template/Skeleton with Interrupt Handler and Device Read Blocking).
In *BSD the equivalent functions are tsleep and wakeup:
static int *dummy;
static void *evtchn_waddr = &dummy;
[...]
tsleep(evtchn_waddr, PWAIT, "evchwt", 10);
[...]
wakeup(evtchn_waddr);

Note for FreeBSD 6.0: For some reason the IOCTL action in "devtest" does not work. The message "devtest: ioctl: Inappropriate ioctl for device" appears when running "devtest" on 6.0. Just remove the "ioctl" from "devtest". If you know why, please contact me (email address at the bottom of this page).

UPDATE:
I met the same error massage as you: ": ioctl: Inappropriate ioctl for device".
It is because you just define TEST1 as 1,
but the macro _IOR or _IOW should be used instead:
 
#define TEST1 _IOR('g', 1, struct test_struct)
 
It worked for me.
 
/Niklas Nielsen
Thank you very much Niklas! :-)

parint.c
/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
 * ----------------------------------------------------------------------------
 */
// Based on pps.c

/* FreeBSD 5.3 / 6.0 Parallel Port Interrupt Device Driver /
                       Kernel Module Template/Skeleton
   ***************************************************
   
 Compile with: make
 Load the module with: kldload -v ./parint.ko
 Connect any parallel port data pin D0-7 (pin 2-9)
   with the interrupt (ACK) pin (pin 10)
 Run "devtest" (read blocks) - unblock the read with "toggle" (generates interrupt)

*/ 

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/timepps.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>
#include <sys/systm.h>
#include <sys/uio.h>

#include <dev/ppbus/ppbconf.h>
#include "ppbus_if.h"
#include <dev/ppbus/ppbio.h>

#define PARINT_NAME	"parint"		/* our official name */
#define TEST1 1
#define MAXMSGLEN 128

struct parint_data {
	int	parint_open;
	struct	ppb_device parint_dev;	
	struct	pps_state parint;
	device_t parintdev;
	struct cdev *devs[1];
	struct resource *intr_resource;	/* interrupt resource */
	void *intr_cookie;		/* interrupt registration cookie */
};

static void	parint_intr(void *arg);

#define DEVTOSOFTC(dev) ((struct parint_data *)device_get_softc(dev))
#define UNITOSOFTC(unit) \
	((struct parint_data *)devclass_get_softc(parint_devclass, (unit)))
#define UNITODEVICE(unit) (devclass_get_device(parint_devclass, (unit)))

int num;
static uint16_t *dummy;
static void *evtchn_waddr = &dummy;
unsigned int nodata = 1;
char teststr[MAXMSGLEN];

static devclass_t parint_devclass;

static d_open_t  parint_open;
static d_close_t parint_close;
static d_ioctl_t parint_ioctl;
static d_read_t  parint_read;
static d_write_t parint_write;

static struct cdevsw parint_cdevsw = {
        .d_version =	D_VERSION,
        .d_flags	=	D_NEEDGIANT,
        .d_open	=	parint_open,
        .d_close	=  parint_close,
        .d_ioctl	=  parint_ioctl,
        .d_read	=	parint_read,
        .d_write	=	parint_write,
        .d_name		=	PARINT_NAME,
};

static void parint_identify(driver_t *driver, device_t parent) {
	device_t dev;
	dev = device_find_child(parent, PARINT_NAME, 0);
	if (!dev)
		BUS_ADD_CHILD(parent, 0, PARINT_NAME, -1);
}

static int parint_probe(device_t parintdev) {
	struct parint_data *sc;
	struct cdev *dev;
	int unit;

	sc = DEVTOSOFTC(parintdev);
	bzero(sc, sizeof(struct parint_data));

	unit = device_get_unit(parintdev);
	dev = make_dev(&parint_cdevsw, unit, UID_ROOT, GID_WHEEL, 0666, PARINT_NAME "%d", unit);
	sc->devs[0] = dev;
	dev->si_drv1 = sc;
	dev->si_drv2 = (void*)0;

	device_set_desc(parintdev, "Parallel Port Interrupt Kernel Module");

	sc->parint.ppscap = PPS_CAPTUREASSERT | PPS_ECHOASSERT;
	pps_init(&sc->parint);
	return (0);
}

static int parint_attach(device_t dev) {
	struct parint_data *sc = DEVTOSOFTC(dev);
	device_t ppbus = device_get_parent(dev);
	int irq, zero = 0;

	/* retrieve the ppbus irq */
	BUS_READ_IVAR(ppbus, dev, PPBUS_IVAR_IRQ, &irq);

	if (irq > 0) {
		/* declare our interrupt handler */
		sc->intr_resource = bus_alloc_resource(dev, SYS_RES_IRQ,
				       &zero, irq, irq, 1, RF_SHAREABLE);
	}
	/* interrupts seem mandatory */
	if (sc->intr_resource == 0)
		return (ENXIO);

	return (0);
}

static int parint_detach(device_t dev) {
    struct parint_data *sc = DEVTOSOFTC(dev);
    // Unregister ourselves
    destroy_dev(sc->devs[0]);
    bus_generic_detach(dev);
    // Free interrupt
    bus_release_resource(dev, SYS_RES_IRQ, 0, sc->intr_resource);
    return(0);
}

static int parint_open(struct cdev *dev, int flags, int fmt, struct thread *td) {
	u_int unit = minor(dev);
	struct parint_data *sc = UNITOSOFTC(unit);
	device_t parintdev = UNITODEVICE(unit);
	device_t ppbus = device_get_parent(parintdev);
	int error;
	if (!sc->parint_open) {
		if (ppb_request_bus(ppbus, parintdev, PPB_WAIT|PPB_INTR))
			return (EINTR);

		/* attach the interrupt handler */
		if ((error = BUS_SETUP_INTR(ppbus, parintdev, sc->intr_resource,
			       INTR_TYPE_TTY, parint_intr, parintdev,
			       &sc->intr_cookie))) {
			ppb_release_bus(ppbus, parintdev);
			return (error);
		}

		ppb_wctr(ppbus, 0);
		ppb_wctr(ppbus, IRQENABLE);
		sc->parint_open = 1;
	}
	return(0);
}

static int parint_close(struct cdev *dev, int flags, int fmt, struct thread *td) {
	u_int unit = minor(dev);
	struct parint_data *sc = UNITOSOFTC(unit);
	device_t parintdev = UNITODEVICE(unit);
	device_t ppbus = device_get_parent(parintdev);
	num = 0; // reset interrupt counter
	sc->parint.ppsparam.mode = 0;	/* PHK ??? */

	ppb_wdtr(ppbus, 0);
	ppb_wctr(ppbus, 0);

	/* Note: the interrupt handler is automatically detached */
	ppb_release_bus(ppbus, parintdev);
	sc->parint_open = 0;
	num = 0; // reset interrupt counter
	return(0);
}

static void parint_intr(void *arg) {
	device_t parintdev = (device_t)arg;
	device_t ppbus = device_get_parent(parintdev);
	num++;
	// ignore first interrupt when handler is attached
	if (num > 1) {
		printf("PARALLEL PORT INTERRUPT HANDLED. num=%d\n", num);
		nodata = 0;
		wakeup(evtchn_waddr);
	}
	ppb_wctr(ppbus, IRQENABLE);	
}

static int parint_ioctl(struct cdev *dev, u_long cmd, caddr_t data,
	int flags, struct thread *td) {

	int error = 0;
	switch(cmd) {
	case TEST1:
		printf("parint IOCTL: TEST1\n");
		break;
	default:
		break;
	}
	return(error);
}

static int parint_read(struct cdev *dev, struct uio *uio, int ioflag) {
	int resid = MAXMSGLEN;
	int error = 0;
	unsigned int sst;
	//printf("reading...\n");
	
	while(nodata) {
		sst = tsleep(evtchn_waddr, PWAIT, "evchwt", 10);
	}
	nodata = 1;

	do {
		if (uio->uio_resid < resid)
			resid = uio->uio_resid;
		error = uiomove(teststr, resid, uio);	
	} while (resid > 0 && error == 0);
	return(error);
}

static int parint_write(struct cdev *dev, struct uio *uio, int flag) {
	int resid = MAXMSGLEN;
	int error = 0;
	//printf("writing...\n");
	do {
		if (uio->uio_resid < resid)
			resid = uio->uio_resid;
		error = uiomove(teststr, resid, uio);	
	} while (resid > 0 && error == 0);
	return(error);
}

static device_method_t parint_methods[] = {
	/* device interface */
	DEVMETHOD(device_identify,	parint_identify),
	DEVMETHOD(device_probe,		parint_probe),
	DEVMETHOD(device_attach,	parint_attach),
	DEVMETHOD(device_detach,	parint_detach),
	{ 0, 0 }
};

static driver_t parint_driver = {
	PARINT_NAME,
	parint_methods,
	sizeof(struct parint_data),
};
DRIVER_MODULE(parint, ppbus, parint_driver, parint_devclass, 0, 0);

devtest.c
// compile with: gcc -o devtest devtest.c

#include <sys/types.h>
#include <sys/ioctl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>

#define TEST1 1

int main(void) {
	int error, fd, wlen;
	double testval;
	char teststr[128];
	char mystring[] = "hello captain!";
	
	fd = open("/dev/parint0", O_RDWR);
	if (fd == -1)
		err(1, "open");

	error = ioctl(fd, TEST1, NULL);
	if (error == -1)
		err(1, "ioctl");

	wlen = strlen(mystring) + 1;
	error = write(fd, mystring, wlen);
	if (error == -1)
		err(1, "write");

	error = read(fd, teststr, 128);
	if (error == -1)
		err(1, "read");
	printf("read2: %s\n", teststr);

	close(fd);
	exit(0);
}

toggle.c
// gcc -o toggle toggle.c

#include <sys/types.h>
#include <machine/sysarch.h>
#include <stdio.h>

#define BASEPORT 0x378

static inline void outb (unsigned short int port, unsigned char val) {
        __asm__ volatile ("outb %0,%1\n"::"a" (val), "d" (port) );
}

int main(int argc, char *argv[]) {
	FILE* dummy;
	if (dummy = fopen("/dev/io", "rw")) {

		// enable INT/ACK and set D0-D7 to output
		outb (BASEPORT+2, 0x10);
		
		outb (BASEPORT, 0);
		usleep(10000);
		outb (BASEPORT, 255);
		usleep(10000);
		outb (BASEPORT, 0);
		fclose(dummy);
	}
	return 0;
}


Makefile
.PATH:  .
KMOD=   parint
SRCS=   bus_if.h device_if.h ppbus_if.h parint.c

.include <bsd.kmod.mk>

load.sh
#!/bin/sh
kldunload parint
kldload -v ./parint.ko

Last-Modified: Tue, 07 Feb 2006 19:57:06 GMT

Google
 
Web www.captain.at
go to top
© 1996-2010 . All rights reserved.
No reproduction, distribution, publishing or transmission of the copyrighted materials at this site is permitted. Policy
go to top