diff --git a/apps/Makefile b/apps/Makefile index 6362698..f2ed663 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -33,6 +33,12 @@ TCP =\ hfs65 \ telnet65 +bin: wget65.bin + +wget65.bin: w5100.c linenoise.c +wget65.bin: IP65LIB = ../ip65/ip65.lib +wget65.bin: A2_DRIVERLIB = ../drivers/ip65_apple2_uther2.lib + date65.bin hfs65.bin: CL65FLAGS = --start-addr 0x0C00 apple2enh-iobuf-0800.o telnet65.com: ATARI_CFG = atrtelnet.cfg @@ -110,6 +116,8 @@ ip65.dsk: bin java -jar $(AC) -p $@ date65.system sys < $(CC65)/apple2enh/util/loader.system java -jar $(AC) -as $@ hfs65 < hfs65.bin java -jar $(AC) -p $@ hfs65.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -as $@ wget65 < wget65.bin + java -jar $(AC) -p $@ wget65.system sys < $(CC65)/apple2enh/util/loader.system java -jar $(AC) -as $@ telnet65 < telnet65.bin ip65.atr: com diff --git a/apps/linenoise.c b/apps/linenoise.c new file mode 100644 index 0000000..fd6aa49 --- /dev/null +++ b/apps/linenoise.c @@ -0,0 +1,552 @@ +/* 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 +#include + +#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 ====================== */ + +#ifdef __APPLE2__ +#pragma code-name (push, "LC") +#endif + +/* 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; +} + +/* 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; + 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. */ + gotox((unsigned char)(pos+plen)); +} + +/* 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/apps/linenoise.h b/apps/linenoise.h new file mode 100644 index 0000000..6b0736f --- /dev/null +++ b/apps/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/apps/w5100.c b/apps/w5100.c new file mode 100644 index 0000000..bc18b40 --- /dev/null +++ b/apps/w5100.c @@ -0,0 +1,286 @@ +/****************************************************************************** + +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. + +******************************************************************************/ + +// 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 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(void) +{ + w5100_mode = eth_driver_io_base; + w5100_addr_hi = eth_driver_io_base + 1; + w5100_addr_lo = eth_driver_io_base + 2; + w5100_data = eth_driver_io_base + 3; + + // 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 + uint8_t sizes = get_byte(reg[do_send]); + + 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 + + addr_basis[do_send] = addr [do_send] + size[sizes & 3]; + addr_limit[do_send] = addr_basis[do_send] + size[sizes >> 2 & 3]; + } + } +} + +bool w5100_connect(uint32_t addr, uint16_t port) +{ + // Socket 1 Mode Register: TCP + set_byte(0x0500, 0x01); + + // Socket 1 Source Port Register + set_word(0x0504, ip65_random_word()); + + // Socket 1 Command Register: OPEN + set_byte(0x0501, 0x01); + + // Socket 1 Status Register: SOCK_INIT ? + while (get_byte(0x0503) != 0x13) + { + if (input_check_for_abort_key()) + { + return false; + } + } + + // Socket 1 Destination IP Address Register + set_quad(0x050C, addr); + + // Socket 1 Destination Port Register + set_word(0x0510, port); + + // Socket 1 Command Register: CONNECT + set_byte(0x0501, 0x04); + + while (true) + { + // Socket 1 Status Register + switch (get_byte(0x0503)) + { + 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 1 Status Register: SOCK_ESTABLISHED ? + return get_byte(0x0503) == 0x17; +} + +void w5100_disconnect(void) +{ + // Socket 1 Command Register: Command Pending ? + while (get_byte(0x0501)) + { + if (input_check_for_abort_key()) + { + return; + } + } + + // Socket 1 Command Register: DISCON + set_byte(0x0501, 0x08); +} + +uint16_t w5100_data_request(bool do_send) +{ + // Socket 1 Command Register: Command Pending ? + if (get_byte(0x0501)) + { + 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] = {0x0526, // Socket 1 RX Received Size Register + 0x0520}; // Socket 1 TX Free Size Register + size = get_word(reg[do_send]); + } + } + while (size != prev_size); + + if (!size) + { + return 0; + } + + { + static uint16_t reg[2] = {0x0528, // Socket 1 RX Read Pointer Register + 0x0524}; // Socket 1 TX Write Pointer Register + + // Calculate and set physical address + uint16_t addr = get_word(reg[do_send]) & 0x0FFF | addr_basis[do_send]; + set_addr(addr); + + // 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); + } + } +} + +void w5100_data_commit(bool do_send, uint16_t size) +{ + { + static uint16_t reg[2] = {0x0528, // Socket 1 RX Read Pointer Register + 0x0524}; // Socket 1 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 1 Command Register + set_byte(0x0501, cmd[do_send]); + } + + // Do NOT wait for command completion here, rather + // let W5100 operation overlap with 6502 operation +} diff --git a/apps/w5100.h b/apps/w5100.h new file mode 100644 index 0000000..6f59692 --- /dev/null +++ b/apps/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(void); + +// 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/apps/wget65.c b/apps/wget65.c new file mode 100644 index 0000000..2ac9f3b --- /dev/null +++ b/apps/wget65.c @@ -0,0 +1,497 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../inc/ip65.h" +#include "w5100.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) + +char buffer[0x1000]; + +void ip65_error_exit(bool quit) +{ + switch (ip65_error) + { + case IP65_ERROR_DEVICE_FAILURE: + printf("- No Uthernet II found\n"); + break; + case IP65_ERROR_ABORTED_BY_USER: + printf("- User abort\n"); + break; + case IP65_ERROR_TIMEOUT_ON_RECEIVE: + printf("- Timeout\n"); + break; + case IP65_ERROR_MALFORMED_URL: + printf("- Malformed URL\n"); + break; + case IP65_ERROR_DNS_LOOKUP_FAILED: + printf("- Lookup failed\n"); + break; + default: + printf("- Error $%X\n", ip65_error); + } + if (quit) + { + exit(EXIT_FAILURE); + } +} + +void file_error_exit(void) +{ + printf("- "); + perror(NULL); + exit(EXIT_FAILURE); +} + +void confirm_exit(void) +{ + printf("\nPress any key "); + cgetc(); +} + +void reset_cwd(void) +{ + chdir(""); +} + +char *self_path(const char *filename) +{ + extern char **_argv[]; + + return strcat(strcpy(buffer, *_argv[0]), filename); +} + +bool match(const char *filter, const char *string) +{ + while (*filter) + { + if (!*string) + { + return false; + } + if (toupper(*filter++) != toupper(*string++)) + { + return false; + } + } + return true; +} + +void url_completion(const char *line, linenoiseCompletions *lc) +{ + if (match(line, "http://")) + { + linenoiseAddCompletion(lc, "http://"); + } + if (match(line, "http://www.")) + { + linenoiseAddCompletion(lc, "http://www."); + } +} + +void file_completion(const char *line, linenoiseCompletions *lc) { + char *lineptr = strrchr(line, '/'); + + // Add device names + if (lineptr == line) + { + unsigned char disk = getfirstdevice(); + while (disk != INVALID_DEVICE) + { + if (getdevicedir(disk, buffer, sizeof(buffer))) + { + if (match(line, buffer)) + { + linenoiseAddCompletion(lc, buffer); + } + } + disk = getnextdevice(disk); + } + } + + // Add directory entries + else + { + DIR *dir; + struct dirent *ent; + char *bufferptr; + + // Absolute or relative path + if (lineptr) + { + *lineptr = '\0'; + dir = opendir(line); + *lineptr = '/'; + ++lineptr; + } + + // Current directory + else + { + dir = opendir("."); + lineptr = (char*)line; + } + + if (!dir) + { + return; + } + + strcpy(buffer, line); + bufferptr = buffer + (lineptr - line); + + while (ent = readdir(dir)) + { + if (match(lineptr, ent->d_name)) + { + strcpy(bufferptr, ent->d_name); + linenoiseAddCompletion(lc, buffer); + } + } + closedir(dir); + } +} + +char *get_argument(char arg, const char *name, const char *history, + linenoiseCompletionCallback *completion) +{ + extern int _argc; + extern char **_argv[]; + char *val; + + linenoiseHistoryReset(); + linenoiseHistoryLoad(self_path(history)); + + if (_argc > arg) + { + val = *_argv[arg]; + printf("%s: %s", name, val); + } + else + { + char prompt[10]; + + linenoiseSetCompletionCallback(completion); + + snprintf(prompt, sizeof(prompt), "%s? ", name); + val = linenoise(prompt); + if (!val) + { + putchar('\n'); + exit(EXIT_FAILURE); + } + } + + linenoiseHistoryAdd(val); + linenoiseHistorySave(self_path(history)); + + return val; +} + +void exit_on_key(void) +{ + if (input_check_for_abort_key()) + { + w5100_disconnect(); + printf("- User abort\n"); + exit(EXIT_FAILURE); + } +} + +void exit_on_disconnect(void) +{ + if (!w5100_connected()) + { + printf("- Connection lost\n"); + exit(EXIT_FAILURE); + } +} + +int main(int, char *argv[]) +{ + uint8_t drv_init = DRV_INIT_DEFAULT; + uint16_t i, len; + char *arg; + char data; + char *dataptr; + int file; + + if (doesclrscrafterexit()) + { + atexit(confirm_exit); + } + + if (!*getcwd(buffer, sizeof(buffer))) + { + // Set a defined working dir before potentially changing devices + chdir(getdevicedir(getcurrentdevice(), buffer, sizeof(buffer))); + atexit(reset_cwd); + } + + // Trim program name from argv[0] to prepare usage in self_path() + arg = strrchr(argv[0], '/'); + if (arg) { + *(arg + 1) = '\0'; + } + else + { + *argv[0] = '\0'; + } + + printf("\nSetting slot "); + file = open(self_path("ethernet.slot"), O_RDONLY); + if (file != -1) + { + read(file, &drv_init, 1); + close(file); + drv_init &= ~'0'; + } + + printf("- %d\n\nInitializing ", drv_init); + if (ip65_init(drv_init)) + { + ip65_error_exit(true); + } + + // Abort on Ctrl-C to be consistent with Linenoise + abort_key = 0x83; + + printf("- Ok\n\nObtaining IP address "); + if (dhcp_init()) + { + ip65_error_exit(true); + } + printf("- Ok\n\n"); + + // Copy IP config from IP65 to W5100 + w5100_config(); + + while (true) + { + arg = get_argument(1, "URL", "wget.urls", url_completion); + + printf("\n\nProcessing URL "); + if (!url_parse(arg)) + { + break; + } + + // Do not actually exit + ip65_error_exit(false); + printf("\n"); + } + printf("- Ok\n\n"); + + arg = get_argument(2, "File", "wget.files", file_completion); + + printf("\n\nConnecting to %s:%d ", dotted_quad(url_ip), url_port); + + if (!w5100_connect(url_ip, url_port)) + { + printf("- Connect failed\n"); + exit(EXIT_FAILURE); + } + + printf("- Ok\n\nSending Request "); + { + uint16_t snd; + uint16_t pos = 0; + + len = strlen(url_selector); + while (len) + { + exit_on_key(); + + snd = w5100_send_request(); + if (!snd) + { + exit_on_disconnect(); + continue; + } + + if (len < snd) + { + snd = len; + } + + // One less to allow for faster pre-increment below + dataptr = url_selector + pos - 1; + for (i = 0; i < snd; ++i) + { + // The variable is necessary to have cc65 generate code + // suitable to access the W5100 auto-increment register. + data = *++dataptr; + *w5100_data = data; + } + + w5100_send_commit(snd); + len -= snd; + pos += snd; + } + } + + printf("- Ok\n\nReceiving Response "); + { + uint16_t rcv; + char *body; + + len = 0; + while (true) + { + exit_on_key(); + + rcv = w5100_receive_request(); + if (!rcv) + { + exit_on_disconnect(); + continue; + } + + // One less to allow for zero-termination further down below + if (rcv > sizeof(buffer) - 1 - len) + { + rcv = sizeof(buffer) - 1 - len; + } + + // One less to allow for faster pre-increment below + dataptr = buffer + len - 1; + for (i = 0; i < rcv; ++i) + { + // The variable is necessary to have cc65 generate code + // suitable to access the W5100 auto-increment register. + data = *w5100_data; + *++dataptr = data; + } + + w5100_receive_commit(rcv); + len += rcv; + + buffer[len] = '\0'; + body = strstr(buffer,"\r\n\r\n"); + if (body) + { + break; + } + + // No body found but full buffer + if (len == sizeof(buffer) - 1) + { + printf("- Invalid response\n"); + w5100_disconnect(); + exit(EXIT_FAILURE); + } + } + + // Replace "HTTP/1.1" with "HTTP/1.0" + buffer[7] = '0'; + + if (!match("HTTP/1.0 200", buffer)) + { + if (match("HTTP/1.0", buffer)) + { + char *eol = strchr(buffer,'\r'); + *eol = '\0'; + printf("- Status%s\n", buffer + 8); + } + else + { + printf("- Unknown response\n"); + } + w5100_disconnect(); + exit(EXIT_FAILURE); + } + + body += strlen("\r\n\r\n"); + len -= body - buffer; + memmove(buffer, body, len); + } + + printf("- Ok\n\nOpening file "); + file = open(arg, O_WRONLY | O_CREAT | O_TRUNC); + if (file == -1) + { + w5100_disconnect(); + file_error_exit(); + } + printf("- Ok\n\n"); + + { + uint16_t rcv; + bool cont = true; + uint32_t size = 0; + + while (cont) + { + exit_on_key(); + + rcv = w5100_receive_request(); + if (!rcv) + { + cont = w5100_connected(); + if (cont) + { + continue; + } + } + + if (rcv > sizeof(buffer) - len) + { + rcv = sizeof(buffer) - len; + } + + // One less to allow for faster pre-increment below + dataptr = buffer + len - 1; + for (i = 0; i < rcv; ++i) + { + // The variable is necessary to have cc65 generate code + // suitable to access the W5100 auto-increment register. + data = *w5100_data; + *++dataptr = data; + } + + w5100_receive_commit(rcv); + len += rcv; + + if (cont && len < sizeof(buffer)) + { + continue; + } + + cprintf("\rWriting "); + if (write(file, buffer, len) != len) + { + w5100_disconnect(); + file_error_exit(); + } + size += len; + cprintf("%lu bytes ", size); + + len = 0; + } + } + + printf("- Ok\n\nClosing file "); + if (close(file)) + { + w5100_disconnect(); + file_error_exit(); + } + + printf("- Ok\n\nDisconnecting "); + w5100_disconnect(); + + printf("- Ok\n"); + return EXIT_SUCCESS; +}