code: plan9front

Download patch

ref: 4f6e074f979e185f093f44b20a1021714c479b3b
parent: 9d20339e479551841d6d917669bf67b83893b126
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Fri Oct 7 21:20:32 EDT 2022

reform/pm: provide /dev/kbdoled to display a 126x32x1 bitmap on the keyboard OLED

--- a/sys/man/1/reform
+++ b/sys/man/1/reform
@@ -109,6 +109,10 @@
 .B cputemp
 Exposes the current temperature reading of the CPU.
 .TP
+.B kbdoled
+An image can be displayed on the keyboard OLED by writing a 126x32x1
+uncompressed Plan 9 image (with or without a header).
+.TP
 .B light
 Provides a way to control the backlight of the built-in LCD by
 writing \fIlcd [-+]N\fR or \fIkbd [-+]N\fR,
--- a/sys/src/cmd/reform/pm.c
+++ b/sys/src/cmd/reform/pm.c
@@ -3,6 +3,8 @@
 #include <fcall.h>
 #include <thread.h>
 #include <9p.h>
+#include <draw.h>
+#include <memdraw.h>
 
 enum
 {
@@ -23,8 +25,12 @@
 	Light = 1,
 	Temp,
 	Battery,
+	Kbdoled,
 	Pmctl,
 
+	KbdoledW = 126,
+	KbdoledH = 32,
+
 	Lcd = 0,
 	Kbd,
 
@@ -72,11 +78,13 @@
 		STAT_RR = 1<<3,
 };
 
+static char *uid = "pm";
 static Reqqueue *lpcreq;
 static u32int *pwm2, *tmu, *spi2;
 static int kbdlight = 0;
 static int kbdhidfd = -1;
-static char *uid = "pm";
+static Memimage *kbdoled;
+static u8int kbdoledraw[4+KbdoledW*KbdoledH/8] = {'W', 'B', 'I', 'T', 0};
 
 static void
 wr(u32int *base, int reg, u32int v)
@@ -125,26 +133,57 @@
 	char path[32], *s, *k, *e;
 	int f;
 
-	if(kbdhidfd >= 0)
-		return kbdhidfd;
-
-	if((f = open("/dev/usb/ctl", OREAD)) >= 0){
+	if(kbdhidfd < 0 && (f = open("/dev/usb/ctl", OREAD)) >= 0){
 		if((s = readall(f)) != nil &&
 			(k = strstr(s, "MNT 'Reform Keyboard'")) != nil &&
 			(e = strchr(k+22, ' ')) != nil){
 			*e = 0;
 			snprint(path, sizeof(path), "/dev/hidU%sctl", k+22);
-			if((kbdhidfd = open(path, OWRITE)) >= 0)
-				write(kbdhidfd, "rawon", 5);
+			if((kbdhidfd = open(path, OWRITE)) >= 0 && write(kbdhidfd, "rawon", 5) != 5){
+				close(kbdhidfd);
+				kbdhidfd = -1;
+			}
 		}
 		free(s);
 		close(f);
 	}
 
-	return kbdhidfd;
+	return kbdhidfd < 0;
 }
 
 static int
