diff --git a/linenoise.c b/linenoise.c new file mode 100644 index 0000000..7d6a5ff --- /dev/null +++ b/linenoise.c @@ -0,0 +1,555 @@ +/* linenoise.c -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/antirez/linenoise + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2016, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ------------------------------------------------------------------------ + */ + +#include +#include +#include +#include +#include +#include +#ifdef __APPLE2__ +#include +#endif + +#include "linenoise.h" + +#define LINENOISE_HISTORY_MAX_LEN 20 +#define LINENOISE_MAX_LINE 256 + +static char buf[LINENOISE_MAX_LINE]; + +static linenoiseCompletionCallback *completionCallback = NULL; + +static int history_len = 0; +static char **history = NULL; + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldpos; /* Previous refresh cursor position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal. */ + int history_index; /* The history index we are currently editing. */ +}; + +enum KEY_ACTION{ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16 /* Ctrl-p */ +}; + +static void refreshLine(struct linenoiseState *l); + +/* ======================= Low level terminal handling ====================== */ + +/* Try to get the number of columns in the current terminal, or assume 80 + * if it fails. */ +static int getColumns() { + unsigned char cols, rows; + + screensize(&cols,&rows); + return cols; +} + +#ifdef __APPLE2__ +#pragma code-name (push, "LC") +#endif + +/* Beep, used for completion when there is nothing to complete or when all + * the choices were already shown. */ +static void linenoiseBeep(void) { + putchar('\a'); +} + +/* ============================== Completion ================================ */ + +/* Free a list of completion option populated by linenoiseAddCompletion(). */ +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) + free(lc->cvec[i]); + if (lc->cvec != NULL) + free(lc->cvec); +} + +/* This is an helper function for linenoiseEdit() and is called when the + * user types the key in order to complete the string currently in the + * input. + * + * The state of the editing is encapsulated into the pointed linenoiseState + * structure as described in the structure definition. */ +static int completeLine(register struct linenoiseState *ls) { + linenoiseCompletions lc = { 0, NULL }; + int nwritten; + char c = 0; + + completionCallback(ls->buf,&lc); + if (lc.len == 0) { + linenoiseBeep(); + } else { + size_t stop = 0, i = 0; + + while(!stop) { + /* Show completion or original buffer */ + if (i < lc.len) { + struct linenoiseState saved; + saved = *ls; + + ls->len = ls->pos = strlen(lc.cvec[i]); + ls->buf = lc.cvec[i]; + refreshLine(ls); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLine(ls); + } + + c = cgetc(); + switch(c) { + case '\t': /* tab */ + i = (i+1) % (lc.len+1); + if (i == lc.len) linenoiseBeep(); + break; + case CH_ESC: /* escape */ + /* Re-show original buffer */ + if (i < lc.len) refreshLine(ls); + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); + ls->len = ls->pos = nwritten; + } + stop = 1; + break; + } + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +/* Register a callback function to be called for tab-completion. */ +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { + completionCallback = fn; +} + +/* This function is used by the callback function registered by the user + * in order to add completion options given the input string when the + * user typed . See the example.c source code for a very easy to + * understand example. */ +void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { + size_t len = strlen(str); + char *copy, **cvec; + + copy = malloc(len+1); + if (copy == NULL) return; + memcpy(copy,str,len+1); + cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + if (cvec == NULL) { + free(copy); + return; + } + lc->cvec = cvec; + lc->cvec[lc->len++] = copy; +} + +/* =========================== Line editing ================================= */ + +/* Low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshLine(struct linenoiseState *l) { + char tmp; + unsigned char y = wherey(); + size_t plen = strlen(l->prompt); + char *buf = l->buf; + size_t len = l->len; + size_t pos = l->pos; + + while((plen+pos) >= l->cols) { + buf++; + len--; + pos--; + } + while (plen+len > l->cols) { + len--; + } + + /* Cursor to left edge */ + gotox(0); + /* Write the prompt */ + cputs(l->prompt); + /* Write the current buffer content */ + tmp = buf[len]; + buf[len] = '\0'; + cputs(buf); + buf[len] = tmp; + /* Erase to right */ + if (wherex()) { + cclear(l->cols-wherex()); + } + /* Move cursor to original position. */ + gotoxy((unsigned char)(pos+plen),y); +} + +/* Insert the character 'c' at cursor current position. */ +void linenoiseEditInsert(register struct linenoiseState *l, char c) { + if (l->len < l->buflen) { + if (l->len == l->pos) { + l->buf[l->pos] = c; + l->pos++; + l->len++; + l->buf[l->len] = '\0'; + if ((l->plen+l->len < l->cols)) { + /* Avoid a full update of the line in the + * trivial case. */ + cputc(c); + } else { + refreshLine(l); + } + } else { + memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); + l->buf[l->pos] = c; + l->len++; + l->pos++; + l->buf[l->len] = '\0'; + refreshLine(l); + } + } +} + +/* Move cursor on the left. */ +void linenoiseEditMoveLeft(struct linenoiseState *l) { + if (l->pos > 0) { + l->pos--; + refreshLine(l); + } +} + +/* Move cursor on the right. */ +void linenoiseEditMoveRight(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos++; + refreshLine(l); + } +} + +/* Move cursor to the start of the line. */ +void linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} + +/* Move cursor to the end of the line. */ +void linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } +} + +/* Substitute the currently edited line with the next or previous history + * entry as specified by 'dir'. */ +#define LINENOISE_HISTORY_NEXT 0 +#define LINENOISE_HISTORY_PREV 1 +void linenoiseEditHistoryNext(register struct linenoiseState *l, int dir) { + if (history_len > 1) { + /* Update the current history entry before to + * overwrite it with the next one. */ + free(history[history_len - 1 - l->history_index]); + history[history_len - 1 - l->history_index] = strdup(l->buf); + /* Show the new entry */ + l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; + if (l->history_index < 0) { + l->history_index = 0; + return; + } else if (l->history_index >= history_len) { + l->history_index = history_len-1; + return; + } + strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); + l->buf[l->buflen-1] = '\0'; + l->len = l->pos = strlen(l->buf); + refreshLine(l); + } +} + +/* Delete the character at the right of the cursor without altering the cursor + * position. Basically this is what happens with the "Delete" keyboard key. */ +void linenoiseEditDelete(register struct linenoiseState *l) { + if (l->len > 0 && l->pos < l->len) { + memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Backspace implementation. */ +void linenoiseEditBackspace(register struct linenoiseState *l) { + if (l->pos > 0 && l->len > 0) { + memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); + l->pos--; + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* This function is the core of the line editing capability of linenoise. + * + * The resulting string is put into 'buf' when the user type enter. + * + * The function returns the length of the current buffer. */ +static int linenoiseEdit(const char *prompt) +{ + struct linenoiseState l; + + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l.buf = buf; + l.buflen = LINENOISE_MAX_LINE; + l.prompt = prompt; + l.plen = strlen(prompt); + l.oldpos = l.pos = 0; + l.len = 0; + l.cols = getColumns(); + l.history_index = 0; + + /* Buffer starts empty. */ + l.buf[0] = '\0'; + l.buflen--; /* Make sure there is always space for the nulterm */ + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + linenoiseHistoryAdd(""); + + cputs(prompt); + while(1) { + char c; + + c = cgetc(); + + /* Only autocomplete when the callback is set. It will return the + * character that should be handled next. */ + if (c == '\t' && completionCallback != NULL) { + c = completeLine(&l); + /* Read next character when 0 */ + if (c == 0) continue; + } + + switch(c) { + case CH_ENTER: + history_len--; + free(history[history_len]); + return (int)l.len; + case CTRL_C: + errno = EAGAIN; + return -1; + case CH_DEL: /* backspace */ + linenoiseEditBackspace(&l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor. */ + if (l.len > 0) { + linenoiseEditDelete(&l); + } + break; + case CTRL_B: /* ctrl-b */ + case CH_CURS_LEFT: + linenoiseEditMoveLeft(&l); + break; + case CTRL_F: /* ctrl-f */ + case CH_CURS_RIGHT: + linenoiseEditMoveRight(&l); + break; + case CTRL_P: /* ctrl-p */ + case CH_CURS_UP: + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + case CH_CURS_DOWN: + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); + break; + default: + if (isprint(c)) linenoiseEditInsert(&l,c); + break; + } + } + return l.len; +} + +#ifdef __APPLE2__ +#pragma code-name (pop) +#endif + +/* The high level function that is the main API of the linenoise library. */ +char *linenoise(const char *prompt) { + unsigned char oldcursor; + int count; + + oldcursor = cursor(1); + count = linenoiseEdit(prompt); + cursor(oldcursor); + + if (count == -1) return NULL; + return buf; +} + +/* ================================ History ================================= */ + +/* Reseet the history. */ +void linenoiseHistoryReset(void) { + if (history) { + int j; + + for (j = 0; j < history_len; j++) + free(history[j]); + free(history); + + history_len = 0; + history = NULL; + } +} + +/* This is the API call to add a new entry in the linenoise history. + * It uses a fixed array of char pointers that are shifted (memmoved) + * when the history max length is reached in order to remove the older + * entry and make room for the new one, so it is not exactly suitable for huge + * histories, but will work well for a few hundred of entries. + * + * Using a circular buffer is smarter, but a bit more complex to handle. */ +int linenoiseHistoryAdd(const char *line) { + char *linecopy; + + /* Initialization on first call. */ + if (history == NULL) { + history = malloc(sizeof(char*)*LINENOISE_HISTORY_MAX_LEN); + if (history == NULL) return 0; + memset(history,0,(sizeof(char*)*LINENOISE_HISTORY_MAX_LEN)); + } + + /* Don't add duplicated lines. */ + if (history_len && !strcmp(history[history_len-1], line)) return 0; + + /* Add an heap allocated copy of the line in the history. + * If we reached the max length, remove the older line. */ + linecopy = strdup(line); + if (!linecopy) return 0; + if (history_len == LINENOISE_HISTORY_MAX_LEN) { + free(history[0]); + memmove(history,history+1,sizeof(char*)*(LINENOISE_HISTORY_MAX_LEN-1)); + history_len--; + } + history[history_len] = linecopy; + history_len++; + return 1; +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int linenoiseHistorySave(const char *filename) { + FILE *fp; + int j; + +#ifdef __APPLE2__ + unsigned char oldfiletype = _filetype; + _filetype = PRODOS_T_TXT; +#endif + + fp = fopen(filename,"w"); + +#ifdef __APPLE2__ + _filetype = oldfiletype; +#endif + + if (fp == NULL) return -1; + for (j = 0; j < history_len; j++) + fprintf(fp,"%s\n",history[j]); + fclose(fp); + return 0; +} + +/* Load the history from the specified file. If the file does not exist + * zero is returned and no operation is performed. + * + * If the file exists and the operation succeeded 0 is returned, otherwise + * on error -1 is returned. */ +int linenoiseHistoryLoad(const char *filename) { + FILE *fp = fopen(filename,"r"); + + if (fp == NULL) return -1; + while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { + char *p; + + p = strchr(buf,'\r'); + if (!p) p = strchr(buf,'\n'); + if (p) *p = '\0'; + linenoiseHistoryAdd(buf); + } + fclose(fp); + return 0; +} diff --git a/linenoise.h b/linenoise.h new file mode 100644 index 0000000..6b0736f --- /dev/null +++ b/linenoise.h @@ -0,0 +1,59 @@ +/* linenoise.h -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LINENOISE_H +#define __LINENOISE_H + +typedef struct linenoiseCompletions { + size_t len; + char **cvec; +} linenoiseCompletions; + +typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseAddCompletion(linenoiseCompletions *, const char *); + +char *linenoise(const char *prompt); + +void linenoiseHistoryReset(void); +int linenoiseHistoryAdd(const char *line); +int linenoiseHistorySetMaxLen(int len); +int linenoiseHistorySave(const char *filename); +int linenoiseHistoryLoad(const char *filename); + +#endif /* __LINENOISE_H */ diff --git a/stream-pwm.c b/stream-pwm.c new file mode 100644 index 0000000..eb51f38 --- /dev/null +++ b/stream-pwm.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "w5100.h" +#include "w5100_http.h" +#include "linenoise.h" + +// Both pragmas are obligatory to have cc65 generate code +// suitable to access the W5100 auto-increment registers. +#pragma optimize (on) +#pragma static-locals (on) + +void streamafterexit(uint8_t eth_init); + +void error_exit(void) +{ + printf("- %s\n", ip65_strerror(ip65_error)); + exit(EXIT_FAILURE); +} + +void confirm_exit(void) +{ + printf("\nPress any key "); + cgetc(); +} + +bool match(const char *filter, const char *string) +{ + while (*filter) + { + if (!*string) + { + return false; + } + if (toupper(*filter++) != toupper(*string++)) + { + return false; + } + } + return true; +} + +void completion(const char *line, linenoiseCompletions *lc) +{ + if (match(line, "http://")) + { + linenoiseAddCompletion(lc, "http://"); + } + if (match(line, "http://www.")) + { + linenoiseAddCompletion(lc, "http://www."); + } +} + +void main(void) +{ + uint8_t eth_init = ETH_INIT_DEFAULT; + char *url; + + videomode(VIDEOMODE_80COL); + + { + int file; + + printf("\nSetting slot "); + file = open("ethernet.slot", O_RDONLY); + if (file != -1) + { + read(file, ð_init, 1); + close(file); + eth_init &= ~'0'; + } + } + +#if 1 + + printf("- %d\n\nInitializing %s ", eth_init, eth_name); + if (ip65_init(eth_init)) + { + error_exit(); + } + + // Abort on Ctrl-C to be consistent with Linenoise + abort_key = 0x83; + + printf("- Ok\n\nObtaining IP address "); + if (dhcp_init()) + { + error_exit(); + } + printf("- Ok\n\n"); + + linenoiseHistoryLoad("stream.urls"); + while (true) + { + linenoiseSetCompletionCallback(completion); + + url = linenoise("URL? "); + if (!url || !*url) + { + putchar('\n'); + return; + } + + linenoiseHistoryAdd(url); + + printf("\n\nProcessing URL "); + if (!url_parse(url)) + { + break; + } + + printf("- %s\n\n", ip65_strerror(ip65_error)); + } + + linenoiseHistorySave("stream.urls"); + linenoiseHistoryReset(); + printf("- Ok\n\n"); + + // Copy IP config from IP65 to W5100 + w5100_config(eth_init); + + { + char buffer[0x1000]; + + if (!w5100_http_open(url_ip, url_port, url_selector, buffer, sizeof(buffer))) + { + return; + } + } + +#else +url = "http://www.myhost.com/myfile.stream"; +#endif + + clrscr(); + gotoxy(0, 20); + cprintf("%.160s", url); + gotoxy(0, 23); + cprintf("Initializing..."); + gotoxy(60, 23); + cprintf("Oliver Schmidt, 2020"); + + { + int y; + + // Switch to split screen with low res graphics + *(char*)0xC056 = 0; + *(char*)0xC053 = 0; + *(char*)0xC050 = 0; + + // Clear graphics part of screen + for (y = 0; y < 20; ++y) + { + gotoy(y); + memset(*(char**)0x28, 0x00, 40); + } + + // Show upper divider line + gotoy(0); + memset(*(char**)0x28, 0x0F, 40); + + // Show big arrow consisting of dark blue, blue, light blue and white + for (y = 0; y < 15; ++y) + { + int x; + int d = 8 - abs(7 - y); + + gotoy(2 + y); + for (x = 0; x < 4; ++x) + { + uint8_t color[] = {0x22, 0x66, 0x77, 0xFF}; + + memset(*(char**)0x28 + d + (x * 8), color[x], 8); + } + } + + // Show lower divider line + gotoy(18); + memset(*(char**)0x28, 0xF0, 40); + } + + // Streaming overwrites almost all of the C program so do first a clean + // shutdown of the C program and initialize the streaming afterwards + streamafterexit(eth_init); +} diff --git a/stream.s b/stream.s new file mode 100644 index 0000000..e18a904 --- /dev/null +++ b/stream.s @@ -0,0 +1,708 @@ +.export _streamafterexit +.import done, abort_key + +.include "apple2.inc" +.macpack apple2 +.pc02 + +src := $00 +dst := $02 +rd_count := $04 +wr_count := $05 +scroll_row := $06 +scroll_col := $07 +scroll_opc := $08 + +program := $100B ; adressed via jmp_table ! +buffer := $9000 +zp_save := $9100 + +px = $8F ; force page crossing +silence = $FF +skew = 3 +end_mark = $FF +rd_mark = $FE +wr_mark = $FD +max_row = 15 +max_column = 39 + +scroll_ldx = $AE +scroll_ldy = $AC +scroll_stx = $8E +scroll_sty = $8C + +_streamafterexit: + asl + asl + asl + asl + tax + clc + adc #px + sta slot + txa + ora #%10001111 + sta slot_mask + + lda abort_key + sta quit_key + + lda #stream + sta done+1 + stx done+2 + rts + + +.segment "LOWCODE" + + +slot: + .res 1 + +slot_mask: + .res 1 + +quit_key: + .res 1 + +key_state: + .res 1 + +jmp_table: + .byte $0B, $10, $10 ; second byte used both as high and low ! + +lowres_lo: + .byte $00, $80, $00, $80, $00, $80, $28, $A8 + .byte $28, $A8, $28, $A8, $28, $A8, $50 + +lowres_hi: + .byte $05, $05, $06, $06, $07, $07, $04, $04 + .byte $05, $05, $06, $06, $07, $07, $04 + +streaming: + scrcode "Streaming... [Space] - Pause [Esc] - Stop" + .byte $00 + +pausing: + scrcode "Pausing... [Space] - Stream" + .byte $00 + +waiting: + scrcode "Waiting... " + .byte $00 + + +update_status: + sta update_status+9 + stx update_status+10 + ldx #$00 +chr:lda a:$0000,x ; patched + bne :+ + rts +: tay + txa + lsr ; even cols go into aux mem + tax + tya + bcs :+ + bit $C055 ; aux mem +: sta $07D0,x + bcs :+ + bit $C054 ; main mem +: txa + rol + tax + inx + bra chr + + +stream: + ; Save zero page + ldx #$00 +: lda $00,x + sta zp_save,x + inx + bne :- + + ; Crate unrolled loop program + jsr unroll + + ; Create socket 0 physical read memory lookup table in zero page + ldx #$00 +: txa + and #>$1FFF + ora #>$6000 + sta $00,x + inx + bne :- + + ; Init part of ring buffer that is played before first data is available + ldx #skew-1 + lda #silence +: sta buffer,x + dex + bpl :- + + ; Show status 'Streaming' + lda #streaming + jsr update_status + + ; Run unrolled loop program + jsr program + + ; Restore zero page + ldx #$00 +: lda zp_save,x + sta $00,x + inx + bne :- + + ; Switch to text screen + bit $C051 + + ; Clear screen + jsr $FC58 ; HOME + + ; Check ProDOS system bit map + lda $BF6F ; protection for pages $B8 - $BF + cmp #%00000001 ; exactly system global page is protected + beq :+ + jmp DOSWARM + + ; Quit to ProDOS dispatcher +: jsr $BF00 ; MLI call entry point + .byte $65 ; quit + .word par + + ; MLI parameter list for quit +par:.byte $04 ; param_count + .byte $00 ; quit_type + .word $0000 ; reserved + .byte $00 ; reserved + .word $0000 ; reserved + + +disconnect: + ldx slot + lda #$04 ; socket 0 + sta $C085-px,x + lda #$01 ; command register + sta $C086-px,x + lda #$08 ; DISCON + sta $C087-px,x + rts + + +check_connect: + ; Show status 'Waiting' + lda #waiting + jsr update_status + + ; Check if still connected at all + ldx slot +chk:lda #$04 ; socket 0 + sta $C085-px,x + lda #$03 ; status register + sta $C086-px,x + lda $C087-px,x + cmp #$17 ; SOCK_ESTABLISHED + beq :+ + pla ; completely ... + pla ; quit ... + rts ; stream + + ; Check if at least one page available +: lda #$04 ; socket 0 + sta $C085-px,x + lda #$26 ; received size register + sta $C086-px,x + lda $C087-px,x ; high byte + beq :+ + + ; Show status 'Streaming' + lda #streaming + jsr update_status + ldx #$04 ; socket 0 + rts ; return to stream + + ; Check keyboard +: lda $C000 + bpl chk + bit $C010 + cmp quit_key + beq :+ + cmp #$9B ; Esc + beq :+ + bra chk + +: pla ; completely ... + pla ; quit ... + jmp disconnect ; stream + + +check_keyboard: + stz key_state + +key:bit $C010 ; keyboard strobe + cpx quit_key + beq :+ + cpx #$9B ; Esc + beq :+ + cpx #$A0 ; Space + beq :++ + + ldx key_state + bne get + rts ; return to stream + +: pla ; completely ... + pla ; quit ... + jmp disconnect ; stream + +: lda key_state + beq :+ + + ; Show status 'Streaming' + lda #streaming + jmp update_status ; return to stream + + ; Show status 'Pausing' +: lda #pausing + jsr update_status + + ; Wait for keypress +get:ldx $C000 ; keyboard + bpl :- + inc key_state + bra key + + +unroll: + lda #program + sta dst + stx dst+1 + + stz rd_count + lda #skew + sta wr_count + + lda #recv_prolog + jsr copy + + ldx #$0100 / 7 +: lda #recv_loop + jsr copy + dex + bne :- + + lda #recv_epilog + jsr copy + + stz scroll_row + lda #max_column + sta scroll_col + lda #scroll_ldx + sta scroll_opc + ldx #$07 +scr:cpx #$07 + bne :+ + lda #speaker_load + bra :++ +: jsr scroll_patch + lda #speaker +: jsr copy + dex + bpl :+ + ldx #$07 +: lda scroll_row + cmp #max_row + bne scr + +fil:cpx #$07 + bne :+ + lda #speaker_load + bra :++ +: lda #speaker_nop4 +: jsr copy + dex + bpl :+ + ldx #$07 +: lda rd_count + bne fil + cpx #$01 + bne fil + + lda #speaker_loop + jsr copy + rts + + +copy: + sta src + sty src+1 + ldy #$00 +nxt:lda (src),y + cmp #end_mark + bne :++ + tya + clc + adc dst + sta dst + bcc :+ + inc dst+1 +: rts +: cmp #rd_mark + bne :+ + lda rd_count + inc rd_count + bra put +: cmp #wr_mark + bne :+ + lda wr_count + inc wr_count + bra put +: cmp #$F5 ; slot mask + bcc put + and slot_mask +put:sta (dst),y + iny + bne nxt + inc src+1 + inc dst+1 + bra nxt + + +scroll_patch: + lda scroll_opc + sta speaker ; opcode + ldy scroll_row + lda lowres_lo,y + clc + adc scroll_col + sta speaker+1 ; operand low + lda lowres_hi,y + sta speaker+2 ; operand high + + lda scroll_opc + cmp #scroll_ldx + bne :+ + dec scroll_col + lda #scroll_ldy + sta scroll_opc + rts + +: cmp #scroll_ldy + bne :+ + inc scroll_col + lda #scroll_sty + sta scroll_opc + rts + +: cmp #scroll_sty + bne :++ + dec scroll_col + beq :+ + dec scroll_col + lda #scroll_ldy + sta scroll_opc + rts + +: lda #scroll_stx + sta scroll_opc + rts + +: lda #scroll_ldx + sta scroll_opc + lda #max_column + sta scroll_col + inc scroll_row + rts + + +; 2 bytes, 2 cycles +.macro nop2 + bit #$00 +.endmacro + + +; 2 bytes, 3 cycles +.macro nop3 + bit $00 +.endmacro + + +; 3 bytes, 4 cycles +.macro nop4 + bit a:$0000 +.endmacro + + +; 13 bytes, 9 cycles +.macro spkr + lsr a ; 2 + bcs :+ ; 2 3 + nop ; 2 + bra :++ ; 3 + .res 4 +: bit $C030 ; 4 +:.endmacro + + +; 9 bytes, 9 cycles +.macro spkr_short + lsr a ; 2 + bcs :+ ; 2 3 + nop ; 2 + bra :++ ; 3 +: bit $C030 ; 4 +:.endmacro + + +; 14 bytes, 9 cycles +.macro spkr_long + lsr a ; 2 + bcs :+ ; 2 3 + nop ; 2 + bra :++ ; 3 + .res 5 +: bit $C030 ; 4 +:.endmacro + + +; 13 bytes, 9 cycles +.macro spkr_jsr addr + lsr a ; 2 + bcs :+ ; 2 3 + nop ; 2 + bra :++ ; 3 + .res 1 + jsr addr +: bit $C030 ; 4 +:.endmacro + + +; 16 bytes, 13 cycles +.macro spkr_load + lda buffer | rd_mark ; 4 + lsr a ; 2 + bcs :+ ; 2 3 + nop ; 2 + bra :++ ; 3 + .res 4 +: bit $C030 ; 4 +:.endmacro + + +; 22 bytes, 20 cycles (= 2 * 13 - 6 used by speaker_loop) +.macro spkr_init + nop4 ; 4 $100B (bit 7 is 0) + bra :+ ; 3 + nop3 ; 3 $1010 (bit 7 is 1) + bit $C030 ; 4 +: lda buffer | rd_mark ; 4 + lsr a ; 2 + bcs :+ ; 2 3 + nop ; 2 + bra :++ ; 3 +: bit $C030 ; 4 +:.endmacro + + +recv_prolog: + spkr_init ; bit 0 + ldx #$04 ; socket 0 + nop + spkr_short ; bit 1 + stx $C0F5 ; socket 0 + spkr ; bit 2 + ldy #$26 ; received size register + nop + spkr ; bit 3 + sty $C0F6 ; received size register + spkr ; bit 4 + ldy $C0F7 ; high byte + spkr ; bit 5 + dey ; at least one page available + bmi *+9 ; branch to jsr + spkr_jsr check_connect ; bit 6 + stx $C0F5 ; socket 0 + spkr ; bit 7 + spkr_load ; bit 0 + ldx #$28 ; read pointer register + nop + spkr ; bit 1 + stx $C0F6 ; read pointer register + spkr ; bit 2 + ldx $C0F7 ; high byte + spkr ; bit 3 + ldy $00,x ; high byte -> physical + spkr_long ; bit 4 + ldx $C0F7 ; low byte + spkr ; bit 5 + sty $C0F5 ; read addr high + spkr ; bit 6 + stx $C0F6 ; read addr low + spkr ; bit 7 + .byte end_mark + + +recv_loop: + spkr_load ; bit 0 + ldx $C0F7 ; data + spkr ; bit 1 + stx buffer | wr_mark + spkr ; bit 2 + ldx $C0F7 ; data + spkr ; bit 3 + stx buffer | wr_mark + spkr ; bit 4 + ldx $C0F7 ; data + spkr ; bit 5 + stx buffer | wr_mark + spkr ; bit 6 + ldx $C0F7 ; data + spkr ; bit 7 + spkr_load ; bit 0 + stx buffer | wr_mark + spkr ; bit 1 + ldx $C0F7 ; data + spkr ; bit 2 + stx buffer | wr_mark + spkr ; bit 3 + ldx $C0F7 ; data + spkr ; bit 4 + stx buffer | wr_mark + spkr ; bit 5 + ldx $C0F7 ; data + spkr ; bit 6 + stx buffer | wr_mark + spkr ; bit 7 + .byte end_mark + + +recv_epilog: + ; $0100 % 7 = 4 -> 4 data bytes left + spkr_load ; bit 0 + ldx $C0F7 ; data + spkr ; bit 1 + stx buffer | wr_mark + spkr ; bit 2 + ldx $C0F7 ; data + spkr ; bit 3 + stx buffer | wr_mark + spkr ; bit 4 + ldx $C0F7 ; data + spkr ; bit 5 + stx buffer | wr_mark + spkr ; bit 6 + ldx $C0F7 ; data + spkr ; bit 7 + spkr_load ; bit 0 + stx buffer | wr_mark + + ; Commit recv + spkr ; bit 1 + ldx #$04 ; socket 0 + nop + spkr ; bit 2 + stx $C0F5 ; socket 0 + spkr ; bit 3 + ldy #$28 ; received size register + nop + spkr ; bit 4 + sty $C0F6 ; received size register + spkr ; bit 5 + ldy $C0F7 ; high byte + spkr ; bit 6 + iny ; commit one page + nop2 + spkr ; bit 7 + spkr_load ; bit 0 + stx $C0F5 ; socket 0 + spkr ; bit 1 + ldx #$28 ; received size register + nop + spkr ; bit 2 + stx $C0F6 ; received size register + spkr ; bit 3 + sty $C0F7 ; high byte + spkr ; bit 4 + ldx #$04 ; socket 0 + nop + spkr ; bit 5 + stx $C0F5 ; socket 0 + spkr ; bit 6 + ldx #$01 ; command register + nop + spkr ; bit 7 + spkr_load ; bit 0 + stx $C0F6 ; command register + spkr ; bit 1 + ldx #$40 ; RECV + nop + spkr ; bit 2 + stx $C0F7 ; RECV + spkr ; bit 3 + + ; Check keyboard + ldx $C000 ; keyboard + spkr ; bit 4 + inx ; prepare for N flag + bit #$00 ; 2 byte nop + spkr ; bit 5 + dex ; set N flag + bmi *+9 ; branch to jsr + spkr_jsr check_keyboard ; bit 6 + + ; Filler + nop4 + spkr ; bit 7 + .byte end_mark + + +; 16 bytes, 13 cycles +speaker: + .res 3 ; patched + spkr + .byte end_mark + + +; 16 bytes, 13 cycles +speaker_load: + spkr_load + .byte end_mark + + +; 16 bytes, 13 cycles +speaker_nop4: + nop4 + spkr + .byte end_mark + + +; 14 bytes, 13 cycles (+ 6 cycles) +speaker_loop: + lsr a ; 2 bit 6 + tax ; 2 bit 7 + nop ; 2 + bcs :+ ; 2 3 + nop ; 2 + bra :++ ; 3 +: bit $C030 ; 4 +: jmp (jmp_table,x) ; 6 + .byte end_mark diff --git a/w5100.c b/w5100.c new file mode 100644 index 0000000..f3349f2 --- /dev/null +++ b/w5100.c @@ -0,0 +1,338 @@ +/****************************************************************************** + +Copyright (c) 2015, Oliver Schmidt +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL OLIVER SCHMIDT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +// The W5100 has the undocumented feature to wrap around the Address Register +// on an Auto-Increment at the end of physical address space to its beginning. +// +// However, the only way to make use of that feature is to have only a single +// socket that uses all of the W5100 physical address space. But having only +// a single socket by defining SINGLE_SOCKET comes with downsides too: +// +// One mustn't call into IP65 network functions anymore after w5100_config(). +// Additionally the program doesn't support 'W5100 Shared Access' anymore +// (https://github.com/a2retrosystems/uthernet2/wiki/W5100-Shared-Access). + +#ifdef SINGLE_SOCKET +#define SOCK_REG(offset) (0x0400 | (offset)) +#else // SINGLE_SOCKET +#define SOCK_REG(offset) (0x0500 | (offset)) +#endif // SINGLE_SOCKET + +// Both pragmas are obligatory to have cc65 generate code +// suitable to access the W5100 auto-increment registers. +#pragma optimize (on) +#pragma static-locals (on) + +#include "../inc/ip65.h" +#include "w5100.h" + +#define MIN(a,b) (((a)<(b))?(a):(b)) + +static volatile uint8_t* w5100_mode; +static volatile uint8_t* w5100_addr_hi; +static volatile uint8_t* w5100_addr_lo; + volatile uint8_t* w5100_data; + +static uint16_t addr_basis[2]; +static uint16_t addr_limit[2]; +static uint16_t addr_mask [2]; + +static void set_addr(uint16_t addr) +{ + // The variables are necessary to have cc65 generate code + // suitable to access the W5100 auto-increment registers. + uint8_t addr_hi = addr >> 8; + uint8_t addr_lo = addr; + *w5100_addr_hi = addr_hi; + *w5100_addr_lo = addr_lo; +} + +static uint8_t get_byte(uint16_t addr) +{ + set_addr(addr); + + return *w5100_data; +} + +static void set_byte(uint16_t addr, uint8_t data) +{ + set_addr(addr); + + *w5100_data = data; +} + +static uint16_t get_word(uint16_t addr) +{ + set_addr(addr); + + { + // The variables are necessary to have cc65 generate code + // suitable to access the W5100 auto-increment registers. + uint8_t data_hi = *w5100_data; + uint8_t data_lo = *w5100_data; + return data_hi << 8 | data_lo; + } +} + +static void set_word(uint16_t addr, uint16_t data) +{ + set_addr(addr); + + { + // The variables are necessary to have cc65 generate code + // suitable to access the W5100 auto-increment registers. + uint8_t data_hi = data >> 8; + uint8_t data_lo = data; + *w5100_data = data_hi; + *w5100_data = data_lo; + } +} + +static void set_quad(uint16_t addr, uint32_t data) +{ + set_addr(addr); + + { + // The variables are necessary to have cc65 generate code + // suitable to access the W5100 auto-increment registers. + uint8_t data_1 = data; + uint8_t data_2 = data >> 8; + uint8_t data_3 = data >> 16; + uint8_t data_4 = data >> 24; + *w5100_data = data_1; + *w5100_data = data_2; + *w5100_data = data_3; + *w5100_data = data_4; + } +} + +void w5100_config(uint8_t eth_init) +{ + w5100_mode = (uint8_t*)(eth_init << 4 | 0xC084); + w5100_addr_hi = w5100_mode + 1; + w5100_addr_lo = w5100_mode + 2; + w5100_data = w5100_mode + 3; + +#ifdef SINGLE_SOCKET + + // IP65 is inhibited so disable the W5100 Ping Block Mode. + *w5100_mode &= ~0x10; + +#endif // SINGLE_SOCKET + + // Source IP Address Register + set_quad(0x000F, cfg_ip); + + // Subnet Mask Register + set_quad(0x0005, cfg_netmask); + + // Gateway IP Address Register + set_quad(0x0001, cfg_gateway); + + { + bool do_send; + for (do_send = false; do_send <= true; ++do_send) + { + static uint16_t reg[2] = {0x001A, // RX Memory Size Register + 0x001B}; // TX Memory Size Register + + static uint16_t addr[2] = {0x6000, // RX Memory + 0x4000}; // TX Memory + + static uint16_t size[4] = {0x0400, // 1KB Memory + 0x0800, // 2KB Memory + 0x1000, // 4KB Memory + 0x2000}; // 8KB Memory + +#ifdef SINGLE_SOCKET + + set_byte(reg[do_send], 0x03); + + // Set Socket 0 Memory Size to 8KB + addr_basis[do_send] = addr [do_send]; + addr_limit[do_send] = addr_basis[do_send] + size[0x03]; + addr_mask [do_send] = size[0x03] - 1; + +#else // SINGLE_SOCKET + + uint8_t sizes = get_byte(reg[do_send]); + + // Get Socket 1 Memory Size + addr_basis[do_send] = addr [do_send] + size[sizes & 0x03]; + addr_limit[do_send] = addr_basis[do_send] + size[sizes >> 2 & 0x03]; + addr_mask [do_send] = size[sizes >> 2 & 0x03] - 1; + +#endif // SINGLE_SOCKET + } + } +} + +bool w5100_connect(uint32_t addr, uint16_t port) +{ + // Socket x Mode Register: TCP + set_byte(SOCK_REG(0x00), 0x01); + + // Socket x Source Port Register + set_word(SOCK_REG(0x04), ip65_random_word()); + + // Socket x Command Register: OPEN + set_byte(SOCK_REG(0x01), 0x01); + + // Socket x Status Register: SOCK_INIT ? + while (get_byte(SOCK_REG(0x03)) != 0x13) + { + if (input_check_for_abort_key()) + { + return false; + } + } + + // Socket x Destination IP Address Register + set_quad(SOCK_REG(0x0C), addr); + + // Socket x Destination Port Register + set_word(SOCK_REG(0x10), port); + + // Socket x Command Register: CONNECT + set_byte(SOCK_REG(0x01), 0x04); + + while (true) + { + // Socket x Status Register + switch (get_byte(SOCK_REG(0x03))) + { + case 0x00: return false; // Socket Status: SOCK_CLOSED + case 0x17: return true; // Socket Status: SOCK_ESTABLISHED + } + + if (input_check_for_abort_key()) + { + return false; + } + } +} + +bool w5100_connected(void) +{ + // Socket x Status Register: SOCK_ESTABLISHED ? + return get_byte(SOCK_REG(0x03)) == 0x17; +} + +void w5100_disconnect(void) +{ + // Socket x Command Register: Command Pending ? + while (get_byte(SOCK_REG(0x01))) + { + if (input_check_for_abort_key()) + { + return; + } + } + + // Socket x Command Register: DISCON + set_byte(SOCK_REG(0x01), 0x08); +} + +uint16_t w5100_data_request(bool do_send) +{ + // Socket x Command Register: Command Pending ? + if (get_byte(SOCK_REG(0x01))) + { + return 0; + } + + { + uint16_t size = 0; + uint16_t prev_size; + + // Reread of nonzero RX Received Size Register / TX Free Size Register + // until its value settles ... + // - is present in the WIZnet driver - getSn_RX_RSR() / getSn_TX_FSR() + // - was additionally tested on 6502 machines to be actually necessary + do + { + prev_size = size; + { + static uint16_t reg[2] = {SOCK_REG(0x26), // Socket x RX Received Size Register + SOCK_REG(0x20)}; // Socket x TX Free Size Register + size = get_word(reg[do_send]); + } + } + while (size != prev_size); + + if (!size) + { + return 0; + } + + { + static uint16_t reg[2] = {SOCK_REG(0x28), // Socket x RX Read Pointer Register + SOCK_REG(0x24)}; // Socket x TX Write Pointer Register + + // Calculate and set physical address + uint16_t addr = get_word(reg[do_send]) & addr_mask [do_send] + | addr_basis[do_send]; + set_addr(addr); + +#ifdef SINGLE_SOCKET + + // The W5100 has the undocumented feature to wrap around the Address Register + // on an Auto-Increment at the end of physical address space to its beginning. + return size; + +#else // SINGLE_SOCKET + + // Access to *w5100_data is limited both by ... + // - size of received / free space + // - end of physical address space + return MIN(size, addr_limit[do_send] - addr); + +#endif // SINGLE_SOCKET + } + } +} + +void w5100_data_commit(bool do_send, uint16_t size) +{ + { + static uint16_t reg[2] = {SOCK_REG(0x28), // Socket x RX Read Pointer Register + SOCK_REG(0x24)}; // Socket x TX Write Pointer Register + set_word(reg[do_send], get_word(reg[do_send]) + size); + } + + { + static uint8_t cmd[2] = {0x40, // Socket Command: RECV + 0x20}; // Socket Command: SEND + // Socket x Command Register + set_byte(SOCK_REG(0x01), cmd[do_send]); + } + + // Do NOT wait for command completion here, rather + // let W5100 operation overlap with 6502 operation +} diff --git a/w5100.h b/w5100.h new file mode 100644 index 0000000..71466d8 --- /dev/null +++ b/w5100.h @@ -0,0 +1,82 @@ +/****************************************************************************** + +Copyright (c) 2014, Oliver Schmidt +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL OLIVER SCHMIDT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#ifndef _W5100_H_ +#define _W5100_H_ + +#ifndef __APPLE2ENH__ +#error W5100 auto-increment register access requires 65C02. +#endif + +#include +#include + +uint16_t w5100_data_request(bool do_send); +void w5100_data_commit(bool do_send, uint16_t size); + +// After w5100_receive_request() every read operation returns the next byte +// from the server. +// After w5100_send_request() every write operation prepares the next byte +// to be sent to the server. +extern volatile uint8_t* w5100_data; + +// Configure W5100 Ethernet controller with additional information from IP65 +// after the IP65 TCP/IP stack has been configured. +void w5100_config(uint8_t eth_init); + +// Connect to server with IP address on TCP port . +// Return true if the connection is established, return false otherwise. +bool w5100_connect(uint32_t addr, uint16_t port); + +// Check if still connected to server. +// Return true if the connection is established, return false otherwise. +bool w5100_connected(void); + +// Disconnect from server. +void w5100_disconnect(void); + +// Request to receive data from the server. +// Return maximum number of bytes to be received by reading from *w5100_data. +#define w5100_receive_request() w5100_data_request(false) + +// Commit receiving of bytes from server. may be smaller than +// the return value of w5100_receive_request(). Not commiting at all just +// makes the next request receive the same data again. +#define w5100_receive_commit(size) w5100_data_commit(false, (size)) + +// Request to send data to the server. +// Return maximum number of bytes to be send by writing to *w5100_data. +#define w5100_send_request() w5100_data_request(true) + +// Commit sending of bytes to server. is usually smaller than +// the return value of w5100_send_request(). Not commiting at all just turns +// the w5100_send_request() - and the writes to *w5100_data - into NOPs. +#define w5100_send_commit(size) w5100_data_commit(true, (size)) + +#endif diff --git a/w5100_http.c b/w5100_http.c new file mode 100644 index 0000000..b262c31 --- /dev/null +++ b/w5100_http.c @@ -0,0 +1,185 @@ +/****************************************************************************** + +Copyright (c) 2020, Oliver Schmidt +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL OLIVER SCHMIDT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#include +#include + +#include "../inc/ip65.h" +#include "w5100.h" +#include "w5100_http.h" + +// Both pragmas are obligatory to have cc65 generate code +// suitable to access the W5100 auto-increment registers. +#pragma optimize (on) +#pragma static-locals (on) + +bool w5100_http_open(uint32_t addr, uint16_t port, const char* selector, + char* buffer, size_t length) +{ + printf("Connecting to %s:%d ", dotted_quad(addr), port); + + if (!w5100_connect(addr, port)) + { + printf("- Connect failed\n"); + return false; + } + + printf("- Ok\n\nSending request "); + { + uint16_t snd; + uint16_t pos = 0; + uint16_t len = strlen(selector); + + while (len) + { + if (input_check_for_abort_key()) + { + printf("- User abort\n"); + w5100_disconnect(); + return false; + } + + snd = w5100_send_request(); + if (!snd) + { + if (!w5100_connected()) + { + printf("- Connection lost\n"); + return false; + } + continue; + } + + if (len < snd) + { + snd = len; + } + + { + // One less to allow for faster pre-increment below + const char *dataptr = selector + pos - 1; + uint16_t i; + for (i = 0; i < snd; ++i) + { + // The variable is necessary to have cc65 generate code + // suitable to access the W5100 auto-increment register. + char data = *++dataptr; + *w5100_data = data; + } + } + + w5100_send_commit(snd); + len -= snd; + pos += snd; + } + } + + printf("- Ok\n\nReceiving response "); + { + uint16_t rcv; + bool body = false; + uint16_t len = 0; + + while (!body) + { + if (input_check_for_abort_key()) + { + printf("- User abort\n"); + w5100_disconnect(); + return false; + } + + rcv = w5100_receive_request(); + if (!rcv) + { + if (!w5100_connected()) + { + printf("- Connection lost\n"); + return false; + } + continue; + } + + if (rcv > length - len) + { + rcv = length - len; + } + + { + // One less to allow for faster pre-increment below + char *dataptr = buffer + len - 1; + uint16_t i; + for (i = 0; i < rcv; ++i) + { + // The variable is necessary to have cc65 generate code + // suitable to access the W5100 auto-increment register. + char data = *w5100_data; + *++dataptr = data; + + if (!memcmp(dataptr - 3, "\r\n\r\n", 4)) + { + rcv = i + 1; + body = true; + } + } + } + + w5100_receive_commit(rcv); + len += rcv; + + // No body found in full buffer + if (len == sizeof(buffer)) + { + printf("- Invalid response\n"); + w5100_disconnect(); + return false; + } + } + + // Replace "HTTP/1.1" with "HTTP/1.0" + buffer[7] = '0'; + + if (memcmp(buffer, "HTTP/1.0 200", 12)) + { + if (!memcmp(buffer, "HTTP/1.0", 8)) + { + char *eol = strchr(buffer,'\r'); + *eol = '\0'; + printf("- Status%s\n", buffer + 8); + } + else + { + printf("- Unknown response\n"); + } + w5100_disconnect(); + return false; + } + } + return true; +} diff --git a/w5100_http.h b/w5100_http.h new file mode 100644 index 0000000..85aafd9 --- /dev/null +++ b/w5100_http.h @@ -0,0 +1,48 @@ +/****************************************************************************** + +Copyright (c) 2020, Oliver Schmidt +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL OLIVER SCHMIDT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +#ifndef _W5100_HTTP_H_ +#define _W5100_HTTP_H_ + +#ifndef __APPLE2ENH__ +#error W5100 auto-increment register access requires 65C02. +#endif + +#include +#include + +// Connect to server with IP address on TCP port , +// then HTTP GET and consume HTTP response header. Provide feedback +// on progress to the user via STDOUT. After returning from w5100_http_open() +// the connection is ready to consume the HTTP body. +// Return true if the connection is established, return false otherwise. +bool w5100_http_open(uint32_t addr, uint16_t port, const char* selector, + char* buffer, size_t length); + +#endif