183030dd5SDavid du Colombier /* 283030dd5SDavid du Colombier * USB Human Interaction Device: keyboard and mouse. 383030dd5SDavid du Colombier * 483030dd5SDavid du Colombier * If there's no usb keyboard, it tries to setup the mouse, if any. 583030dd5SDavid du Colombier * It should be started at boot time. 683030dd5SDavid du Colombier * 7d40255d8SDavid du Colombier * Mouse events are converted to the format of mouse(3)'s mousein file. 883030dd5SDavid du Colombier * Keyboard keycodes are translated to scan codes and sent to kbin(3). 983030dd5SDavid du Colombier * 10d40255d8SDavid du Colombier * If there is no keyboard, it tries to setup the mouse properly, else it falls 11d40255d8SDavid du Colombier * back to boot protocol. 1283030dd5SDavid du Colombier */ 1383030dd5SDavid du Colombier 1483030dd5SDavid du Colombier #include <u.h> 1583030dd5SDavid du Colombier #include <libc.h> 1683030dd5SDavid du Colombier #include <thread.h> 1783030dd5SDavid du Colombier #include "usb.h" 1883030dd5SDavid du Colombier #include "hid.h" 1983030dd5SDavid du Colombier 20906943f9SDavid du Colombier enum 21906943f9SDavid du Colombier { 22906943f9SDavid du Colombier Awakemsg= 0xdeaddead, 23906943f9SDavid du Colombier Diemsg = 0xbeefbeef, 24*0cc6832dSDavid du Colombier Dwcidle = 8, 25906943f9SDavid du Colombier }; 26906943f9SDavid du Colombier 27906943f9SDavid du Colombier typedef struct KDev KDev; 28906943f9SDavid du Colombier typedef struct Kin Kin; 29906943f9SDavid du Colombier 30906943f9SDavid du Colombier struct KDev 31906943f9SDavid du Colombier { 32906943f9SDavid du Colombier Dev* dev; /* usb device*/ 33906943f9SDavid du Colombier Dev* ep; /* endpoint to get events */ 34906943f9SDavid du Colombier Kin* in; /* used to send events to kernel */ 35*0cc6832dSDavid du Colombier int idle; /* min time between reports (× 4ms) */ 36906943f9SDavid du Colombier Channel*repeatc; /* only for keyboard */ 37906943f9SDavid du Colombier int accel; /* only for mouse */ 38d40255d8SDavid du Colombier int bootp; /* has associated keyboard */ 39d40255d8SDavid du Colombier HidRepTempl templ; 40d40255d8SDavid du Colombier int (*ptrvals)(KDev *kd, Chain *ch, int *px, int *py, int *pb); 41906943f9SDavid du Colombier }; 42906943f9SDavid du Colombier 43906943f9SDavid du Colombier /* 44906943f9SDavid du Colombier * Kbdin and mousein files must be shared among all instances. 45906943f9SDavid du Colombier */ 46906943f9SDavid du Colombier struct Kin 47906943f9SDavid du Colombier { 48906943f9SDavid du Colombier int ref; 49906943f9SDavid du Colombier int fd; 50906943f9SDavid du Colombier char* name; 51906943f9SDavid du Colombier }; 52906943f9SDavid du Colombier 5383030dd5SDavid du Colombier /* 5483030dd5SDavid du Colombier * Map for the logitech bluetooth mouse with 8 buttons and wheels. 5583030dd5SDavid du Colombier * { ptr ->mouse} 5683030dd5SDavid du Colombier * { 0x01, 0x01 }, // left 5783030dd5SDavid du Colombier * { 0x04, 0x02 }, // middle 5883030dd5SDavid du Colombier * { 0x02, 0x04 }, // right 5983030dd5SDavid du Colombier * { 0x40, 0x08 }, // up 6083030dd5SDavid du Colombier * { 0x80, 0x10 }, // down 6183030dd5SDavid du Colombier * { 0x10, 0x08 }, // side up 6283030dd5SDavid du Colombier * { 0x08, 0x10 }, // side down 6383030dd5SDavid du Colombier * { 0x20, 0x02 }, // page 6483030dd5SDavid du Colombier * besides wheel and regular up/down report the 4th byte as 1/-1 6583030dd5SDavid du Colombier */ 6683030dd5SDavid du Colombier 6783030dd5SDavid du Colombier /* 6883030dd5SDavid du Colombier * key code to scan code; for the page table used by 6983030dd5SDavid du Colombier * the logitech bluetooth keyboard. 7083030dd5SDavid du Colombier */ 71906943f9SDavid du Colombier static char sctab[256] = 7283030dd5SDavid du Colombier { 7383030dd5SDavid du Colombier [0x00] 0x0, 0x0, 0x0, 0x0, 0x1e, 0x30, 0x2e, 0x20, 7483030dd5SDavid du Colombier [0x08] 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, 7583030dd5SDavid du Colombier [0x10] 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1f, 0x14, 7683030dd5SDavid du Colombier [0x18] 0x16, 0x2f, 0x11, 0x2d, 0x15, 0x2c, 0x2, 0x3, 7783030dd5SDavid du Colombier [0x20] 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 7883030dd5SDavid du Colombier [0x28] 0x1c, 0x1, 0xe, 0xf, 0x39, 0xc, 0xd, 0x1a, 7983030dd5SDavid du Colombier [0x30] 0x1b, 0x2b, 0x2b, 0x27, 0x28, 0x29, 0x33, 0x34, 8083030dd5SDavid du Colombier [0x38] 0x35, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 8183030dd5SDavid du Colombier [0x40] 0x41, 0x42, 0x43, 0x44, 0x57, 0x58, 0x63, 0x46, 8283030dd5SDavid du Colombier [0x48] 0x77, 0x52, 0x47, 0x49, 0x53, 0x4f, 0x51, 0x4d, 8383030dd5SDavid du Colombier [0x50] 0x4b, 0x50, 0x48, 0x45, 0x35, 0x37, 0x4a, 0x4e, 8483030dd5SDavid du Colombier [0x58] 0x1c, 0x4f, 0x50, 0x51, 0x4b, 0x4c, 0x4d, 0x47, 8583030dd5SDavid du Colombier [0x60] 0x48, 0x49, 0x52, 0x53, 0x56, 0x7f, 0x74, 0x75, 8683030dd5SDavid du Colombier [0x68] 0x55, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 8783030dd5SDavid du Colombier [0x70] 0x78, 0x79, 0x7a, 0x7b, 0x0, 0x0, 0x0, 0x0, 8883030dd5SDavid du Colombier [0x78] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71, 8983030dd5SDavid du Colombier [0x80] 0x73, 0x72, 0x0, 0x0, 0x0, 0x7c, 0x0, 0x0, 9083030dd5SDavid du Colombier [0x88] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 9183030dd5SDavid du Colombier [0x90] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 9283030dd5SDavid du Colombier [0x98] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 9383030dd5SDavid du Colombier [0xa0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 9483030dd5SDavid du Colombier [0xa8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 9583030dd5SDavid du Colombier [0xb0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 9683030dd5SDavid du Colombier [0xb8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 9783030dd5SDavid du Colombier [0xc0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 9883030dd5SDavid du Colombier [0xc8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 9983030dd5SDavid du Colombier [0xd0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 10083030dd5SDavid du Colombier [0xd8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 10183030dd5SDavid du Colombier [0xe0] 0x1d, 0x2a, 0x38, 0x7d, 0x61, 0x36, 0x64, 0x7e, 10283030dd5SDavid du Colombier [0xe8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x73, 0x72, 0x71, 10383030dd5SDavid du Colombier [0xf0] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 10483030dd5SDavid du Colombier [0xf8] 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 10583030dd5SDavid du Colombier }; 10683030dd5SDavid du Colombier 107906943f9SDavid du Colombier static QLock inlck; 108906943f9SDavid du Colombier static Kin kbdin = 109906943f9SDavid du Colombier { 110906943f9SDavid du Colombier .ref = 0, 111906943f9SDavid du Colombier .name = "#Ι/kbin", 112906943f9SDavid du Colombier .fd = -1, 113906943f9SDavid du Colombier }; 114906943f9SDavid du Colombier static Kin ptrin = 115906943f9SDavid du Colombier { 116906943f9SDavid du Colombier .ref = 0, 117906943f9SDavid du Colombier .name = "#m/mousein", 118906943f9SDavid du Colombier .fd = -1, 11983030dd5SDavid du Colombier }; 12083030dd5SDavid du Colombier 121906943f9SDavid du Colombier static int kbdebug; 12283030dd5SDavid du Colombier 123d40255d8SDavid du Colombier static int ptrbootpvals(KDev *kd, Chain *ch, int *px, int *py, int *pb); 124d40255d8SDavid du Colombier static int ptrrepvals(KDev *kd, Chain *ch, int *px, int *py, int *pb); 125d40255d8SDavid du Colombier 126d5789509SDavid du Colombier static int 127d40255d8SDavid du Colombier setbootproto(KDev* f, int eid, uchar *, int) 128d5789509SDavid du Colombier { 129*0cc6832dSDavid du Colombier int nr, r, id; 130d5789509SDavid du Colombier 131d40255d8SDavid du Colombier f->ptrvals = ptrbootpvals; 132d5789509SDavid du Colombier r = Rh2d|Rclass|Riface; 133d40255d8SDavid du Colombier dprint(2, "setting boot protocol\n"); 134d5789509SDavid du Colombier id = f->dev->usb->ep[eid]->iface->id; 135*0cc6832dSDavid du Colombier nr = usbcmd(f->dev, r, Setproto, Bootproto, id, nil, 0); 136*0cc6832dSDavid du Colombier if(nr < 0) 137*0cc6832dSDavid du Colombier return -1; 138*0cc6832dSDavid du Colombier usbcmd(f->dev, r, Setidle, f->idle<<8, id, nil, 0); 139*0cc6832dSDavid du Colombier return nr; 140d5789509SDavid du Colombier } 141d5789509SDavid du Colombier 142d40255d8SDavid du Colombier static uchar ignoredesc[128]; 143d40255d8SDavid du Colombier 144d40255d8SDavid du Colombier static int 145d40255d8SDavid du Colombier setfirstconfig(KDev* f, int eid, uchar *desc, int descsz) 146d40255d8SDavid du Colombier { 147d40255d8SDavid du Colombier int nr, r, id, i; 148d40255d8SDavid du Colombier 149d40255d8SDavid du Colombier dprint(2, "setting first config\n"); 150d40255d8SDavid du Colombier if(desc == nil){ 151d40255d8SDavid du Colombier descsz = sizeof ignoredesc; 152d40255d8SDavid du Colombier desc = ignoredesc; 153d40255d8SDavid du Colombier } 154d40255d8SDavid du Colombier id = f->dev->usb->ep[eid]->iface->id; 155d40255d8SDavid du Colombier r = Rh2d | Rstd | Rdev; 156d40255d8SDavid du Colombier nr =usbcmd(f->dev, r, Rsetconf, 1, id, nil, 0); 157d40255d8SDavid du Colombier if(nr < 0) 158d40255d8SDavid du Colombier return -1; 159d40255d8SDavid du Colombier r = Rh2d | Rclass | Riface; 160*0cc6832dSDavid du Colombier nr = usbcmd(f->dev, r, Setidle, f->idle<<8, id, nil, 0); 161d40255d8SDavid du Colombier if(nr < 0) 162d40255d8SDavid du Colombier return -1; 163d40255d8SDavid du Colombier r = Rd2h | Rstd | Riface; 164d40255d8SDavid du Colombier nr=usbcmd(f->dev, r, Rgetdesc, Dreport<<8, id, desc, descsz); 165d40255d8SDavid du Colombier if(nr < 0) 166d40255d8SDavid du Colombier return -1; 167d40255d8SDavid du Colombier if(kbdebug && nr > 0){ 168d40255d8SDavid du Colombier fprint(2, "report descriptor:"); 169d40255d8SDavid du Colombier for(i = 0; i < nr; i++){ 170eb2d6162SDavid du Colombier if(i%8 == 0) 171eb2d6162SDavid du Colombier fprint(2, "\n\t"); 172d40255d8SDavid du Colombier fprint(2, "%#2.2ux ", desc[i]); 173d40255d8SDavid du Colombier } 174d40255d8SDavid du Colombier fprint(2, "\n"); 175d40255d8SDavid du Colombier } 176d40255d8SDavid du Colombier f->ptrvals = ptrrepvals; 177d40255d8SDavid du Colombier return nr; 178d40255d8SDavid du Colombier } 179d40255d8SDavid du Colombier 180d5789509SDavid du Colombier /* 181d5789509SDavid du Colombier * Try to recover from a babble error. A port reset is the only way out. 182d5789509SDavid du Colombier * BUG: we should be careful not to reset a bundle with several devices. 183d5789509SDavid du Colombier */ 184d5789509SDavid du Colombier static void 185d5789509SDavid du Colombier recoverkb(KDev *f) 186d5789509SDavid du Colombier { 187d5789509SDavid du Colombier int i; 188d5789509SDavid du Colombier 189d5789509SDavid du Colombier close(f->dev->dfd); /* it's for usbd now */ 190d5789509SDavid du Colombier devctl(f->dev, "reset"); 191d5789509SDavid du Colombier for(i = 0; i < 10; i++){ 192d40255d8SDavid du Colombier if(i == 5) 193d40255d8SDavid du Colombier f->bootp++; 194d5789509SDavid du Colombier sleep(500); 195d5789509SDavid du Colombier if(opendevdata(f->dev, ORDWR) >= 0){ 196d40255d8SDavid du Colombier if(f->bootp) 197d40255d8SDavid du Colombier /* TODO func pointer */ 198d40255d8SDavid du Colombier setbootproto(f, f->ep->id, nil, 0); 199d40255d8SDavid du Colombier else 200d40255d8SDavid du Colombier setfirstconfig(f, f->ep->id, nil, 0); 201d5789509SDavid du Colombier break; 202d5789509SDavid du Colombier } 203d5789509SDavid du Colombier /* else usbd still working... */ 204d5789509SDavid du Colombier } 205d5789509SDavid du Colombier } 206d5789509SDavid du Colombier 207906943f9SDavid du Colombier static void 208906943f9SDavid du Colombier kbfatal(KDev *kd, char *sts) 20983030dd5SDavid du Colombier { 210906943f9SDavid du Colombier Dev *dev; 21183030dd5SDavid du Colombier 212906943f9SDavid du Colombier if(sts != nil) 21339dc1420SDavid du Colombier fprint(2, "kb: fatal: %s\n", sts); 21439dc1420SDavid du Colombier else 21539dc1420SDavid du Colombier fprint(2, "kb: exiting\n"); 216906943f9SDavid du Colombier if(kd->repeatc != nil) 21739dc1420SDavid du Colombier nbsendul(kd->repeatc, Diemsg); 21839dc1420SDavid du Colombier dev = kd->dev; 21939dc1420SDavid du Colombier kd->dev = nil; 22039dc1420SDavid du Colombier if(kd->ep != nil) 221906943f9SDavid du Colombier closedev(kd->ep); 222906943f9SDavid du Colombier kd->ep = nil; 22339dc1420SDavid du Colombier devctl(dev, "detach"); 224906943f9SDavid du Colombier closedev(dev); 225906943f9SDavid du Colombier /* 226906943f9SDavid du Colombier * free(kd); done by closedev. 227906943f9SDavid du Colombier */ 228906943f9SDavid du Colombier threadexits(sts); 22983030dd5SDavid du Colombier } 23083030dd5SDavid du Colombier 23183030dd5SDavid du Colombier static int 232906943f9SDavid du Colombier scale(KDev *f, int x) 23383030dd5SDavid du Colombier { 23483030dd5SDavid du Colombier int sign = 1; 23583030dd5SDavid du Colombier 23683030dd5SDavid du Colombier if(x < 0){ 23783030dd5SDavid du Colombier sign = -1; 23883030dd5SDavid du Colombier x = -x; 23983030dd5SDavid du Colombier } 24083030dd5SDavid du Colombier switch(x){ 24183030dd5SDavid du Colombier case 0: 24283030dd5SDavid du Colombier case 1: 24383030dd5SDavid du Colombier case 2: 24483030dd5SDavid du Colombier case 3: 24583030dd5SDavid du Colombier break; 24683030dd5SDavid du Colombier case 4: 247906943f9SDavid du Colombier x = 6 + (f->accel>>2); 24883030dd5SDavid du Colombier break; 24983030dd5SDavid du Colombier case 5: 250906943f9SDavid du Colombier x = 9 + (f->accel>>1); 25183030dd5SDavid du Colombier break; 25283030dd5SDavid du Colombier default: 25383030dd5SDavid du Colombier x *= MaxAcc; 25483030dd5SDavid du Colombier break; 25583030dd5SDavid du Colombier } 25683030dd5SDavid du Colombier return sign*x; 25783030dd5SDavid du Colombier } 25883030dd5SDavid du Colombier 259906943f9SDavid du Colombier /* 260906943f9SDavid du Colombier * ps2 mouse is processed mostly at interrupt time. 261906943f9SDavid du Colombier * for usb we do what we can. 262906943f9SDavid du Colombier */ 263906943f9SDavid du Colombier static void 264906943f9SDavid du Colombier sethipri(void) 265906943f9SDavid du Colombier { 266906943f9SDavid du Colombier char fn[30]; 267906943f9SDavid du Colombier int fd; 268906943f9SDavid du Colombier 269d40255d8SDavid du Colombier snprint(fn, sizeof fn, "/proc/%d/ctl", getpid()); 270906943f9SDavid du Colombier fd = open(fn, OWRITE); 271d40255d8SDavid du Colombier if(fd >= 0) { 272906943f9SDavid du Colombier fprint(fd, "pri 13"); 273906943f9SDavid du Colombier close(fd); 274906943f9SDavid du Colombier } 275d40255d8SDavid du Colombier } 276d40255d8SDavid du Colombier 277d40255d8SDavid du Colombier static int 278d40255d8SDavid du Colombier ptrrepvals(KDev *kd, Chain *ch, int *px, int *py, int *pb) 279d40255d8SDavid du Colombier { 280d40255d8SDavid du Colombier int i, x, y, b, c; 281d40255d8SDavid du Colombier static char buts[] = {0x0, 0x2, 0x1}; 282d40255d8SDavid du Colombier 283d40255d8SDavid du Colombier c = ch->e / 8; 284f7db6155SDavid du Colombier 285f7db6155SDavid du Colombier /* sometimes there is a report id, sometimes not */ 286f7db6155SDavid du Colombier if(c == kd->templ.sz + 1) 287f7db6155SDavid du Colombier if(ch->buf[0] == kd->templ.id) 288f7db6155SDavid du Colombier ch->b += 8; 289f7db6155SDavid du Colombier else 290f7db6155SDavid du Colombier return -1; 291d40255d8SDavid du Colombier parsereport(&kd->templ, ch); 292d40255d8SDavid du Colombier 293d40255d8SDavid du Colombier if(kbdebug) 294d40255d8SDavid du Colombier dumpreport(&kd->templ); 295d40255d8SDavid du Colombier if(c < 3) 296d40255d8SDavid du Colombier return -1; 297d40255d8SDavid du Colombier x = hidifcval(&kd->templ, KindX, 0); 298d40255d8SDavid du Colombier y = hidifcval(&kd->templ, KindY, 0); 299d40255d8SDavid du Colombier b = 0; 300d40255d8SDavid du Colombier for(i = 0; i<sizeof buts; i++) 301d40255d8SDavid du Colombier b |= (hidifcval(&kd->templ, KindButtons, i) & 1) << buts[i]; 302d40255d8SDavid du Colombier if(c > 3 && hidifcval(&kd->templ, KindWheel, 0) > 0) /* up */ 303d40255d8SDavid du Colombier b |= 0x10; 304d40255d8SDavid du Colombier if(c > 3 && hidifcval(&kd->templ, KindWheel, 0) < 0) /* down */ 305d40255d8SDavid du Colombier b |= 0x08; 306d40255d8SDavid du Colombier 307d40255d8SDavid du Colombier *px = x; 308d40255d8SDavid du Colombier *py = y; 309d40255d8SDavid du Colombier *pb = b; 310d40255d8SDavid du Colombier return 0; 311d40255d8SDavid du Colombier } 312d40255d8SDavid du Colombier 313d40255d8SDavid du Colombier static int 314d40255d8SDavid du Colombier ptrbootpvals(KDev *kd, Chain *ch, int *px, int *py, int *pb) 315d40255d8SDavid du Colombier { 316d40255d8SDavid du Colombier int b, c; 317d40255d8SDavid du Colombier char x, y; 318d40255d8SDavid du Colombier static char maptab[] = {0x0, 0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7}; 319d40255d8SDavid du Colombier 320d40255d8SDavid du Colombier c = ch->e / 8; 321d40255d8SDavid du Colombier if(c < 3) 322d40255d8SDavid du Colombier return -1; 323d40255d8SDavid du Colombier x = hidifcval(&kd->templ, KindX, 0); 324d40255d8SDavid du Colombier y = hidifcval(&kd->templ, KindY, 0); 325d40255d8SDavid du Colombier 326d40255d8SDavid du Colombier b = maptab[ch->buf[0] & 0x7]; 327d40255d8SDavid du Colombier if(c > 3 && ch->buf[3] == 1) /* up */ 328d40255d8SDavid du Colombier b |= 0x08; 329d40255d8SDavid du Colombier if(c > 3 && ch->buf[3] == 0xff) /* down */ 330d40255d8SDavid du Colombier b |= 0x10; 331d40255d8SDavid du Colombier *px = x; 332d40255d8SDavid du Colombier *py = y; 333d40255d8SDavid du Colombier *pb = b; 334d40255d8SDavid du Colombier return 0; 335d40255d8SDavid du Colombier } 336906943f9SDavid du Colombier 337906943f9SDavid du Colombier static void 33883030dd5SDavid du Colombier ptrwork(void* a) 33983030dd5SDavid du Colombier { 340d40255d8SDavid du Colombier int hipri, mfd, nerrs, x, y, b, c, ptrfd; 341906943f9SDavid du Colombier char mbuf[80]; 342d40255d8SDavid du Colombier Chain ch; 343906943f9SDavid du Colombier KDev* f = a; 34483030dd5SDavid du Colombier 345*0cc6832dSDavid du Colombier threadsetname("ptr %s", f->in->name); 346d5789509SDavid du Colombier hipri = nerrs = 0; 347906943f9SDavid du Colombier ptrfd = f->ep->dfd; 348906943f9SDavid du Colombier mfd = f->in->fd; 349d40255d8SDavid du Colombier if(f->ep->maxpkt < 3 || f->ep->maxpkt > MaxChLen) 35039dc1420SDavid du Colombier kbfatal(f, "weird mouse maxpkt"); 35183030dd5SDavid du Colombier for(;;){ 352d40255d8SDavid du Colombier memset(ch.buf, 0, MaxChLen); 35339dc1420SDavid du Colombier if(f->ep == nil) 35439dc1420SDavid du Colombier kbfatal(f, nil); 355d40255d8SDavid du Colombier c = read(ptrfd, ch.buf, f->ep->maxpkt); 35639dc1420SDavid du Colombier assert(f->dev != nil); 35739dc1420SDavid du Colombier assert(f->ep != nil); 358d5789509SDavid du Colombier if(c < 0){ 35939dc1420SDavid du Colombier dprint(2, "kb: mouse: %s: read: %r\n", f->ep->dir); 360d5789509SDavid du Colombier if(++nerrs < 3){ 361d5789509SDavid du Colombier recoverkb(f); 362d5789509SDavid du Colombier continue; 363d5789509SDavid du Colombier } 364d5789509SDavid du Colombier } 365906943f9SDavid du Colombier if(c <= 0) 366906943f9SDavid du Colombier kbfatal(f, nil); 367d40255d8SDavid du Colombier ch.b = 0; 368d40255d8SDavid du Colombier ch.e = 8 * c; 369d40255d8SDavid du Colombier if(f->ptrvals(f, &ch, &x, &y, &b) < 0) 37083030dd5SDavid du Colombier continue; 371906943f9SDavid du Colombier if(f->accel){ 372d40255d8SDavid du Colombier x = scale(f, x); 373d40255d8SDavid du Colombier y = scale(f, y); 37483030dd5SDavid du Colombier } 375d5789509SDavid du Colombier if(kbdebug > 1) 37639dc1420SDavid du Colombier fprint(2, "kb: m%11d %11d %11d\n", x, y, b); 377906943f9SDavid du Colombier seprint(mbuf, mbuf+sizeof(mbuf), "m%11d %11d %11d", x, y,b); 37839dc1420SDavid du Colombier if(write(mfd, mbuf, strlen(mbuf)) < 0) 37939dc1420SDavid du Colombier kbfatal(f, "mousein i/o"); 380906943f9SDavid du Colombier if(hipri == 0){ 381906943f9SDavid du Colombier sethipri(); 382906943f9SDavid du Colombier hipri = 1; 38383030dd5SDavid du Colombier } 38483030dd5SDavid du Colombier } 38583030dd5SDavid du Colombier } 38683030dd5SDavid du Colombier 38783030dd5SDavid du Colombier static void 388906943f9SDavid du Colombier stoprepeat(KDev *f) 38983030dd5SDavid du Colombier { 390906943f9SDavid du Colombier sendul(f->repeatc, Awakemsg); 39183030dd5SDavid du Colombier } 39283030dd5SDavid du Colombier 39383030dd5SDavid du Colombier static void 394906943f9SDavid du Colombier startrepeat(KDev *f, uchar esc1, uchar sc) 39583030dd5SDavid du Colombier { 39683030dd5SDavid du Colombier ulong c; 39783030dd5SDavid du Colombier 39883030dd5SDavid du Colombier if(esc1) 39983030dd5SDavid du Colombier c = SCesc1 << 8 | (sc & 0xff); 40083030dd5SDavid du Colombier else 40183030dd5SDavid du Colombier c = sc; 402906943f9SDavid du Colombier sendul(f->repeatc, c); 40383030dd5SDavid du Colombier } 40483030dd5SDavid du Colombier 40583030dd5SDavid du Colombier static void 406906943f9SDavid du Colombier putscan(int kbinfd, uchar esc, uchar sc) 40783030dd5SDavid du Colombier { 408906943f9SDavid du Colombier uchar s[2] = {SCesc1, 0}; 40983030dd5SDavid du Colombier 41083030dd5SDavid du Colombier if(sc == 0x41){ 411d5789509SDavid du Colombier kbdebug += 2; 41283030dd5SDavid du Colombier return; 41383030dd5SDavid du Colombier } 41483030dd5SDavid du Colombier if(sc == 0x42){ 415906943f9SDavid du Colombier kbdebug = 0; 41683030dd5SDavid du Colombier return; 41783030dd5SDavid du Colombier } 418906943f9SDavid du Colombier if(kbdebug) 41983030dd5SDavid du Colombier fprint(2, "sc: %x %x\n", (esc? SCesc1: 0), sc); 42083030dd5SDavid du Colombier s[1] = sc; 42183030dd5SDavid du Colombier if(esc && sc != 0) 42283030dd5SDavid du Colombier write(kbinfd, s, 2); 42383030dd5SDavid du Colombier else if(sc != 0) 42483030dd5SDavid du Colombier write(kbinfd, s+1, 1); 42583030dd5SDavid du Colombier } 42683030dd5SDavid du Colombier 42783030dd5SDavid du Colombier static void 428906943f9SDavid du Colombier repeatproc(void* a) 42983030dd5SDavid du Colombier { 430906943f9SDavid du Colombier KDev *f; 431906943f9SDavid du Colombier Channel *repeatc; 432906943f9SDavid du Colombier int kbdinfd; 433906943f9SDavid du Colombier ulong l, t, i; 43483030dd5SDavid du Colombier uchar esc1, sc; 43583030dd5SDavid du Colombier 436*0cc6832dSDavid du Colombier threadsetname("kbd repeat"); 437906943f9SDavid du Colombier /* 438906943f9SDavid du Colombier * too many jumps here. 439906943f9SDavid du Colombier * Rewrite instead of debug, if needed. 440906943f9SDavid du Colombier */ 441906943f9SDavid du Colombier f = a; 442906943f9SDavid du Colombier repeatc = f->repeatc; 443906943f9SDavid du Colombier kbdinfd = f->in->fd; 444906943f9SDavid du Colombier l = Awakemsg; 445906943f9SDavid du Colombier Repeat: 446906943f9SDavid du Colombier if(l == Diemsg) 447906943f9SDavid du Colombier goto Abort; 448906943f9SDavid du Colombier while(l == Awakemsg) 449906943f9SDavid du Colombier l = recvul(repeatc); 450906943f9SDavid du Colombier if(l == Diemsg) 451906943f9SDavid du Colombier goto Abort; 45283030dd5SDavid du Colombier esc1 = l >> 8; 45383030dd5SDavid du Colombier sc = l; 454906943f9SDavid du Colombier t = 160; 45583030dd5SDavid du Colombier for(;;){ 456906943f9SDavid du Colombier for(i = 0; i < t; i += 5){ 457906943f9SDavid du Colombier if(l = nbrecvul(repeatc)) 458906943f9SDavid du Colombier goto Repeat; 459906943f9SDavid du Colombier sleep(5); 46083030dd5SDavid du Colombier } 461906943f9SDavid du Colombier putscan(kbdinfd, esc1, sc); 462906943f9SDavid du Colombier t = 30; 46383030dd5SDavid du Colombier } 464906943f9SDavid du Colombier Abort: 465906943f9SDavid du Colombier chanfree(repeatc); 466906943f9SDavid du Colombier threadexits("aborted"); 467906943f9SDavid du Colombier 46883030dd5SDavid du Colombier } 46983030dd5SDavid du Colombier 47083030dd5SDavid du Colombier 47183030dd5SDavid du Colombier #define hasesc1(sc) (((sc) > 0x47) || ((sc) == 0x38)) 47283030dd5SDavid du Colombier 47383030dd5SDavid du Colombier static void 474906943f9SDavid du Colombier putmod(int fd, uchar mods, uchar omods, uchar mask, uchar esc, uchar sc) 47583030dd5SDavid du Colombier { 476906943f9SDavid du Colombier /* BUG: Should be a single write */ 47783030dd5SDavid du Colombier if((mods&mask) && !(omods&mask)) 478906943f9SDavid du Colombier putscan(fd, esc, sc); 47983030dd5SDavid du Colombier if(!(mods&mask) && (omods&mask)) 480906943f9SDavid du Colombier putscan(fd, esc, Keyup|sc); 48183030dd5SDavid du Colombier } 48283030dd5SDavid du Colombier 48383030dd5SDavid du Colombier /* 48483030dd5SDavid du Colombier * This routine diffs the state with the last known state 48583030dd5SDavid du Colombier * and invents the scan codes that would have been sent 48683030dd5SDavid du Colombier * by a non-usb keyboard in that case. This also requires supplying 48783030dd5SDavid du Colombier * the extra esc1 byte as well as keyup flags. 48883030dd5SDavid du Colombier * The aim is to allow future addition of other keycode pages 48983030dd5SDavid du Colombier * for other keyboards. 49083030dd5SDavid du Colombier */ 491906943f9SDavid du Colombier static uchar 492906943f9SDavid du Colombier putkeys(KDev *f, uchar buf[], uchar obuf[], int n, uchar dk) 49383030dd5SDavid du Colombier { 49483030dd5SDavid du Colombier int i, j; 495906943f9SDavid du Colombier uchar uk; 496906943f9SDavid du Colombier int fd; 49783030dd5SDavid du Colombier 498906943f9SDavid du Colombier fd = f->in->fd; 499906943f9SDavid du Colombier putmod(fd, buf[0], obuf[0], Mctrl, 0, SCctrl); 500906943f9SDavid du Colombier putmod(fd, buf[0], obuf[0], (1<<Mlshift), 0, SClshift); 501906943f9SDavid du Colombier putmod(fd, buf[0], obuf[0], (1<<Mrshift), 0, SCrshift); 502906943f9SDavid du Colombier putmod(fd, buf[0], obuf[0], Mcompose, 0, SCcompose); 503906943f9SDavid du Colombier putmod(fd, buf[0], obuf[0], Maltgr, 1, SCcompose); 50483030dd5SDavid du Colombier 50583030dd5SDavid du Colombier /* Report key downs */ 50683030dd5SDavid du Colombier for(i = 2; i < n; i++){ 50783030dd5SDavid du Colombier for(j = 2; j < n; j++) 50883030dd5SDavid du Colombier if(buf[i] == obuf[j]) 50983030dd5SDavid du Colombier break; 51083030dd5SDavid du Colombier if(j == n && buf[i] != 0){ 511906943f9SDavid du Colombier dk = sctab[buf[i]]; 512906943f9SDavid du Colombier putscan(fd, hasesc1(dk), dk); 513906943f9SDavid du Colombier startrepeat(f, hasesc1(dk), dk); 51483030dd5SDavid du Colombier } 51583030dd5SDavid du Colombier } 51683030dd5SDavid du Colombier 51783030dd5SDavid du Colombier /* Report key ups */ 518906943f9SDavid du Colombier uk = 0; 51983030dd5SDavid du Colombier for(i = 2; i < n; i++){ 52083030dd5SDavid du Colombier for(j = 2; j < n; j++) 52183030dd5SDavid du Colombier if(obuf[i] == buf[j]) 52283030dd5SDavid du Colombier break; 52383030dd5SDavid du Colombier if(j == n && obuf[i] != 0){ 524906943f9SDavid du Colombier uk = sctab[obuf[i]]; 525906943f9SDavid du Colombier putscan(fd, hasesc1(uk), uk|Keyup); 52683030dd5SDavid du Colombier } 52783030dd5SDavid du Colombier } 528906943f9SDavid du Colombier if(uk && (dk == 0 || dk == uk)){ 529906943f9SDavid du Colombier stoprepeat(f); 530906943f9SDavid du Colombier dk = 0; 531906943f9SDavid du Colombier } 532906943f9SDavid du Colombier return dk; 53383030dd5SDavid du Colombier } 53483030dd5SDavid du Colombier 53583030dd5SDavid du Colombier static int 53683030dd5SDavid du Colombier kbdbusy(uchar* buf, int n) 53783030dd5SDavid du Colombier { 53883030dd5SDavid du Colombier int i; 53983030dd5SDavid du Colombier 54083030dd5SDavid du Colombier for(i = 1; i < n; i++) 54183030dd5SDavid du Colombier if(buf[i] == 0 || buf[i] != buf[0]) 54283030dd5SDavid du Colombier return 0; 54383030dd5SDavid du Colombier return 1; 54483030dd5SDavid du Colombier } 54583030dd5SDavid du Colombier 546906943f9SDavid du Colombier static void 54783030dd5SDavid du Colombier kbdwork(void *a) 54883030dd5SDavid du Colombier { 549a23bc242SDavid du Colombier int c, i, kbdfd, nerrs; 550a23bc242SDavid du Colombier uchar dk, buf[64], lbuf[64]; 551a23bc242SDavid du Colombier char err[128]; 552906943f9SDavid du Colombier KDev *f = a; 55383030dd5SDavid du Colombier 554*0cc6832dSDavid du Colombier threadsetname("kbd %s", f->in->name); 555906943f9SDavid du Colombier kbdfd = f->ep->dfd; 556906943f9SDavid du Colombier 557906943f9SDavid du Colombier if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf) 558906943f9SDavid du Colombier kbfatal(f, "weird maxpkt"); 559906943f9SDavid du Colombier 560906943f9SDavid du Colombier f->repeatc = chancreate(sizeof(ulong), 0); 561906943f9SDavid du Colombier if(f->repeatc == nil) 562906943f9SDavid du Colombier kbfatal(f, "chancreate failed"); 563906943f9SDavid du Colombier 564906943f9SDavid du Colombier proccreate(repeatproc, f, Stack); 56583030dd5SDavid du Colombier memset(lbuf, 0, sizeof lbuf); 566a23bc242SDavid du Colombier dk = nerrs = 0; 56783030dd5SDavid du Colombier for(;;){ 56883030dd5SDavid du Colombier memset(buf, 0, sizeof buf); 569906943f9SDavid du Colombier c = read(kbdfd, buf, f->ep->maxpkt); 57039dc1420SDavid du Colombier assert(f->dev != nil); 57139dc1420SDavid du Colombier assert(f->ep != nil); 572a23bc242SDavid du Colombier if(c < 0){ 573a23bc242SDavid du Colombier rerrstr(err, sizeof(err)); 574d5789509SDavid du Colombier fprint(2, "kb: %s: read: %s\n", f->ep->dir, err); 575a23bc242SDavid du Colombier if(strstr(err, "babble") != 0 && ++nerrs < 3){ 576a23bc242SDavid du Colombier recoverkb(f); 577a23bc242SDavid du Colombier continue; 578a23bc242SDavid du Colombier } 579a23bc242SDavid du Colombier } 580906943f9SDavid du Colombier if(c <= 0) 58139dc1420SDavid du Colombier kbfatal(f, nil); 58283030dd5SDavid du Colombier if(c < 3) 58383030dd5SDavid du Colombier continue; 58483030dd5SDavid du Colombier if(kbdbusy(buf + 2, c - 2)) 58583030dd5SDavid du Colombier continue; 586d5789509SDavid du Colombier if(usbdebug > 2 || kbdebug > 1){ 58783030dd5SDavid du Colombier fprint(2, "kbd mod %x: ", buf[0]); 58883030dd5SDavid du Colombier for(i = 2; i < c; i++) 58983030dd5SDavid du Colombier fprint(2, "kc %x ", buf[i]); 59083030dd5SDavid du Colombier fprint(2, "\n"); 59183030dd5SDavid du Colombier } 592906943f9SDavid du Colombier dk = putkeys(f, buf, lbuf, f->ep->maxpkt, dk); 59383030dd5SDavid du Colombier memmove(lbuf, buf, c); 594a23bc242SDavid du Colombier nerrs = 0; 59583030dd5SDavid du Colombier } 59683030dd5SDavid du Colombier } 59783030dd5SDavid du Colombier 59883030dd5SDavid du Colombier static void 599906943f9SDavid du Colombier freekdev(void *a) 60083030dd5SDavid du Colombier { 601906943f9SDavid du Colombier KDev *kd; 60283030dd5SDavid du Colombier 603906943f9SDavid du Colombier kd = a; 604906943f9SDavid du Colombier if(kd->in != nil){ 605906943f9SDavid du Colombier qlock(&inlck); 606906943f9SDavid du Colombier if(--kd->in->ref == 0){ 607906943f9SDavid du Colombier close(kd->in->fd); 608906943f9SDavid du Colombier kd->in->fd = -1; 60983030dd5SDavid du Colombier } 610906943f9SDavid du Colombier qunlock(&inlck); 61183030dd5SDavid du Colombier } 61239dc1420SDavid du Colombier dprint(2, "freekdev\n"); 613906943f9SDavid du Colombier free(kd); 61483030dd5SDavid du Colombier } 61583030dd5SDavid du Colombier 61683030dd5SDavid du Colombier static void 617d40255d8SDavid du Colombier kbstart(Dev *d, Ep *ep, Kin *in, void (*f)(void*), KDev *kd) 618906943f9SDavid du Colombier { 619d40255d8SDavid du Colombier uchar desc[128]; 620*0cc6832dSDavid du Colombier int n, res; 621906943f9SDavid du Colombier 622906943f9SDavid du Colombier qlock(&inlck); 623906943f9SDavid du Colombier if(in->fd < 0){ 624906943f9SDavid du Colombier in->fd = open(in->name, OWRITE); 625906943f9SDavid du Colombier if(in->fd < 0){ 626906943f9SDavid du Colombier fprint(2, "kb: %s: %r\n", in->name); 627906943f9SDavid du Colombier qunlock(&inlck); 628906943f9SDavid du Colombier return; 629906943f9SDavid du Colombier } 630906943f9SDavid du Colombier } 63139dc1420SDavid du Colombier in->ref++; /* for kd->in = in */ 632906943f9SDavid du Colombier qunlock(&inlck); 633906943f9SDavid du Colombier d->free = freekdev; 634906943f9SDavid du Colombier kd->in = in; 635906943f9SDavid du Colombier kd->dev = d; 636d40255d8SDavid du Colombier res = -1; 637f7db6155SDavid du Colombier kd->ep = openep(d, ep->id); 638f7db6155SDavid du Colombier if(kd->ep == nil){ 639f7db6155SDavid du Colombier fprint(2, "kb: %s: openep %d: %r\n", d->dir, ep->id); 640f7db6155SDavid du Colombier return; 641f7db6155SDavid du Colombier } 642*0cc6832dSDavid du Colombier if(in == &kbdin){ 643*0cc6832dSDavid du Colombier /* 644*0cc6832dSDavid du Colombier * DWC OTG controller misses some split transaction inputs. 645*0cc6832dSDavid du Colombier * Set nonzero idle time to return more frequent reports 646*0cc6832dSDavid du Colombier * of keyboard state, to avoid losing key up/down events. 647*0cc6832dSDavid du Colombier */ 648*0cc6832dSDavid du Colombier n = read(d->cfd, desc, sizeof desc - 1); 649*0cc6832dSDavid du Colombier if(n > 0){ 650*0cc6832dSDavid du Colombier desc[n] = 0; 651*0cc6832dSDavid du Colombier if(strstr((char*)desc, "dwcotg") != nil) 652*0cc6832dSDavid du Colombier kd->idle = Dwcidle; 653*0cc6832dSDavid du Colombier } 654*0cc6832dSDavid du Colombier } 655d40255d8SDavid du Colombier if(!kd->bootp) 656d40255d8SDavid du Colombier res= setfirstconfig(kd, ep->id, desc, sizeof desc); 657d40255d8SDavid du Colombier if(res > 0) 658d40255d8SDavid du Colombier res = parsereportdesc(&kd->templ, desc, sizeof desc); 659d40255d8SDavid du Colombier /* if we could not set the first config, we give up */ 660d40255d8SDavid du Colombier if(kd->bootp || res < 0){ 661d40255d8SDavid du Colombier kd->bootp = 1; 662d40255d8SDavid du Colombier if(setbootproto(kd, ep->id, nil, 0) < 0){ 663906943f9SDavid du Colombier fprint(2, "kb: %s: bootproto: %r\n", d->dir); 664906943f9SDavid du Colombier return; 665906943f9SDavid du Colombier } 666d40255d8SDavid du Colombier }else if(kbdebug) 667d40255d8SDavid du Colombier dumpreport(&kd->templ); 668906943f9SDavid du Colombier if(opendevdata(kd->ep, OREAD) < 0){ 669906943f9SDavid du Colombier fprint(2, "kb: %s: opendevdata: %r\n", kd->ep->dir); 670906943f9SDavid du Colombier closedev(kd->ep); 671906943f9SDavid du Colombier kd->ep = nil; 672906943f9SDavid du Colombier return; 673906943f9SDavid du Colombier } 674906943f9SDavid du Colombier 675906943f9SDavid du Colombier incref(d); 676906943f9SDavid du Colombier proccreate(f, kd, Stack); 677906943f9SDavid du Colombier } 678906943f9SDavid du Colombier 679906943f9SDavid du Colombier static int 68083030dd5SDavid du Colombier usage(void) 68183030dd5SDavid du Colombier { 682d40255d8SDavid du Colombier werrstr("usage: usb/kb [-bdkm] [-a n] [-N nb]"); 683906943f9SDavid du Colombier return -1; 68483030dd5SDavid du Colombier } 68583030dd5SDavid du Colombier 686906943f9SDavid du Colombier int 687906943f9SDavid du Colombier kbmain(Dev *d, int argc, char* argv[]) 68883030dd5SDavid du Colombier { 689d40255d8SDavid du Colombier int bootp, i, kena, pena, accel, devid; 690906943f9SDavid du Colombier Ep *ep; 691d40255d8SDavid du Colombier KDev *kd; 692d40255d8SDavid du Colombier Usbdev *ud; 69383030dd5SDavid du Colombier 694906943f9SDavid du Colombier kena = pena = 1; 695d40255d8SDavid du Colombier bootp = 0; 696906943f9SDavid du Colombier accel = 0; 697ed868a7cSDavid du Colombier devid = d->id; 69883030dd5SDavid du Colombier ARGBEGIN{ 69983030dd5SDavid du Colombier case 'a': 700ed868a7cSDavid du Colombier accel = strtol(EARGF(usage()), nil, 0); 70183030dd5SDavid du Colombier break; 70283030dd5SDavid du Colombier case 'd': 703906943f9SDavid du Colombier kbdebug++; 70483030dd5SDavid du Colombier break; 70583030dd5SDavid du Colombier case 'k': 706906943f9SDavid du Colombier kena = 1; 707906943f9SDavid du Colombier pena = 0; 70883030dd5SDavid du Colombier break; 70983030dd5SDavid du Colombier case 'm': 710906943f9SDavid du Colombier kena = 0; 711906943f9SDavid du Colombier pena = 1; 71283030dd5SDavid du Colombier break; 713ed868a7cSDavid du Colombier case 'N': 714ed868a7cSDavid du Colombier devid = atoi(EARGF(usage())); /* ignore dev number */ 715ed868a7cSDavid du Colombier break; 716d40255d8SDavid du Colombier case 'b': 717d40255d8SDavid du Colombier bootp++; 718d40255d8SDavid du Colombier break; 71983030dd5SDavid du Colombier default: 720906943f9SDavid du Colombier return usage(); 72183030dd5SDavid du Colombier }ARGEND; 722d40255d8SDavid du Colombier if(argc != 0) 723906943f9SDavid du Colombier return usage(); 724ed868a7cSDavid du Colombier USED(devid); 725906943f9SDavid du Colombier ud = d->usb; 726906943f9SDavid du Colombier d->aux = nil; 727906943f9SDavid du Colombier dprint(2, "kb: main: dev %s ref %ld\n", d->dir, d->ref); 728d40255d8SDavid du Colombier 729d40255d8SDavid du Colombier if(kena) 730d40255d8SDavid du Colombier for(i = 0; i < nelem(ud->ep); i++) 731d40255d8SDavid du Colombier if((ep = ud->ep[i]) == nil) 732d40255d8SDavid du Colombier break; 733d40255d8SDavid du Colombier else if(ep->iface->csp == KbdCSP) 734d40255d8SDavid du Colombier bootp = 1; 735d40255d8SDavid du Colombier 736906943f9SDavid du Colombier for(i = 0; i < nelem(ud->ep); i++){ 737906943f9SDavid du Colombier if((ep = ud->ep[i]) == nil) 738906943f9SDavid du Colombier break; 739d40255d8SDavid du Colombier if(kena && ep->type == Eintr && ep->dir == Ein && 740d40255d8SDavid du Colombier ep->iface->csp == KbdCSP){ 741d40255d8SDavid du Colombier kd = d->aux = emallocz(sizeof(KDev), 1); 742d40255d8SDavid du Colombier kd->accel = 0; 743d40255d8SDavid du Colombier kd->bootp = 1; 744d40255d8SDavid du Colombier kbstart(d, ep, &kbdin, kbdwork, kd); 745d40255d8SDavid du Colombier } 746d40255d8SDavid du Colombier if(pena && ep->type == Eintr && ep->dir == Ein && 747d40255d8SDavid du Colombier ep->iface->csp == PtrCSP){ 748d40255d8SDavid du Colombier kd = d->aux = emallocz(sizeof(KDev), 1); 749d40255d8SDavid du Colombier kd->accel = accel; 750d40255d8SDavid du Colombier kd->bootp = bootp; 751d40255d8SDavid du Colombier kbstart(d, ep, &ptrin, ptrwork, kd); 752d40255d8SDavid du Colombier } 753906943f9SDavid du Colombier } 754906943f9SDavid du Colombier return 0; 75583030dd5SDavid du Colombier } 756