/* * 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 #include #include #include #include #include #include #include #include #include #include #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);