From 817636242a425e9c737fd356033b3e1decbdcb6c Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Fri, 14 Jun 2019 14:15:30 -0400 Subject: [PATCH] Add at28w app and recipe This allows us to write to an AT28 EEPROM from within collapse os. --- apps/at28w/glue.asm | 24 ++++++++++++ apps/at28w/main.asm | 76 +++++++++++++++++++++++++++++++++++++ recipes/rc2014/README.md | 1 + recipes/rc2014/eeprom/Makefile | 10 +++++ recipes/rc2014/eeprom/README.md | 83 +++++++++++++++++++++++++++++++++++++++++ recipes/rc2014/eeprom/glue.asm | 73 ++++++++++++++++++++++++++++++++++++ 6 files changed, 267 insertions(+) create mode 100644 apps/at28w/glue.asm create mode 100644 apps/at28w/main.asm create mode 100644 recipes/rc2014/eeprom/Makefile create mode 100644 recipes/rc2014/eeprom/README.md create mode 100644 recipes/rc2014/eeprom/glue.asm diff --git a/apps/at28w/glue.asm b/apps/at28w/glue.asm new file mode 100644 index 0000000..e1ee260 --- /dev/null +++ b/apps/at28w/glue.asm @@ -0,0 +1,24 @@ +; at28w - Write to AT28 EEPROM +; +; Write data from the active block device into an eeprom device geared as +; regular memory. Implements write polling to know when the next byte can be +; written and verifies that data is written properly. +; +; Optionally receives a word argument that specifies the number or bytes to +; write. If unspecified, will write until max bytes (0x2000) is reached or EOF +; is reached on the block device. + +; *** Requirements *** +; blkGetC +; parseArgs +; +; *** Includes *** + +#include "user.h" +#include "err.h" +.org USER_CODE +.equ AT28W_RAMSTART USER_RAMSTART + +jp at28wMain + +#include "at28w/main.asm" diff --git a/apps/at28w/main.asm b/apps/at28w/main.asm new file mode 100644 index 0000000..98a9568 --- /dev/null +++ b/apps/at28w/main.asm @@ -0,0 +1,76 @@ +; *** Consts *** +; Memory address where the AT28 is configured to start +.equ AT28W_MEMSTART 0x2000 + +; Value mismatch during validation +.equ AT28W_ERR_MISMATCH 0x10 + +; *** Variables *** +.equ AT28W_MAXBYTES AT28W_RAMSTART +.equ AT28W_RAMEND AT28W_MAXBYTES+2 +; *** Code *** + +at28wMain: + ld de, .argspecs + ld ix, AT28W_MAXBYTES + call parseArgs + jr z, at28wInner + ; bad args + ld a, SHELL_ERR_BAD_ARGS + ret +.argspecs: + .db 0b111, 0b101, 0 + +at28wInner: + ld hl, (AT28W_MAXBYTES) + ld b, h + ld c, l + ld hl, AT28W_MEMSTART + call at28BCZero + jr nz, .loop + ; BC is zero, default to 0x2000 (8x, the size of the AT28) + ld bc, 0x2000 +.loop: + call blkGetC + jr nz, .loopend + ld (hl), a + ld e, a ; save expected data for verification + ; initiate polling + ld a, (hl) + ld d, a +.wait: + ; as long as writing operation is running, IO/6 will toggle at each + ; read attempt. We know that write is finished when we read the same + ; value twice. + ld a, (hl) + cp d + jr z, .waitend + ld d, a + jr .wait +.waitend: + + ; same value was read twice. A contains our final value for this memory + ; address. Let's compare with what we're written. + cp e + jr nz, .mismatch + inc hl + dec bc + call at28BCZero + jr nz, .loop + +.loopend: + ; We're finished. Success! + xor a + ret + +.mismatch: + ld a, AT28W_ERR_MISMATCH + ret + +at28BCZero: + xor a + cp b + ret nz + cp c + ret + diff --git a/recipes/rc2014/README.md b/recipes/rc2014/README.md index 3bf381c..dc90fc9 100644 --- a/recipes/rc2014/README.md +++ b/recipes/rc2014/README.md @@ -23,6 +23,7 @@ disabled. This recipe is for installing a minimal Collapse OS system on the RC2014. There are other recipes related to the RC2014: +* [Writing to a AT28 from Collapse OS](eeprom/README.md) * [Accessing a MicroSD card](sdcard/README.md) * [Assembling binaries](zasm/README.md) diff --git a/recipes/rc2014/eeprom/Makefile b/recipes/rc2014/eeprom/Makefile new file mode 100644 index 0000000..33fd356 --- /dev/null +++ b/recipes/rc2014/eeprom/Makefile @@ -0,0 +1,10 @@ +TARGET = os.bin +ZASM = ../../../tools/zasm.sh +KERNEL = ../../../kernel +APPS = ../../../apps + +.PHONY: all +all: $(TARGET) +$(TARGET): glue.asm + $(ZASM) $(KERNEL) $(APPS) < $< > $@ + diff --git a/recipes/rc2014/eeprom/README.md b/recipes/rc2014/eeprom/README.md new file mode 100644 index 0000000..39b1108 --- /dev/null +++ b/recipes/rc2014/eeprom/README.md @@ -0,0 +1,83 @@ +# Writing to a AT28 from Collapse OS + +## Goal + +Write in an AT28 EEPROM from within Collapse OS so that you can have it update +itself. + +## Gathering parts + +* A RC2014 Classic that could install the base recipe +* An extra AT28C64B +* 1x 40106 inverter gates +* Proto board, RC2014 header pins, wires, IC sockets, etc. + +## Building the EEPROM holder + +The AT28 is SRAM compatible so you could use a RAM module for it. However, +there is only one RAM module with the Classic version of the RC2014 and we +need it to run Collapse OS. + +You could probably use the 64K RAM module for this purpose, but I don't have one +and I haven't tried it. For this recipe, I built my own module which is the same +as the regular ROM module but with `WR` wired and geared for address range +`0x2000-0x3fff`. + +If you're tempted by the idea of hacking your existing RC2014 ROM module by +wiring `WR` and write directly to the range `0x0000-0x1fff` while running it, +be aware that it's not that easy. I was also tempted by this idea, tried it, +but on bootup, it seems that some random `WR` triggers happen and it corrupts +the EEPROM contents. Theoretically, we could go around that my putting the AT28 +in write protection mode, but I preferred building my own module. + +I don't think you need a schematic. It's really simple. + +## Building the kernel + +For this recipe to work, we need a block device for the `at28w` program to read +from. The easiest way to go around would be to use a SD card, but maybe you +haven't built a SPI relay yet and it's quite a challenge to do so. + +Therefore, for this recipe, we'll have `at28w` read from a memory map and we'll +upload contents to write to memory through our serial link. + +`at28w` is designed to be ran as a "user application", but in this case, because +we run from a kernel without a filesystem and that `pgm` can't run without it, +we'll integrate `at28w` directly in our kernel and expose it as an extra shell +command (renaming it to `a28w` to fit the 4 chars limit). + +For all this to work, you'll need [glue code that looks like this](glue.asm). +Running `make` in this directory will produce a `os.bin` with that glue code +that you can install in the same way you did with the basic RC2014 recipe. + +If your range is different than `0x2000-0x3fff`, you'll have to modify +`AT28W_MEMSTART` before you build. + +## Writing contents to the AT28 + +The memory map is configured to start at `0xd000`. The first step is to upload +contents at that address as documented in ["Load code in RAM and run it"][load]. + +You have to know the size of the contents you've loaded because you'll pass it +as at argument to `a28w`. You can run: + + Collapse OS + > bsel 0 + > seek 00 0000 + > a28w + +It takes a while to write. About 1 second per byte (soon, I'll implement page +writing which should make it much faster). + +If the program doesn't report an error, you're all good! The program takes care +of verifying each byte, so everything should be in place. You can verify +yourself by `peek`-ing around the `0x2000-0x3fff` range. + +Note that to write a single byte to the AT28 eeprom, you don't need a special +program. You can, while you're in the `0x2000-0x3fff` range, run `poke 1` and +send an arbitrary char. It will work. The problem is with writing multiple +bytes: you have to wait until the eeprom is finished writing before writing to +a new address, something a regular `poke` doesn't do but `at28w` does. + +[load]: ../../../doc/load-run-code.md + diff --git a/recipes/rc2014/eeprom/glue.asm b/recipes/rc2014/eeprom/glue.asm new file mode 100644 index 0000000..c2560ce --- /dev/null +++ b/recipes/rc2014/eeprom/glue.asm @@ -0,0 +1,73 @@ +; classic RC2014 setup (8K ROM + 32K RAM) and a stock Serial I/O module +; The RAM module is selected on A15, so it has the range 0x8000-0xffff +.equ RAMSTART 0x8000 +.equ RAMEND 0xffff +.equ ACIA_CTL 0x80 ; Control and status. RS off. +.equ ACIA_IO 0x81 ; Transmit. RS on. + +jp init + +; interrupt hook +.fill 0x38-$ +jp aciaInt + +#include "err.h" +#include "core.asm" +#include "parse.asm" +.equ ACIA_RAMSTART RAMSTART +#include "acia.asm" + +.equ MMAP_START 0xd000 +#include "mmap.asm" + +.equ BLOCKDEV_RAMSTART ACIA_RAMEND +.equ BLOCKDEV_COUNT 1 +#include "blockdev.asm" +; List of devices +.dw mmapGetC, mmapPutC + +.equ STDIO_RAMSTART BLOCKDEV_RAMEND +#include "stdio.asm" + +.equ AT28W_RAMSTART STDIO_RAMEND +#include "at28w/main.asm" + +.equ SHELL_RAMSTART AT28W_RAMEND +.equ SHELL_EXTRA_CMD_COUNT 5 +#include "shell.asm" +; Extra cmds +.dw a28wCmd +.dw blkBselCmd, blkSeekCmd, blkLoadCmd, blkSaveCmd + +#include "blockdev_cmds.asm" + +init: + di + ; setup stack + ld hl, RAMEND + ld sp, hl + im 1 + + call aciaInit + ld hl, aciaGetC + ld de, aciaPutC + call stdioInit + call shellInit + + xor a + ld de, BLOCKDEV_SEL + call blkSel + + ei + jp shellLoop + +a28wCmd: + .db "a28w", 0b011, 0b001 + ld a, (hl) + ld (AT28W_MAXBYTES+1), a + inc hl + ld a, (hl) + ld (AT28W_MAXBYTES), a + jp at28wInner + +