diff --git a/src/cmd.c b/src/cmd.c index 9c3d92a..b21e9e1 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -206,11 +206,11 @@ void parse_command(const uint8_t *s, uint8_t n) pipe.valid = 0; r = pipe.valid; if (have_b) { - if (cmd_flag('!') || ~r & bflg) { + if (bflg && cmd_flag('!') || ~r & bflg) { memcpy(bptr, cmd_buffer, 16); - r = pipe.valid |= bflg; + pipe.valid = r |= bflg; } - else + else if (bflg) goto error; } if (cmd_flag('%')) { @@ -220,14 +220,15 @@ void parse_command(const uint8_t *s, uint8_t n) if (cmd_flag('!')) { bch4369_fini(); memcpy(flash_buffer+64, bch_parity, 16); - r = pipe.valid |= 0x10; + pipe.valid = r |= 0x10; } } if (cmd_flag('<')) { if (cmd_flag('!')) goto send_buffer; if (~r & bflg) goto error; - pipe.valid &=~ bflg; + pipe.valid = r &=~ bflg; + r |= pipe.status << 4; goto send_buffer; } break; @@ -240,17 +241,23 @@ void parse_command(const uint8_t *s, uint8_t n) r = flash_submit_command(cmd_buffer); else r = spi_busy_p(); - if (cmd_flag('@')) + if (cmd_flag('!')) spi_poll(); if (cmd_flag('<')) goto send_buffer; break; case 'P': - if (cmd_flag('!')) + if (cmd_flag('@')) pipe.dest = 0; if (have_b) pipe_config((void*)cmd_buffer, (void*)bptr); - r = pipe_poll(); + if (cmd_flag('.')) + flash_poll(0); + else if (cmd_flag(',')) + flash_poll(1); + if (cmd_flag('!')) + pipe_poll(); + r = pipe.status; break; #ifdef HAVE_FPGA case 'O': diff --git a/src/config.h b/src/config.h index c87f9bb..47dc5e9 100644 --- a/src/config.h +++ b/src/config.h @@ -38,6 +38,7 @@ enum magic_flags { MAGIC = 0xC5, VERSION = 0x00, #endif + hold_pipe = 0x01, }; extern const struct config config; @@ -158,6 +159,7 @@ void apply_config() extern struct magic { uint8_t magic; uint8_t reset_source; + uint8_t flags; } magic; #if 0 diff --git a/src/dose.py b/src/dose.py index 74a830d..e08cbe7 100755 --- a/src/dose.py +++ b/src/dose.py @@ -66,6 +66,16 @@ mmap = memmap(map_fn) class dose_cmd(uart.uart): + def parse_line(self, l): + self.responses.append(l) + v = self._verbose + if v>=4 or v and (l[:1]!=b'#' or b'?' in l.split()[0]): + try: + s = l.decode() + print(f"{self.portname}<- {s}", file=sys.stderr) + except: + print(f"{self.portname}<- {repr(l)}", file=sys.stderr) + def cmd(self, c, d=None, timeout=0.2): if not isinstance(c, bytes): c = c.encode() @@ -83,7 +93,7 @@ class dose_cmd(uart.uart): e = int(r[-1], 16) if len(r)==3: d = base85_decode(r[1]) - if self._verbose: + if self._verbose >= 3: print(f"{r[0]}[{r[2]}] {"".join([f"{b:02x}" for b in d])}", file=sys.stderr) self.last_cmd = c[0:1] return r[0], e, d @@ -91,26 +101,32 @@ class dose_cmd(uart.uart): def poke(self, a, d, s=None, ccp=0): return self.peek(a, s=s, d=d, ccp=ccp) - def peek(self, a=None, s=None, d=None, ccp=0): + def peek(self, a=None, s=None, d=None, ccp=0, o=0): name = None - if a in SFR: - name = a - a, ss = SFR[a] - if s is None: - s = ss - if a in mmap: - a, ss = mmap[a] - if ss and s is None: - s = ss if isinstance(a, str): - print(f"PEEK: unknown addr {a}", file=sys.stderr) - for k in mmap.keys(): - if a in k: - print(f"PEEK: in RAM we have {k}", file=sys.stderr) - for k in SFR.keys(): - if a in k: - print(f"PEEK: IO SFR we have {k}", file=sys.stderr) - raise ValueError(a) + aa = a.split('+',1) + if aa[1:]: + o += int(aa[1], 0) + aa = aa[0] + if aa in SFR: + name = aa + a, ss = SFR[aa] + if s is None: + s = ss + elif aa in mmap: + a, ss = mmap[aa] + if ss and s is None: + s = ss + else: + print(f"PEEK: unknown addr {a}", file=sys.stderr) + for k in mmap.keys(): + if a in k: + print(f"PEEK: in RAM we have {k}", file=sys.stderr) + for k in SFR.keys(): + if a in k: + print(f"PEEK: IO SFR we have {k}", file=sys.stderr) + raise ValueError(a) + a += o FF = {1: "B", 2: "H", 4:"I"} if a is None: cc, nn, dd = self.cmd(b"M=") @@ -223,7 +239,10 @@ class dose_cmd(uart.uart): def flash(self, op=None, resp=False, poll=False, **aa): c = "F" if poll: - c = "F@" + if poll in ".,!": + c = "F"+poll + else: + c = "F!" if resp: c += "<" if op is None: @@ -278,10 +297,8 @@ class dose_cmd(uart.uart): if min(d) == 255: print(f"FLASH: page is erased") elif bch.check_page(d): - if not bch.galois: - raise bch.BCHError("galois module not loaded for Parity Error correction") - d = bch.fix_page(d) - return d[:512] + d = self.fix_page(d) + return d def read_flash(self, page): print(f"FLASH: reading from {page=}") @@ -355,6 +372,17 @@ class dose_cmd(uart.uart): self.flash(op, size=s, what=what, byte=i) self.wait_for_spi() + def make_parity(self, page): + for i in range(32): + if i==0: + c = "B4!%@" + elif i!=31: + c = "B4!%" + else: + c = "B4!%!" + self.cmd(c, page[16*i:16*(i+1)]) + return self.cmd("B4= 2: print(f"FPGA cmd status {s!r}", file=sys.stderr) return d @@ -462,7 +489,7 @@ class dose_cmd(uart.uart): w.extend([0x8001]*(r-1)) w.append(0) d = struct.pack(f">{len(w)}H", *w) - if self._verbose: + if self._verbose >= 2: if mes: mes = f"[{mes}]" else: @@ -470,13 +497,13 @@ class dose_cmd(uart.uart): print(f"icmd{mes} → {[f"{ww:04x}" for ww in w]}") d = self.fpga_cmd(d) r = struct.unpack(f">{len(w)+1}H", d[:2*len(w)+2]) - if self._verbose: + if self._verbose >= 2: print(f" {mes} ← {[f"{rr:04x}" for rr in r]}", file=sys.stderr) return r def acmd(self, c, v=None, task=None, verb=True): _v = self._verbose - self._verbose = _v + self._verbose = verb+1 r = self.icmd(c, v, mes=task)[-1] self._verbose = _v return r @@ -500,7 +527,7 @@ class dose_cmd(uart.uart): nnn -= nn d += dd[2:] r = struct.unpack(f">{len(d)//2}H", d)[:n] - if self._verbose: + if self._verbose >= 2: print(f"FIFO[{fifo}] ← {[f"{rr:04x}" for rr in r]}", file=sys.stderr) return r @@ -522,9 +549,140 @@ class dose_cmd(uart.uart): else: self.icmd("MCONF_CLR", what) + PIPE = { + "CMD" : 1, + "ADC" : 2, + "FLASH" : 4, + "FPGA" : 8, + "CONFIG" : 0x208, + "FPGA_CMD" : 0x408, + } + + def pipe(self, source=None, dest=None, + fpga_cmd=4, psize=64, n=0, + page=0, npages=0, + poll=True, stop=False): + astatus = flags2int(self.PIPE, dest) + astatus |= flags2int(self.PIPE, source) + astatus >>= 8 + dest = flags2int(self.PIPE, dest) & 0xff + source = flags2int(self.PIPE, source) & 0xff + flash = 0x80 # FS_528 + status = 0x80 # PS_OUT + c = 'P' + if astatus & self.PIPE["FPGA_CMD"]: + if not isinstance(fpga_cmd, int): + self.cmd("B4!", fpga_cmd) + fpga_cmd = 4 + c = 'P{fpga_cmd}' + if stop: + c += '@' + if poll: + c += '!' + + if source & self.PIPE["FLASH"]: + source = self.PIPE["FLASH"] + flash |= 4 + status |= 0x30 # PS_528 | PS_BCH + dest &=~ self.PIPE["FLASH"] + if dest & self.PIPE["FPGA"] and not n: + n = 512/64 * npages + if dest & self.PIPE["FLASH"]: + flash |= 8 + status |= 0x30 # PS_528 | PS_BCH + if source == self.PIPE["FPGA"] and not n: + n = (512*npages+psize-1) // psize + + d = struct.pack("<6BH3B2HB", + source, dest, status, 0, 0, + astatus, n, psize, 0, 0, + page, npages-1, flash) + return self.cmd(c, d) + + Sync = False + + def read_pipe(self, n=0, file=None, timeout=2, parity=False): + if isinstance(file, str): + f = open(file, "ab") + else: + f = file + result = [] + i = 0 + page = [] + t = time.time() + s = 1/256 + while i < n: + cc, nn, dd = self.cmd("B") + if not parity or len(page) < 7: + bflg = 0x0f + else: + bflg = 0x1f + + tt = time.time() + if ~nn & bflg: + if tt > t+timeout: + if self._verbose: + print("read_pipe timeout", file=sys.stderr) + break + time.sleep(s) + s *= 2 + if s > 1: + s = 1 + continue + t = tt + s = 1/256 + + if len(result) >= n: + break + + if nn & 0x10: + p = self.cmd("B4<")[2] + else: + p = None + + data = [self.cmd(f"B{i}<")[2] for i in range(4)] + data = b''.join(data) + page.append(data) + if len(page)==8: + if p: + page.append(p) + page = b''.join(page) + i += 1 + if parity: + if bch.check_page(page): + print(f"parity error on page {i}", file=sys.stderr) + if parity == "fix": + page = self.fix_page(page) + if f and parity == "fix": + file.write(page[:512]) + if not f: + result.append(page) + page = [] + + if f and parity != "fix": + f.write(data) + if len(page) and parity == "save": + f.write_flash(p) + if f and self.Sync: + f.sync() + + if isinstance(file, str): + f.close() + return result + + def fix_page(self, page): + if self.load_galois(): + page = bch.fix_page(page) + + def load_galois(self): + if not bch.galois: + raise bch.BCHError("galois module not loaded for Parity Error correction") + return True def flags2int(FLAGS, flags): r = 0 + if flags is None: + return r if isinstance(flags, int): return flags if isinstance(flags, str): @@ -546,7 +704,7 @@ def int2flags(FLAGS, flags): if tty: tty = dose_cmd(tty, baud) tty._export(globals()) - tty._verbose = False + tty._verbose = True import dorn from dorn import * dorn._connect(tty) diff --git a/src/flash.c b/src/flash.c index 2739ce0..c50b172 100644 --- a/src/flash.c +++ b/src/flash.c @@ -150,7 +150,7 @@ uint8_t flash_cmd(uint16_t mode, uint16_t what, uint16_t page, uint16_t byte) spi_start_read(csize, flash_cmd_buffer, size, b); break; case FM_WAIT: - spi.mask = 0x80; + spi.mask = spi.wait = 0x80; spi_start_read(csize, flash_cmd_buffer, 2, b); break; } @@ -173,7 +173,7 @@ uint8_t flash_stream_submit(uint16_t mode, uint8_t size) { uint8_t b = fs.block; uint16_t p = fs.page; - uint8_t r = fs.status & ~FS_Ready; + uint8_t r = fs.status & ~FS_Ready | FS_Busy; mode |= (uint16_t)config.flash_page_size << 8; if (size) { if (b & 8) { @@ -193,21 +193,9 @@ uint8_t flash_stream_submit(uint16_t mode, uint8_t size) uint8_t e = flash_cmd(mode, size, p, (uint16_t)(b&7) << 6); if (e) - r |= FS_Ready; // FS_Error - r |= FS_Busy; + r |= FS_Error; fs.status = r; - return e; -} - -__attribute__ ((noinline, noclone)) -uint8_t flash_stream_done() -{ - uint8_t r = fs.status & FS_Error; - if (!r || r == FS_Error) - return 1; - if (fs.npages || !(fs.block & 8)) - return 0; - return 1; + return r; } static inline @@ -253,55 +241,50 @@ uint8_t flash_burn_page() uint8_t flash_poll(uint8_t rr) { uint8_t r = fs.status; + if (rr) + r |= FS_Ack; if (spi_busy_p()) return r; - if ((r & FS_Error) == FS_Error) + if (flash_stream_done()) return r; - if (r & FS_StBsy) { - // status bytes arrived - if (~flash_status_bytes[0] & 0x80) - // flash is still busy burning - goto rd_status; - // not busy any more, move Bsy → Rdy - if (r & FS_Busy) - r |= FS_Ready; - goto ready; - } - if (!(r & FS_Busy)) + if (r & FS_Ready) goto ready; - if (rr) { - r |= FS_Error; - fs.status = r; - return r; + + if (r & FS_StBsy && flash_status_bytes[0] & 0x80) + // flash is done burning + goto ready; + if (r & FS_Write && fs.block == 9) { + // Write or Erase + r |= FS_StBsy; + flash_status_bytes[0] = 0; + flash_cmd_na(0xd7 | FM_READ, 0xf002); + goto done; } - if (r & FS_Write) { - if (fs.block == 9) { - rd_status: - // request status bytes for pending Write or Error - r |= FS_StBsy; - fs.status = r; - flash_cmd_na(0xd7 | FM_READ, 0xf002); - return r; - } + if (r & FS_Dir == FS_Write && fs.block == 8) { + // Write + r = flash_burn_page(); + goto done; } ready: - r &= ~(FS_Busy | FS_StBsy); - if (rr) - r |= FS_Ack; - fs.status = r; - if (r & FS_Dir == FS_Write && fs.block == 8) - flash_burn_page(); - else if (!flash_stream_done()) { - if (r & (FS_Dir|FS_Ack) == (FS_Read|FS_Ack)) - flash_read_next_block(); - else if (r & (FS_Dir|FS_Ack) == (FS_Write|FS_Ack)) - flash_write_next_block(); - else if (r & FS_Dir == FS_Erase) - flash_erase_next_page(); - fs.status &=~ FS_Ack; + r &= ~(FS_Error | FS_StBsy); + if (!(r & FS_Ack)) { + r |= FS_Ready; + goto done; } - return fs.status; + r &=~ FS_Ack; + if (!fs.npages && fs.block & 8) + goto done; + + if ((r & FS_Dir) == FS_Read) + r = flash_read_next_block(); + else if ((r & FS_Dir) == FS_Write) + r = flash_write_next_block(); + else if ((r & FS_Dir) == FS_Erase) + r = flash_erase_next_page(); +done: + fs.status = r; + return r; } uint8_t flash_start_stream(uint16_t page, uint16_t npages, uint8_t flags) @@ -310,14 +293,12 @@ uint8_t flash_start_stream(uint16_t page, uint16_t npages, uint8_t flags) if ((r & FS_Error) == FS_Busy) return FS_Error; r = flags | FS_Ready; - if (config.flash_page_size != FM_528) - r &=~ FS_528; fs.page = page; - fs.block = 0; fs.npages = npages; + flash_status_bytes[0] = 0; + fs.block = 0; fs.status = r; - flash_status_bytes[0] = 0xff; - return flash_poll(0); + return r; } static inline diff --git a/src/flash.h b/src/flash.h index 46af975..525c493 100644 --- a/src/flash.h +++ b/src/flash.h @@ -28,7 +28,6 @@ uint8_t flash_cmd(uint16_t mode, uint16_t what, uint16_t page, uint16_t byte); extern uint8_t flash_buffer[FB_SIZE]; uint8_t flash_submit_command(uint8_t *cmd); uint8_t flash_start_stream(uint16_t page, uint16_t npages, uint8_t flags); -uint8_t flash_stream_done(); uint8_t flash_poll(uint8_t rr); uint16_t flash_find_free(); @@ -55,5 +54,7 @@ enum { }; static inline uint8_t flash_current_block() { return fs.block; } +static inline uint8_t flash_stream_done() +{ return !(fs.status & FS_Error) || !(~fs.status & FS_Error); } #endif diff --git a/src/fpga.c b/src/fpga.c index 9300742..fc01a79 100644 --- a/src/fpga.c +++ b/src/fpga.c @@ -113,7 +113,6 @@ uint8_t fpga_start_read() uint8_t n = 64 - pipe.fpga.val; if (!n) return n; - spi.rdata = flash_buffer + pipe.fpga.val; n >>= 1; if (~pipe.fpga.status & AS_CONT) { if (!pipe.fpga.count) @@ -130,7 +129,10 @@ uint8_t fpga_start_read() pipe.fpga.pos += n; if (pipe.fpga.pos == pipe.fpga.size) pipe.fpga.status &=~ AS_CONT; + + spi.rdata = flash_buffer + pipe.fpga.val; spi.rsize = n << 1; + spi.zsize += spi.rsize; pipe.fpga.val += spi.rsize; spi_start(); return n; diff --git a/src/pipe.c b/src/pipe.c index edd4c60..2ee6058 100644 --- a/src/pipe.c +++ b/src/pipe.c @@ -18,7 +18,7 @@ uint8_t pipe_busy() // call this with cli() before sei();sleep() if (spi_busy_p()) return 1; if (flash_poll(0) & FS_Busy) return 1; - if (adc_poll(0)) return 1; + if (adc_busy()) return 1; return 0; } @@ -77,14 +77,14 @@ uint8_t pipe_poll() // Return if next ADC reading is not yet due. // Come back here, until is is. - if (pipe.source & pipe_adc && !adc_poll(pipe.adc)) + if (pipe.source == pipe_adc && !adc_poll(pipe.adc)) goto done; // Continue the flash stream. // PS_BLK is the 64-Bytes buffer number in the page valid = 0; - if (pipe.source & pipe_flash) { + if (pipe.source == pipe_flash) { r &= ~ PS_BLK; r |= flash_current_block() & PS_BLK; flash_poll(1); @@ -92,9 +92,9 @@ uint8_t pipe_poll() #ifdef HAVE_FPGA // Continue the FPGA stream. - else if (pipe.source & pipe_fpga) { + else if (pipe.source == pipe_fpga) { if (~fpga_start_read()) - pipe.source &=~ pipe_fpga; + pipe.source = 0; } #endif goto done; @@ -109,7 +109,7 @@ uint8_t pipe_poll() bflgs = 0x1f; // Data from the ADC is in named .bss segments. We send - // 16, 32, or 64 Bytes from the .bss segement for ead ADC reading. + // 16, 32, or 64 Bytes from the .bss segement for each ADC reading. // The first named .bss segment is `magic`. Observe the link order // in the Makefile. // @@ -118,7 +118,7 @@ uint8_t pipe_poll() // `o` is the addr in the flash_buffer // `v` are the currently valid cmd_buffer flags // `f` are the fresh valid cmd_buffer flags - if (pipe.source & pipe_adc) { + if (pipe.source == pipe_adc) { uint8_t n = pipe.adc & 0x70; if (!n) goto adc_done; @@ -160,11 +160,11 @@ adc_done: // When the flash is done, flag the buffer valid, // including BCH Bytes. - if (pipe.source & pipe_flash && fs.status & FS_Ready) + if (pipe.source == pipe_flash && fs.status & FS_Ready) valid = bflgs; #ifdef HAVE_FPGA // The FPGA delivers 64 Bytes. - else if (pipe.source & pipe_fpga && fpga_pipe_ready()) + else if (pipe.source == pipe_fpga && fpga_pipe_ready()) valid = 0x0f; #endif // The buffer is not here, yet. Nothing we can do now. @@ -181,10 +181,10 @@ adc_done: // Last buffer of the page: if (!(~r & PS_BLK)) { bch4369_fini(); - valid = 0x01f; // When the Flash is not the source, + // and cmd did not provide the parity // copy the computed parity into the buffer. - if (~pipe.source & pipe_flash) + if (~valid & 0x10) _memcopyyz(bend, bch_parity, 16); // When the page as read from flash has invalid parity, // stop writing to the FPGA. @@ -192,6 +192,7 @@ adc_done: dest &=~ pipe_fpga; r |= PS_ERR; } + valid = 0x1f; } } // We still do not have all data we need @@ -203,7 +204,7 @@ adc_done: #ifdef HAVE_FPGA // Resume the FPGA stream - if (dest & pipe_fpga & ~fpga_start_write()) + if (dest & pipe_fpga && ~fpga_start_write()) dest &=~ pipe_fpga; else #endif @@ -227,7 +228,7 @@ void pipe_config(const struct pipe_config *c, const struct pipe_fpga_cmd *a) { /************************************************************ * cmd("B0!", «fpga-cmd») optionally configure an FPGA cmd - * cmd("P0:, «pipe-cfg») with `AS_CMD` in `.status` + * cmd("P0", «pipe-cfg») with `AS_CMD` in `.status` ***********************************************************/ if (c->pipe.dest) { @@ -238,6 +239,7 @@ void pipe_config(const struct pipe_config *c, const struct pipe_fpga_cmd *a) else { // new source (NOT flash) pipe.source = c->pipe.source; + pipe.adc = c->pipe.adc; } if (pipe.source & pipe_adc) adc_start_stream(pipe.adc); @@ -247,6 +249,7 @@ void pipe_config(const struct pipe_config *c, const struct pipe_fpga_cmd *a) _memcopyzy((void*)&pipe_fpga_cmd, (void*)a, sizeof(*a)); else memset(&pipe_fpga_cmd, 0, sizeof(*a)); + pipe.fpga = c->pipe.fpga; fpga_start_read(); } #endif diff --git a/src/spi.c b/src/spi.c index 98a4329..083ab8d 100644 --- a/src/spi.c +++ b/src/spi.c @@ -387,23 +387,18 @@ ISR(SPI0_INT_vect, ISR_NAKED) "rjmp 4f" "\n" "1:" "\n\t" - "lds r26, %[MASK]" "\n\t" - "cpi r26, 0" "\n\t" - "breq 1f" "\n\t" - "and r26, r25" "\n\t" - "lds r24, %[WAIT]" "\n\t" - "cp r24, r26" "\n\t" - "brne 2f" "\n\t" + "lds r26, %[WAIT]" "\n\t" + "eor r26, r25" "\n\t" + "lds r24, %[MASK]" "\n\t" + "and r26, r24" "\n\t" + "breq 2f" "\n\t" "lds r25, %[ZSZ]" "\n\t" "subi r25, -1" "\n\t" "sts %[ZSZ], r25" "\n\t" "rjmp 4f" "\n" "2:" "\n\t" - "clr r26" "\n\t" - "sts %[MASK], r26" "\n" - - "1:" "\n\t" + "sts %[MASK], r26" "\n\t" "lds r26, %[RSZ]" "\n\t" "subi r26, 1" "\n\t" "brcs 4f" "\n\t" diff --git a/src/thhor.c b/src/thhor.c index e6a2869..8d3ebc6 100644 --- a/src/thhor.c +++ b/src/thhor.c @@ -13,6 +13,7 @@ #include "config.h" #include "uart.h" #include "pipe.h" +#include "adc.h" //////////////////////////////////////////////////////////////////////////////// // @@ -40,17 +41,18 @@ int main() send_hex_byte_eol(magic.reset_source); while (1) { - // The pipe may become idle before we reach sleep. - // Make sure we have nothing to do before we sleep. - // The sleep command will execute even when an irq - // is pending. It will wake us immediately. + // The irq sources may become idle before we reach sleep. + // Make sure we have nothing pending before we sleep. + // The sleep command will execute even when an irq becomes + // pending after cli(). It will wake us immediately. cli(); - if (!command_pending() && (!pipe.dest || pipe_busy())) { + if (!command_pending() || adc_busy() || spi_busy_p()) { sei(); sleep_cpu(); } sei(); command(); - pipe_poll(); + if (~magic.flags & hold_pipe) + pipe_poll(); } } diff --git a/src/uart.py b/src/uart.py index e8bdba3..4ba9779 100755 --- a/src/uart.py +++ b/src/uart.py @@ -61,7 +61,7 @@ class uart(threading.Thread): def parse_line(self, l): self.responses.append(l) - if self._verbose: + if self._verbose >= 4: try: s = l.decode() print(f"{self.portname}<- {s}", file=sys.stderr) @@ -92,7 +92,7 @@ class uart(threading.Thread): c = c.encode() if c[-1:] != b'\n': c = c + b'\n' - if self._verbose: + if self._verbose >= 4: print(f"{self.portname}-> {c.decode().rstrip()}", file=sys.stderr) self.write(c)