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