panel-poweroff.patch -- NetBSD 11.0/i386: power a Whistle InterJet off on "shutdown -p" by sending ESC X to its front panel (com1), once every file system is unmounted, and only with machdep.interjet_escx=1 and DCD low, that is, mains absent. Enable with "options INTERJET_PANEL_POWEROFF". Apply in usr/src with: patch -p0 Author: Royce Williams License: the same terms as each NetBSD source file it modifies. --- sys/arch/i386/conf/files.i386.orig +++ sys/arch/i386/conf/files.i386 @@ -45,6 +45,12 @@ defparam opt_beep.h BEEP_ONHALT_PITCH=1500 defparam opt_beep.h BEEP_ONHALT_PERIOD=250 +# Power a Whistle InterJet off from shutdown -p: ESC X to the front panel +# (com1) in cpu_reboot(). +# _TEST reads DCD inverted, for a QEMU control kernel only. +defflag opt_interjet_poweroff.h INTERJET_PANEL_POWEROFF +defflag opt_interjet_poweroff.h INTERJET_PANEL_POWEROFF_TEST + # Multiboot support defflag opt_multiboot.h MULTIBOOT obsolete defparam MULTIBOOT_SYMTAB_SPACE --- sys/arch/i386/i386/machdep.c.orig +++ sys/arch/i386/i386/machdep.c @@ -74,6 +74,7 @@ #include "opt_compat_netbsd.h" #include "opt_cpureset_delay.h" #include "opt_ddb.h" +#include "opt_interjet_poweroff.h" #include "opt_kgdb.h" #include "opt_mtrr.h" #include "opt_modular.h" @@ -805,6 +806,118 @@ splx(s); } +#ifdef INTERJET_PANEL_POWEROFF +#include /* com_lcr, com_msr, LCR_DLAB, MSR_DCD */ + +/* + * interjet_panel_poweroff: + * Local change. Power a Whistle + * InterJet off on shutdown -p by sending ESC X, 1b 58, to the front + * panel, com1 at 0x2f8. With mains absent the panel cuts power at once + * and the machine stays off; with mains present it power-CYCLES the + * machine, so ESC X is sent only while DCD, the panel's mains-present + * line, is low. WhistleWare's kernel sent the same two bytes at the + * same point in its boot(): after the sync, after vfs_unmountall() and a + * 100 ms settle, on a power-off only. + * + * It sends only when all of these hold. Otherwise it prints which one + * failed and returns, and the machine halts as it would without it. + * - machdep.interjet_escx is 1. It is 0 at boot; rc.d/lcdhalt sets it + * during shutdown -p when /etc/rc.conf has lcdhalt_escx=YES. + * - No file system is mounted. cpu_reboot() has unmounted them all by + * now, except after a panic, RB_NOSYNC or a cold halt, which skip the + * unmount. _mountlist_next() reads the list unlocked, which its + * comment reserves for DDB, where nothing else can change the list; + * that holds here too: one CPU, IPL_HIGH, every process killed by + * halt(8). The locked iterator allocates and takes an adaptive + * mutex, neither of which belongs at IPL_HIGH. + * - The UART is there and DLAB is clear. An absent UART reads 0xff; + * with DLAB set a data write would load the divisor. + * - DCD is low twice, before and after the 100 ms settle. An absent + * UART reads 0xff in MSR too, so it reads as mains present. + * + * Write only, and never the rate: the firmware's 57600 stands. com(4) + * has detached by now, and the data register is never read. Each byte + * waits for THRE with the bound com_common_putc() uses. ESC W, one bit + * away, arms the panel's reset watchdog, so the two bytes are a const + * array and nothing else is sent. + * + * INTERJET_PANEL_POWEROFF_TEST inverts the DCD reading so the send path + * runs under QEMU, whose UART reports DCD high. + * NEVER put it in a unit's config: on hardware it sends ESC X with mains + * present, and the machine power-cycles. + * + * Author: Royce Williams + */ +#define IJPOFF_BASE IO_COM2 /* 0x2f8, com1, the panel */ +#define IJPOFF_TIMO 150000 /* LSR polls per byte */ + +int interjet_escx = 0; /* machdep.interjet_escx */ + +SYSCTL_SETUP(sysctl_interjet_escx_setup, "machdep.interjet_escx") +{ + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT | CTLFLAG_READWRITE, CTLTYPE_INT, + "interjet_escx", + SYSCTL_DESCR("Send ESC X to the InterJet panel on power-off " + "when mains is absent"), + NULL, 0, &interjet_escx, 0, CTL_MACHDEP, CTL_CREATE, CTL_EOL); +} + +static void +interjet_panel_poweroff(void) +{ + static const uint8_t escx[] = { 0x1b, 'X' }; + uint8_t lcr, msr; + u_int i, t; + + if (interjet_escx == 0) { + printf("interjet: machdep.interjet_escx is 0: " + "ESC X not sent\n"); + return; + } + if (_mountlist_next(NULL) != NULL) { + printf("interjet: a file system is still mounted: " + "ESC X not sent\n"); + return; + } + lcr = inb(IJPOFF_BASE + com_lcr); + if (lcr == 0xff || (lcr & LCR_DLAB) != 0) { + printf("interjet: no usable UART at 0x%x (lcr 0x%02x): " + "ESC X not sent\n", IJPOFF_BASE, lcr); + return; + } + for (i = 0; i < 2; i++) { + if (i == 1) + delay(100000); /* WhistleWare's 100 ms settle */ + msr = inb(IJPOFF_BASE + com_msr); +#ifdef INTERJET_PANEL_POWEROFF_TEST + msr ^= MSR_DCD; /* QEMU control only */ +#endif + if ((msr & MSR_DCD) != 0) { + printf("interjet: DCD high (msr 0x%02x), mains present: " + "ESC X not sent\n", msr); + return; + } + } + printf("Sending the power down signal...\n"); /* WhistleWare's */ + for (i = 0; i < __arraycount(escx); i++) { + for (t = IJPOFF_TIMO; t > 0; t--) + if ((inb(IJPOFF_BASE + com_lsr) & LSR_TXRDY) != 0) + break; + if (t == 0) { + printf("interjet: UART never drained after %u of 2 " + "bytes\n", i); + return; + } + outb(IJPOFF_BASE + com_data, escx[i]); + } + /* Mains absent: the power goes within milliseconds. */ + delay(2000000); + printf("interjet: ESC X sent, and the power is still on\n"); +} +#endif /* INTERJET_PANEL_POWEROFF */ + void cpu_reboot(int howto, char *bootstr) { @@ -853,6 +966,9 @@ doshutdownhooks(); if ((howto & RB_POWERDOWN) == RB_POWERDOWN) { +#ifdef INTERJET_PANEL_POWEROFF + interjet_panel_poweroff(); +#endif #if NACPICA > 0 if (s != IPL_NONE) splx(s);