Added (Apple II specific) wget65 program.

IP65 doesn't support TCP flow control. Therefore it doesn't make sense to write a program receiving a significant amount of data via TCP using IP65. From that perspective it makes sense that IP65's HTTP client doesn't allow to handle incoming data with a callback but requires a buffer being able to hold the whole HTTP body.

However, on the Apple II there's the Uthernet II card with its W5100 Ethernet controller chip. That chip has it's own TCP implementation supporting TCP flow control. Therefore the wget65 program uses the W5100 TCP capabilities for the HTTP client.

But even with the W5100 TCP implementation in place IP65 still plays a role for the wget65 program as it desires DHCP and requires (usually) DNS. Both are not supported by the W5100 capabilities.
This commit is contained in:
Oliver Schmidt 2018-08-01 19:39:06 +02:00
parent dcbe2e002b
commit 29793d4814
6 changed files with 1484 additions and 0 deletions

View File

@ -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

552
apps/linenoise.c Normal file
View File

@ -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 <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* 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 <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <conio.h>
#include <apple2_filetype.h>
#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 <tab> 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 <tab>. 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;
}

59
apps/linenoise.h Normal file
View File

@ -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 <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* 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 */

286
apps/w5100.c Normal file
View File

@ -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 <organization> 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
}

82
apps/w5100.h Normal file
View File

@ -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 <organization> 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 <stdint.h>
#include <stdbool.h>
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 <server_addr> on TCP port <server_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 <size> bytes from server. <size> 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 <size> bytes to server. <size> 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

497
apps/wget65.c Normal file
View File

@ -0,0 +1,497 @@
#include <cc65.h>
#include <ctype.h>
#include <fcntl.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <device.h>
#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;
}