ref: 82b046f36f8084a22bbb5d71edd0edd9179561eb
dir: /appl/cmd/usb/usbd.b/
implement Usbd; include "sys.m"; sys: Sys; include "draw.m"; include "string.m"; str: String; include "lock.m"; lock: Lock; Semaphore: import lock; include "arg.m"; arg: Arg; include "usb.m"; usb: Usb; Device, Configuration, Endpt: import Usb; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; Detached, Attached, Enabled, Assigned, Configured: con (iota); Usbd: module { init: fn(nil: ref Draw->Context, args: list of string); }; Hub: adt { nport, pwrmode, compound, pwrms, maxcurrent, removable, pwrctl: int; ports: cyclic ref DDevice; }; DDevice: adt { port: int; pids: list of int; parent: cyclic ref DDevice; next: cyclic ref DDevice; cfd, setupfd, rawfd: ref Sys->FD; id: int; ls: int; state: int; ep: array of ref Endpt; config: array of ref Usb->Configuration; hub: Hub; mod: UsbDriver; d: ref Device; }; Line: adt { level: int; command: string; value: int; svalue: string; }; ENUMERATE_POLL_INTERVAL: con 1000; FAILED_ENUMERATE_RETRY_INTERVAL: con 10000; verbose: int; debug: int; stderr: ref Sys->FD; usbportfd: ref Sys->FD; usbctlfd: ref Sys->FD; usbctl0: ref Sys->FD; usbsetup0: ref Sys->FD; usbbase: string; configsema, setupsema, treesema: ref Semaphore; # UHCI style status which is returned by the driver. UHCIstatus_Suspend: con 1 << 12; UHCIstatus_PortReset: con 1 << 9; UHCIstatus_SlowDevice: con 1 << 8; UHCIstatus_ResumeDetect: con 1 << 6; UHCIstatus_PortEnableChange: con 1 << 3; UHCIstatus_PortEnable: con 1 << 2; UHCIstatus_ConnectStatusChange: con 1 << 1; UHCIstatus_DevicePresent: con 1 << 0; obt() { # sys->fprint(stderr, "%d waiting\n", sys->pctl(0, nil)); setupsema.obtain(); # sys->fprint(stderr, "%d got\n", sys->pctl(0, nil)); } rel() { # sys->fprint(stderr, "%d releasing\n", sys->pctl(0, nil)); setupsema.release(); } hubid(hub: ref DDevice): int { if (hub == nil) return 0; return hub.id; } hubfeature(d: ref DDevice, p: int, feature: int, on: int): int { rtyp: int; if (p == 0) rtyp = Usb->Rclass; else rtyp = Usb->Rclass | Usb->Rother; obt(); rv := usb->setclear_feature(d.setupfd, rtyp, feature, p, on); rel(); return rv; } portpower(hub: ref DDevice, port: int, on: int) { if (verbose) sys->fprint(stderr, "portpower %d/%d %d\n", hubid(hub), port, on); if (hub == nil) return; if (port) hubfeature(hub, port, Usb->PORT_POWER, on); } countrootports(): int { sys->seek(usbportfd, big 0, Sys->SEEKSTART); buf := array [256] of byte; n := sys->read(usbportfd, buf, len buf); if (n <= 0) { sys->fprint(stderr, "usbd: countrootports: error reading root port status\n"); exit; } (nv, nil) := sys->tokenize(string buf[0: n], "\n"); if (nv < 1) { sys->fprint(stderr, "usbd: countrootports: strange root port status\n"); exit; } return nv; } portstatus(hub: ref DDevice, port: int): int { rv: int; # setupsema.obtain(); obt(); if (hub == nil) { sys->seek(usbportfd, big 0, Sys->SEEKSTART); buf := array [256] of byte; n := sys->read(usbportfd, buf, len buf); if (n < 1) { sys->fprint(stderr, "usbd: portstatus: read error\n"); rel(); return 0; } (nil, l) := sys->tokenize(string buf[0: n], "\n"); for(; l != nil; l = tl l){ (nv, f) := sys->tokenize(hd l, " "); if(nv < 2){ sys->fprint(stderr, "usbd: portstatus: odd status line\n"); rel(); return 0; } if(int hd f == port){ (rv, nil) = usb->strtol(hd tl f, 16); # the status change bits are not used so mask them off rv &= 16rffff; break; } } if (l == nil) { sys->fprint(stderr, "usbd: portstatus: no status for port %d\n", port); rel(); return 0; } } else rv = usb->get_status(hub.setupfd, port); # setupsema.release(); rel(); if (rv < 0) return 0; return rv; } portenable(hub: ref DDevice, port: int, enable: int) { if (verbose) sys->fprint(stderr, "portenable %d/%d %d\n", hubid(hub), port, enable); if (hub == nil) { if (enable) sys->fprint(usbctlfd, "enable %d", port); else sys->fprint(usbctlfd, "disable %d", port); return; } if (port) hubfeature(hub, port, Usb->PORT_ENABLE, enable); } portreset(hub: ref DDevice, port: int) { if (verbose) sys->fprint(stderr, "portreset %d/%d\n", hubid(hub), port); if (hub == nil) { if(0)sys->fprint(usbctlfd, "reset %d", port); for (i := 0; i < 4; ++i) { sys->sleep(20); # min 10 milli second reset recovery. s := portstatus(hub, port); if ((s & UHCIstatus_PortReset) == 0) # only leave when reset is finished. break; } return; } if (port) hubfeature(hub, port, Usb->PORT_RESET, 1); return; } devspeed(d: ref DDevice) { sys->fprint(d.cfd, "speed %d", !d.ls); if (debug) { s: string; if (d.ls) s = "low"; else s = "high"; sys->fprint(stderr, "%d: set speed %s\n", d.id, s); } } devmaxpkt0(d: ref DDevice, size: int) { sys->fprint(d.cfd, "maxpkt 0 %d", size); if (debug) sys->fprint(stderr, "%d: set maxpkt0 %d\n", d.id, size); } closedev(d: ref DDevice) { d.cfd = usbctl0; d.rawfd = nil; d.setupfd = usbsetup0; } openusb(f: string, mode: int): ref Sys->FD { fd := sys->open(usbbase + f, mode); if (fd == nil) { sys->fprint(stderr, "usbd: can't open %s: %r\n", usbbase + f); raise "fail:open"; } return fd; } opendevf(id: int, f: string, mode: int): ref Sys->FD { fd := sys->open(usbbase + string id + "/" + f, mode); if (fd == nil) { sys->fprint(stderr, "usbd: can't open %s: %r\n", usbbase + string id + "/" + f); exit; } return fd; } kill(pid: int): int { if (debug) sys->print("killing %d\n", pid); fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); if (fd == nil) { sys->print("kill: open failed\n"); return -1; } if (sys->write(fd, array of byte "kill", 4) != 4) { sys->print("kill: write failed\n"); return -1; } return 0; } rdetach(d: ref DDevice) { if (d.mod != nil) { d.mod->shutdown(); d.mod = nil; } while (d.pids != nil) { if (verbose) sys->fprint(stderr, "kill %d\n", hd d.pids); kill(hd d.pids); d.pids = tl d.pids; } if (d.parent != nil) { last, hp: ref DDevice; last = nil; hp = d.parent.hub.ports; while (hp != nil && hp != d) hp = hp.next; if (last != nil) last.next = d.next; else d.parent.hub.ports = d.next; } if (d.hub.ports != nil) { for (c := d.hub.ports; c != nil; c = c.next) { c.parent = nil; rdetach(c); } } d.state = Detached; if (sys->fprint(d.cfd, "detach") < 0) sys->fprint(stderr, "detach failed\n"); d.cfd = nil; d.rawfd = nil; d.setupfd = nil; } detach(d: ref DDevice) { configsema.obtain(); treesema.obtain(); obt(); # setupsema.obtain(); if (verbose) sys->fprint(stderr, "detach %d\n", d.id); rdetach(d); if (verbose) sys->fprint(stderr, "detach %d done\n", d.id); # setupsema.release(); rel(); treesema.release(); configsema.release(); } readnum(fd: ref Sys->FD): int { buf := array [16] of byte; n := sys->read(fd, buf, len buf); if (n <= 0) return -1; (rv , nil) := usb->strtol(string buf[0: n], 0); return rv; } setaddress(d: ref DDevice): int { if (d.state == Assigned) return d.id; closedev(d); d.id = 0; d.cfd = openusb("new", Sys->ORDWR); id := readnum(d.cfd); if (id <= 0) { if (debug) sys->fprint(stderr, "usbd: usb/new ID: %r\n"); d.cfd = nil; return -1; } # setupsema.obtain(); obt(); if (usb->set_address(d.setupfd, id) < 0) { # setupsema.release(); rel(); return -1; } # setupsema.release(); rel(); d.id = id; d.state = Assigned; return id; } #optstring(d: ref DDevice, langids: list of int, desc: string, index: int) #{ # if (index) { # buf := array [256] of byte; # while (langids != nil) { # nr := usb->get_descriptor(d.setupfd, Usb->Rstandard, Usb->STRING, index, hd langids, buf); # if (nr > 2) { # sys->fprint(stderr, "%s: ", desc); # usbdump->desc(d, -1, buf[0: nr]); # } # langids = tl langids; # } # } #} langid(d: ref DDevice): (list of int) { l: list of int; buf := array [256] of byte; nr := usb->get_standard_descriptor(d.setupfd, Usb->STRING, 0, buf); if (nr < 4) return nil; if (nr & 1) nr--; l = nil; for (i := nr - 2; i >= 2; i -= 2) l = usb->get2(buf[i:]) :: l; return l; } describedevice(d: ref DDevice): int { obt(); devmaxpkt0(d, 64); # guess 64 byte max packet to avoid overrun on read for (x := 0; x < 3; x++) { # retry 3 times d.d = usb->get_parsed_device_descriptor(d.setupfd); if (d.d != nil) break; sys->sleep(200); # tolerate out of spec. devices } if (d.d == nil) { rel(); return -1; } if (d.d.maxpkt0 != 64) { devmaxpkt0(d, d.d.maxpkt0); d.d = usb->get_parsed_device_descriptor(d.setupfd); if (d.d == nil) { rel(); return -1; } } rel(); if (verbose) { sys->fprint(stderr, "usb %x.%x", d.d.usbmajor, d.d.usbminor); sys->fprint(stderr, " class %d subclass %d proto %d [%s] max0 %d", d.d.class, d.d.subclass, d.d.proto, usb->sclass(d.d.class, d.d.subclass, d.d.proto), d.d.maxpkt0); sys->fprint(stderr, " vendor 0x%.4x product 0x%.4x rel %x.%x", d.d.vid, d.d.did, d.d.relmajor, d.d.relminor); sys->fprint(stderr, " nconf %d", d.d.nconf); sys->fprint(stderr, "\n"); obt(); l := langid(d); if (l != nil) { l2 := l; sys->fprint(stderr, "langids ["); while (l2 != nil) { sys->fprint(stderr, " %d", hd l2); l2 = tl l2; } sys->fprint(stderr, "]\n"); } # optstring(d, l, "manufacturer", int buf[14]); # optstring(d, l, "product", int buf[15]); # optstring(d, l, "serial number", int buf[16]); rel(); } return 0; } describehub(d: ref DDevice): int { b := array [256] of byte; # setupsema.obtain(); obt(); nr := usb->get_class_descriptor(d.setupfd, 0, 0, b); if (nr < Usb->DHUBLEN) { # setupsema.release(); rel(); sys->fprint(stderr, "usbd: error reading hub descriptor: got %d of %d\n", nr, Usb->DHUBLEN); return -1; } # setupsema.release(); rel(); if (verbose) sys->fprint(stderr, "nport %d charac 0x%.4ux pwr %dms current %dmA remov 0x%.2ux pwrctl 0x%.2ux", int b[2], usb->get2(b[3:]), int b[5] * 2, int b[6] * 2, int b[7], int b[8]); d.hub.nport = int b[2]; d.hub.pwrms = int b[5] * 2; d.hub.maxcurrent = int b[6] * 2; char := usb->get2(b[3:]); d.hub.pwrmode = char & 3; d.hub.compound = (char & 4) != 0; d.hub.removable = int b[7]; d.hub.pwrctl = int b[8]; return 0; } loadconfig(d: ref DDevice, n: int): int { obt(); d.config[n] = usb->get_parsed_configuration_descriptor(d.setupfd, n); if (d.config[n] == nil) { rel(); sys->fprint(stderr, "usbd: error reading configuration descriptor\n"); return -1; } rel(); if (verbose) usb->dump_configuration(stderr, d.config[n]); return 0; } #setdevclass(d: ref DDevice, n: int) #{ # dd := d.config[n]; # if (dd != nil) # sys->fprint(d.cfd, "class %d %d %d %d %d", d.d.nconf, n, dd.class, dd.subclass, dd.proto); #} setconfig(d: ref DDevice, n: int): int { obt(); rv := usb->set_configuration(d.setupfd, n); rel(); if (rv < 0) return -1; d.state = Configured; return 0; } configure(hub: ref DDevice, port: int): ref DDevice { configsema.obtain(); portreset(hub, port); sys->sleep(300); # long sleep necessary for strange hardware.... # sys->sleep(20); s := portstatus(hub, port); s = portstatus(hub, port); if (debug) sys->fprint(stderr, "port %d status 0x%ux\n", port, s); if ((s & UHCIstatus_DevicePresent) == 0) { configsema.release(); return nil; } if ((s & UHCIstatus_PortEnable) == 0) { if (debug) sys->fprint(stderr, "hack: re-enabling port %d\n", port); portenable(hub, port, 1); s = portstatus(hub, port); if (debug) sys->fprint(stderr, "port %d status now 0x%.ux\n", port, s); } d := ref DDevice; d.port = port; d.cfd = usbctl0; d.setupfd = usbsetup0; d.id = 0; if (hub == nil) d.ls = (s & UHCIstatus_SlowDevice) != 0; else d.ls = (s & (1 << 9)) != 0; d.state = Enabled; devspeed(d); if (describedevice(d) < 0) { portenable(hub, port, 0); configsema.release(); return nil; } if (setaddress(d) < 0) { portenable(hub, port, 0); configsema.release(); return nil; } d.setupfd = opendevf(d.id, "setup", Sys->ORDWR); d.cfd = opendevf(d.id, "ctl", Sys->ORDWR); devspeed(d); devmaxpkt0(d, d.d.maxpkt0); d.config = array [d.d.nconf] of ref Configuration; for (i := 0; i < d.d.nconf; i++) { loadconfig(d, i); # setdevclass(d, i); } if (hub != nil) { treesema.obtain(); d.parent = hub; d.next = hub.hub.ports; hub.hub.ports = d; treesema.release(); } configsema.release(); return d; } enumerate(hub: ref DDevice, port: int) { if (hub != nil) hub.pids = sys->pctl(0, nil) :: hub.pids; reenumerate := 0; for (;;) { if (verbose) sys->fprint(stderr, "enumerate: starting\n"); if ((portstatus(hub, port) & UHCIstatus_DevicePresent) == 0) { if (verbose) sys->fprint(stderr, "%d: port %d empty\n", hubid(hub), port); do { sys->sleep(ENUMERATE_POLL_INTERVAL); } while ((portstatus(hub, port) & UHCIstatus_DevicePresent) == 0); } if (verbose) sys->fprint(stderr, "%d: port %d attached\n", hubid(hub), port); # Δt3 (TATTDB) guarantee 100ms after attach detected sys->sleep(200); d := configure(hub, port); if (d == nil) { if (verbose) sys->fprint(stderr, "%d: can't configure port %d\n", hubid(hub), port); } else if (d.d.class == Usb->CL_HUB) { i: int; if (setconfig(d, 1) < 0) { if (verbose) sys->fprint(stderr, "%d: can't set configuration for hub on port %d\n", hubid(hub), port); detach(d); d = nil; } else if (describehub(d) < 0) { if (verbose) sys->fprint(stderr, "%d: failed to describe hub on port %d\n", hubid(hub), port); detach(d); d = nil; } else { for (i = 1; i <= d.hub.nport; i++) portpower(d, i, 1); sys->sleep(d.hub.pwrms); for (i = 1; i <= d.hub.nport; i++) spawn enumerate(d, i); } } else if (d.d.nconf >= 1 && (path := searchdriverdatabase(d.d, d.config[0])) != nil) { d.mod = load UsbDriver path; if (d.mod == nil) sys->fprint(stderr, "usbd: failed to load %s\n", path); else { rv := d.mod->init(usb, d.setupfd, d.cfd, d.d, d.config, usbbase + string d.id + "/"); if (rv == -11) { sys->fprint(stderr, "usbd: %s: reenumerate\n", path); d.mod = nil; reenumerate = 1; } else if (rv < 0) { sys->fprint(stderr, "usbd: %s:init failed\n", path); d.mod = nil; } else if (verbose) sys->fprint(stderr, "%s running\n", path); } } else if (setconfig(d, 1) < 0) { if (verbose) sys->fprint(stderr, "%d: can't set configuration for port %d\n", hubid(hub), port); detach(d); d = nil; } if (!reenumerate) { if (d != nil) { # wait for it to be unplugged while (portstatus(hub, port) & UHCIstatus_DevicePresent) sys->sleep(ENUMERATE_POLL_INTERVAL); } else { # wait a bit and prod it again if (portstatus(hub, port) & UHCIstatus_DevicePresent) sys->sleep(FAILED_ENUMERATE_RETRY_INTERVAL); } } if (d != nil) { detach(d); d = nil; } reenumerate = 0; } } lines: array of Line; searchdriverdatabase(d: ref Device, conf: ref Configuration): string { backtracking := 0; level := 0; for (i := 0; i < len lines; i++) { if (verbose > 1) sys->fprint(stderr, "search line %d: lvl %d cmd %s val %d (back %d lvl %d)\n", i, lines[i].level, lines[i].command, lines[i].value, backtracking, level); if (backtracking) { if (lines[i].level > level) continue; backtracking = 0; } if (lines[i].level != level) { level = 0; backtracking = 1; } case lines[i].command { "class" => if (d.class != 0) { if (lines[i].value != d.class) backtracking = 1; } else if (lines[i].value != (hd conf.iface[0].altiface).class) backtracking = 1; "subclass" => if (d.class != 0) { if (lines[i].value != d.subclass) backtracking = 1; } else if (lines[i].value != (hd conf.iface[0].altiface).subclass) backtracking = 1; "proto" => if (d.class != 0) { if (lines[i].value != d.proto) backtracking = 1; } else if (lines[i].value != (hd conf.iface[0].altiface).proto) backtracking = 1; "vendor" => if (lines[i].value != d.vid) backtracking =1; "product" => if (lines[i].value != d.did) backtracking =1; "load" => return lines[i].svalue; * => continue; } if (!backtracking) level++; } return nil; } loaddriverdatabase() { newlines: array of Line; if (bufio == nil) bufio = load Bufio Bufio->PATH; iob := bufio->open(Usb->DATABASEPATH, Sys->OREAD); if (iob == nil) { sys->fprint(stderr, "usbd: couldn't open %s: %r\n", Usb->DATABASEPATH); return; } lines = array[100] of Line; lc := 0; while ((line := iob.gets('\n')) != nil) { if (line[0] == '#') continue; level := 0; while (line[0] == '\t') { level++; line = line[1:]; } (n, l) := sys->tokenize(line[0: len line - 1], "\t "); if (n != 2) continue; if (lc >= len lines) { newlines = array [len lines * 2] of Line; newlines[0:] = lines[0: len lines]; lines = newlines; } lines[lc].level = level; lines[lc].command = hd l; case hd l { "class" or "subclass" or "proto" or "vendor" or "product" => (lines[lc].value, nil) = usb->strtol(hd tl l, 0); "load" => lines[lc].svalue = hd tl l; * => continue; } lc++; } if (verbose) sys->fprint(stderr, "usbd: loaded %d lines\n", lc); newlines = array [lc] of Line; newlines[0:] = lines[0 : lc]; lines = newlines; } init(nil: ref Draw->Context, args: list of string) { usbbase = "/dev/usbh/"; sys = load Sys Sys->PATH; str = load String String->PATH; lock = load Lock Lock->PATH; lock->init(); usb = load Usb Usb->PATH; usb->init(); arg = load Arg Arg->PATH; stderr = sys->fildes(2); verbose = 0; debug = 0; arg->init(args); arg->setusage("usbd [-dv] [-i interface]"); while ((c := arg->opt()) != 0) case c { 'v' => verbose = 1; 'd' => debug = 1; 'i' => usbbase = arg->earg() + "/"; * => arg->usage(); } args = arg->argv(); usbportfd = openusb("port", Sys->OREAD); usbctlfd = sys->open(usbbase + "ctl", Sys->OWRITE); if(usbctlfd == nil) usbctlfd = openusb("port", Sys->OWRITE); usbctl0 = opendevf(0, "ctl", Sys->ORDWR); usbsetup0 = opendevf(0, "setup", Sys->ORDWR); setupsema = Semaphore.new(); configsema = Semaphore.new(); treesema = Semaphore.new(); loaddriverdatabase(); ports := countrootports(); if (verbose) sys->print("%d root ports found\n", ports); for (p := 2; p <= ports; p++) spawn enumerate(nil, p); if (p >= 1) enumerate(nil, 1); }