; ============================================================================= ; pipe.asm - anonymous pipes: bounded in-kernel FIFOs with blocking ends. ; ; A pipe fd is $F0 + idx*2 (+1 for the write end); $FF stays "console". getb/ ; putb/close dispatch pipe fds here. read blocks (yields) while empty and there ; is still a writer, then returns EOF once all writers are gone; write blocks ; while full and there is a reader, and if the last reader is gone the writer is ; killed (SIGPIPE). refcounts (writers/readers) are set to 1 by pipe() - the one ; producer and one consumer of a shell pipeline - and dropped on close/exit. ; ============================================================================= INCLUDE "include/gbos.inc" SECTION "pipe_bss", WRAM0 wPipeW:: DS PIPE_MAX ; writers refcount (0 = end/free) wPipeR:: DS PIPE_MAX ; readers refcount wPipeHead:: DS PIPE_MAX ; read index wPipeTail:: DS PIPE_MAX ; write index wPipeCnt:: DS PIPE_MAX ; bytes buffered wPipeBuf:: DS PIPE_MAX * PIPE_BUFSZ ; ring buffers wPipeByte:: DS 1 ; scratch SECTION "pipe", ROM0 ; pipe_init - mark every pipe free (writers=readers=0). Called at boot. pipe_init:: ld hl, wPipeW ld b, PIPE_MAX * 2 ; wPipeW followed by wPipeR xor a .z ld [hl+], a dec b jr nz, .z ret ; sys_pipe - allocate a pipe -> A = read fd (write = read+1), or $FF. sys_pipe:: jp pipe_alloc ; is_pipe_fd - A = fd. CF set (carry) if it is NOT a pipe fd. Preserves A. is_pipe_fd:: cp PIPE_FD_BASE ret c ; < base -> not a pipe (CF set) cp PIPE_FD_BASE + PIPE_MAX*2 ccf ; >= top -> CF set (not pipe); else CF clear ret ; pipe_alloc - -> A = read fd, or $FF if none free. Empties + writers=readers=1. pipe_alloc:: ld c, 0 .scan ld hl, wPipeW ld b, 0 add hl, bc ld a, [hl] or a jr nz, .next ld hl, wPipeR add hl, bc ld a, [hl] or a jr z, .found .next inc c ld a, c cp PIPE_MAX jr c, .scan ld a, $FF ret .found ld b, 0 ld hl, wPipeW add hl, bc ld [hl], 1 ld hl, wPipeR add hl, bc ld [hl], 1 ld hl, wPipeHead add hl, bc ld [hl], 0 ld hl, wPipeTail add hl, bc ld [hl], 0 ld hl, wPipeCnt add hl, bc ld [hl], 0 ld a, c add a ; idx*2 add PIPE_FD_BASE ret ; pipe_close - A = fd. If a pipe fd, drop the appropriate refcount. pipe_close:: call is_pipe_fd ret c sub PIPE_FD_BASE ld c, a srl c ; C = idx and 1 ; A = 1 if write end ld hl, wPipeR jr z, .have ld hl, wPipeW .have ld b, 0 add hl, bc ld a, [hl] or a ret z dec [hl] ret ; pipe_bufptr - C = idx, A = pos -> HL = &wPipeBuf[idx*PIPE_BUFSZ + pos]. keeps BC. ; (idx*64 + pos <= 255, so the whole offset fits in one byte.) pipe_bufptr: ld e, a ; E = pos ld a, c swap a add a add a ; A = idx*64 add e ; + pos ld e, a ld d, 0 ld hl, wPipeBuf add hl, de ret ; pipe_read - A = read fd -> A = byte, CF set on EOF. Blocks while empty w/ writer. pipe_read:: sub PIPE_FD_BASE srl a ld c, a ; C = idx .retry ld hl, wPipeCnt ld b, 0 add hl, bc ld a, [hl] or a jr nz, .data ld hl, wPipeW ; empty: any writers left? add hl, bc ld a, [hl] or a jr z, .eof ; none -> EOF call SchedYield ; block: let the writer run jr .retry .data ld hl, wPipeHead add hl, bc ld a, [hl] ; head push af call pipe_bufptr ; A=head, C=idx -> HL = &buf byte ld a, [hl] ld [wPipeByte], a pop af ; head inc a cp PIPE_BUFSZ jr c, .hok xor a .hok ld hl, wPipeHead add hl, bc ld [hl], a ld hl, wPipeCnt add hl, bc dec [hl] ld a, [wPipeByte] and a ; CF = 0 ret .eof scf ret ; pipe_write - A = write fd, byte in wPipeByte. Blocks while full w/ reader; ; SIGPIPE (exit) if no reader. pipe_write:: sub PIPE_FD_BASE srl a ld c, a ; C = idx .retry ld hl, wPipeCnt ld b, 0 add hl, bc ld a, [hl] cp PIPE_BUFSZ jr c, .space ld hl, wPipeR ; full: any readers left? add hl, bc ld a, [hl] or a jr z, .epipe ; none -> SIGPIPE call SchedYield ; block: let the reader drain jr .retry .space ld hl, wPipeTail add hl, bc ld a, [hl] ; tail push af call pipe_bufptr ; A=tail, C=idx -> HL = &buf byte ld a, [wPipeByte] ld [hl], a pop af ; tail inc a cp PIPE_BUFSZ jr c, .tok xor a .tok ld hl, wPipeTail add hl, bc ld [hl], a ld hl, wPipeCnt add hl, bc inc [hl] ret .epipe ld b, 141 ; 128 + SIGPIPE(13) jp sys_exit