+loadkbdoled(void *data, int size)
+{
+	int x, y, i, k, v, bpl;
+	u8int *p, q;
+
+	bpl = bytesperline(kbdoled->r, kbdoled->depth);
+	if(size == 60+bpl*KbdoledH){
+		data = (u8int*)data + 60;
+		size -= 60;
+	}else if(size != bpl*KbdoledH){
+		werrstr("invalid image: expected %dx%d GREY1 (%d bytes)", KbdoledW, KbdoledH, bpl*KbdoledH);
+		return -1;
+	}
+
+	k = loadmemimage(kbdoled, kbdoled->r, data, size);
+	if(k < 0 || openkbdhid() != 0)
+		return -1;
+	for(y = 0, i = 4; y < KbdoledH; y += 8){
+		for(x = v = 0; x < KbdoledW; x++, v = (v+1)&7){
+			SET(p);
+			if(v == 0)
+				p = byteaddr(kbdoled, Pt(x,y));
+			for(k = q = 0; k < 8; k++)
+				q |= ((p[bpl*k] >> (7-v)) & 1) << k;
+			kbdoledraw[i++] = q;
+		}
+	}
+
+	return write(kbdhidfd, kbdoledraw, sizeof(kbdoledraw));
+}
+
+static int
 setlight(int k, int p)
 {
 	u32int v;
@@ -158,7 +197,7 @@
 		v = Pwmsrcclk / rd(pwm2, PWMSAR);
 		wr(pwm2, PWMPR, (Pwmsrcclk/(v*p/100))-2);
 		return 0;
-	}else if(k == Kbd && (kbdhidfd = openkbdhid()) >= 0){
+	}else if(k == Kbd && openkbdhid() == 0){
 		v = Kbdlightmax*p/100;
 		if(fprint(kbdhidfd, "LITE%c", '0'+v) > 0){
 			kbdlight = v;
@@ -420,10 +459,21 @@
 	int nf, v, p, k;
 	void *aux;
 
-	snprint(msg, sizeof(msg), "%.*s",
-		utfnlen((char*)r->ifcall.data, r->ifcall.count), (char*)r->ifcall.data);
-	nf = tokenize(msg, f, nelem(f));
 	aux = r->fid->file->aux;
+
+	if(aux == (void*)Kbdoled){
+		if(loadkbdoled(r->ifcall.data, r->ifcall.count) < 0){
+Err:
+			responderror(r);
+			return;
+		}
+		r->ofcall.count = r->ifcall.count;
+		respond(r, nil);
+		return;
+	}
+
+	snprint(msg, sizeof(msg), "%.*s", utfnlen(r->ifcall.data, r->ifcall.count), r->ifcall.data);
+	nf = tokenize(msg, f, nelem(f));
 	if(aux == (void*)Light){
 		if(nf < 2){
 Bad:
@@ -439,10 +489,8 @@
 		v = atoi(f[1]);
 		if(*f[1] == '+' || *f[1] == '-')
 			v += getlight(k);
-		if(setlight(k, v) != 0){
-			responderror(r);
-			return;
-		}
+		if(setlight(k, v) != 0)
+			goto Err;
 	}else if(aux == (void*)Pmctl){
 		p = -1;
 		if(nf >= 2 && strcmp(f[0], "power") == 0){
@@ -451,7 +499,7 @@
 				 * LPC firmware might not be up to date so try
 				 * shutting down through the keyboard first
 				 */
-				if((kbdhidfd = openkbdhid()) >= 0){
+				if(openkbdhid() == 0){
 					write(kbdhidfd, "PWR0", 4);
 					sleep(2000); /* give it a chance */
 				}
@@ -520,11 +568,16 @@
 	if((spi2 = segattach(0, "ecspi2", 0, 0x20)) == (void*)-1)
 		sysfatal("no spi2");
 	tmuinit();
+	if(memimageinit() != 0)
+		sysfatal("%r");
+	if((kbdoled = allocmemimage(Rect(0, 0, KbdoledW, KbdoledH), GREY1)) == nil)
+		sysfatal("%r");
 	lpcreq = reqqueuecreate();
 	fs.tree = alloctree(uid, uid, DMDIR|0555, nil);
 	createfile(fs.tree->root, "battery", uid, 0444, (void*)Battery);
 	createfile(fs.tree->root, "cputemp", uid, 0444, (void*)Temp);
 	createfile(fs.tree->root, "light", uid, 0666, (void*)Light);
+	createfile(fs.tree->root, "kbdoled", uid, 0222, (void*)Kbdoled);
 	createfile(fs.tree->root, "pmctl", uid, 0666, (void*)Pmctl);
 	threadpostmountsrv(&fs, srv, mtpt, MAFTER);