/*
* FreeBSD Driver for Lamp in MLH 310
* (Parallel Port driver to trigger PowerTail)
* Seth Long, Spring 2024
* http://isoptera.lcsc.edu/seth
*
* This is a *very* simple device driver. The PowerTail has a solid state
* amplification circuit followed by a relay, and can switch mains current.
* I use this as a device driver demo for CS430 Operating Systems. This
* driver is not compatible with other uses of the parallel port at the same
* time, simply sets all data pins high for on, all low for off. The powertail
* can be connected to any of the data pins. The driver makes no attempt to
* determine if there's anything else on the port, and assumes exclusive use.
*/
#include<sys/param.h>
#include<sys/uio.h>
#include<sys/types.h>
#include<sys/systm.h>
#include<sys/conf.h>
#include<sys/module.h>
#include<sys/kernel.h>
#include<sys/bus.h>
#include<sys/malloc.h>
#include<sys/pcpu.h>
#include<sys/sysctl.h>
#include "ppbus_if.h" /* Will be generated by make, include in the Makefile */
int status = 0;
struct cdev *lamp_dev;
/* Parallel Port Controller. NOT ppbus or ppi, although these could look
* like the right thing.
*
* Note that a device_t is "struct _device *" by typedef, but this constitutes
* an incomplete type in this code. The definition is in kern/subr_bus.c as
* of FreeBSD 13.2, but it looks (I'm no authority here) like kernel module
* authors should avoid using structure members directly, and instead rely on
* functions like device_get_name whenever access is needed. */
device_t ppc;
/* Note: This does not actually check to see if the user buffer is large
* enough, or if there's an offset less than 3 or 4. Therefore, the read
* must be accomplished in a single call to read. This is what will happen
* if the user runs something like "cat /dev/lamp" */
static int lamp_read(struct cdev *dev, struct uio *uio, int ioflag){
if(uio->uio_offset)
return 0;
if(status)
uiomove("ON\n", 3, uio);
else
uiomove("OFF\n", 4, uio);
return 0;
}
/* Note: Expected usage from userspace is something like this:
* echo on > /dev/lamp
* Or from code:
* write(lamp_fd, "on", 2);
* NOT this:
* write(lamp_fd, "o", 1);
* write(lamp_fd, "n", 1);
*
* Writing data to the port:
* PPBUS_IO type 14 will set the data register. There might be a constant I
* could have used instead. Setting the data register sets the state of the
* pins, so 0xff will set all pins high. The two arguments which are 0 are
* the address of a data area, and the size of the data area, but for a type
* 14, the last parameter is the value of the register. Despite the naming
* of PPBUS_IO, it needs the ppc, not the ppbus.
*/
static int lamp_write(struct cdev *dev, struct uio *uio, int ioflag){
char cmd[16];
size_t len = MIN(uio->uio_resid, 16);
uiomove(cmd, len, uio);
if(!strncmp(cmd, "on", 2) || !strncmp(cmd, "ON", 2)){
status = 1;
uprintf("Turning light ON\n");
printf("IO Result: %d\n", PPBUS_IO(ppc, 14, 0, 0, 0xff));
} else { /* Anything that isn't "on" or "ON" means off */
status = 0;
uprintf("Turning light OFF\n");
printf("IO Result: %d\n", PPBUS_IO(ppc, 14, 0, 0, 0x00));
}
return 0;
}
static struct cdevsw lamp_dev_description = {
.d_version = D_VERSION,
.d_write = lamp_write,
.d_read = lamp_read
};
/* print_all_devices is recursive, and prints the entire device tree. But it
* also sets the value of the global device_t ppc, so without calling this
* function, the driver won't work. Could be converted to search silently,
* and terminate once the correct device is found. It would be better to
* use an iterative solution here to avoid excessive stack use, but I was in
* a hurry when I wrote this. */
void print_all_devices(device_t, char*);
void print_all_devices(device_t startpoint, char *prefix){
device_t *devlist;
int devcount;
const char* name = device_get_name(startpoint);
if(name && !strcmp(name, "ppc"))
ppc = startpoint;
device_get_children(startpoint, &devlist, &devcount);
if(!devcount)
return;
char new_prefix[32];
snprintf(new_prefix, 32, "%s- ", prefix);
printf("%s Device count: %d\n", prefix, devcount);
for(int i = 0; i < devcount; i++) {
printf("%s %s : %s\n", prefix, device_get_name(devlist[i]), device_get_desc(devlist[i]));
print_all_devices(devlist[i], new_prefix);
}
free(devlist, M_TEMP);
}
static int lamp_loader(struct module *m, int what, void *arg) {
switch(what){
case MOD_LOAD:
ppc = 0;
print_all_devices(root_bus, "");
if(ppc)
printf("ppbus was found\n");
printf("Loading Lamp Module\n");
if(make_dev_p(0, &lamp_dev, &lamp_dev_description, 0, UID_ROOT, GID_WHEEL, 0666, "lamp"))
printf("Lamp Device Failed to load!\n");
break;
case MOD_UNLOAD:
destroy_dev(lamp_dev);
printf("Unloading Lamp Module\n");
break;
}
return 0;
}
static moduledata_t lamp_mod = {
"lamp_driver",
lamp_loader,
0
};
/* There is a DECLARE_DRIVER macro that may be more appropriate here */
DECLARE_MODULE(lamp_driver, lamp_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);