summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeo C2016-06-09 02:20:50 +0200
committerLeo C2016-06-09 02:20:50 +0200
commita2907f2e2b2725679d6b3b8d3231f3d94c325014 (patch)
tree9893b25216a37f6d955cc1078674cf3de67ef3fe
parentbeafa6d6a908081ccb65e36953b2e58529499934 (diff)
downloadz180-stamp-a2907f2e2b2725679d6b3b8d3231f3d94c325014.zip
New command: cfboot - boot from cf card.
-rw-r--r--avr/Tupfile6
-rw-r--r--avr/cmd_boot.c236
-rw-r--r--avr/command_tbl.c24
-rw-r--r--z180/Makefile2
-rw-r--r--z180/Tupfile4
-rw-r--r--z180/cfboot.180351
-rw-r--r--z180/config.inc4
7 files changed, 602 insertions, 25 deletions
diff --git a/avr/Tupfile b/avr/Tupfile
index 9f95376..0eb910a 100644
--- a/avr/Tupfile
+++ b/avr/Tupfile
@@ -22,7 +22,7 @@ SRC += ../time/system_time.c ../time/set_system_time.c
ASRC += ../time/system_tick.S
-SRC_Z = ../z180/hdrom.c
+SRC_Z = ../z180/hdrom.c ../z180/cfboot.c
#TARGETS = $(PROG).elf
@@ -110,8 +110,8 @@ LDFLAGS += -Wl,--cref
!SIZE = |> ^ SIZE^ $(SIZE) %f |>
: foreach $(ASRC) |> !as |> {objs}
-: foreach $(SRC) | ../z180/hdrom.h |> !cc |> {objs}
-: $(SRC_Z) |> !cc -D'const=const __flash' |> {objs}
+: foreach $(SRC) | ../z180/hdrom.h ../z180/cfboot.h |> !cc |> {objs}
+: foreach $(SRC_Z) |> !cc -D'const=const __flash' |> {objs}
: {objs} |> !LINK |> $(PROG).elf
: $(PROG).elf |> !OBJCOPY |> %B.hex
diff --git a/avr/cmd_boot.c b/avr/cmd_boot.c
index 73f2cf3..b00d062 100644
--- a/avr/cmd_boot.c
+++ b/avr/cmd_boot.c
@@ -20,6 +20,7 @@
#include "cli.h" /* run_command() */
#include "env.h"
#include "con-utils.h"
+#include "getopt-min.h"
#include "z80-if.h"
#include "z180-serv.h" /* restart_z180_serv() */
#include "debug.h"
@@ -27,28 +28,34 @@
/* ugly hack to get Z180 loadfile into flash memory */
#define const const FLASH
#include "../z180/hdrom.h"
+#include "../z180/cfboot.h"
#undef const
-static void z80_load_mem(void)
+static void z80_load_mem(int_fast8_t verbosity,
+ const FLASH unsigned char data[],
+ const FLASH unsigned long *sections,
+ const FLASH unsigned long address[],
+ const FLASH unsigned long length_of_sections[])
{
- unsigned sec = 0;
- uint32_t sec_base = hdrom_start;
-
- printf_P(PSTR("Loading Z180 memory... \n"));
-
- while (sec < hdrom_sections) {
- printf_P(PSTR(" From: 0x%.5lX to: 0x%.5lX (%5li bytes)\n"),
- hdrom_address[sec],
- hdrom_address[sec]+hdrom_length_of_sections[sec] - 1,
- hdrom_length_of_sections[sec]);
-
- z80_write_block_P((const FLASH unsigned char *) &hdrom[sec_base], /* src */
- hdrom_address[sec], /* dest */
- hdrom_length_of_sections[sec]); /* len */
- sec_base+=hdrom_length_of_sections[sec];
- sec++;
+ uint32_t sec_base = 0;
+
+ if (verbosity > 1)
+ printf_P(PSTR("Loading Z180 memory... \n"));
+
+ for (unsigned sec = 0; sec < *sections; sec++) {
+ if (verbosity > 0) {
+ printf_P(PSTR(" From: 0x%.5lX to: 0x%.5lX (%5li bytes)\n"),
+ address[sec],
+ address[sec]+length_of_sections[sec] - 1,
+ length_of_sections[sec]);
+ }
+
+ z80_write_block_P((const FLASH unsigned char *) &data[sec_base], /* src */
+ address[sec], /* dest */
+ length_of_sections[sec]); /* len */
+ sec_base += length_of_sections[sec];
}
}
@@ -64,13 +71,206 @@ command_ret_t do_loadf(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]
my_puts_P(PSTR("Bus timeout\n"));
return CMD_RET_FAILURE;
}
- z80_load_mem();
+ z80_load_mem(2, hdrom,
+ &hdrom_sections,
+ hdrom_address,
+ hdrom_length_of_sections);
+
z80_bus_cmd(Release);
return CMD_RET_SUCCESS;
}
+void print_vars(char *title)
+{
+ uint8_t buf[5];
+ zstate_t state = z80_bus_state();
+
+ if((state & ZST_ACQUIRED) == 0)
+ z80_bus_cmd(Request);
+
+ z80_read_block(buf, 9, sizeof buf);
+
+ if((state & ZST_ACQUIRED) == 0)
+ z80_bus_cmd(Release);
+
+ printf_P(PSTR("%s: stage: %d, flag: 0x%.02x, result: %d, IDE stat/error: 0x%.02x/0x%.02x\n"),
+ title, buf[0], buf[1], buf[2], buf[3], buf[4]);
+}
+
+
+/*
+ * bootcf [options]
+ *
+ * -a address (100h)
+ * -s start sector (0)
+ * -c sector count (7)
+ * -i Partition id (52)
+ * -n load only
+ * -t timeout (10000)
+ * -v verbose
+ */
+
+command_ret_t do_bootcf(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
+{
+ struct {
+ uint8_t jr[2];
+ uint16_t loadaddr;
+ uint8_t sec_start;
+ uint8_t sec_cnt;
+ uint8_t part_id;
+ uint16_t timeout;
+ uint8_t stop_stage;
+ } boot_param;
+
+ struct {
+ uint8_t stop_stage;
+ uint8_t done;
+ uint8_t result;
+ uint8_t ide_stat;
+ uint8_t ide_error;
+ } boot_res;
+
+ int_fast8_t verbosity = 0;
+ uint8_t stages;
+ uint32_t val;
+
+ (void) cmdtp; (void) flag;
+
+ /* get default values */
+ memcpy_P(&boot_param, cfboot, sizeof boot_param);
+ stages = boot_param.stop_stage;
+
+ /* reset getopt() */
+ optind = 0;
+
+ int opt;
+ while ((opt = getopt(argc, argv, PSTR("vna:s:c:t:i:"))) != -1) {
+ switch (opt) {
+ case 'v':
+ verbosity++;
+ break;
+ case 'n':
+ if (boot_param.stop_stage > 0)
+ boot_param.stop_stage--;
+ break;
+ case 'a':
+ val = strtoul(optarg, NULL, 16);
+ if (val < 0x100 || val > 0xFE00) {
+ printf_P(PSTR("Address out of range: 0x%.4lX\n"), val);
+ return CMD_RET_FAILURE;
+ }
+ boot_param.loadaddr = val;
+ break;
+ case 's':
+ val = strtoul(optarg, NULL, 16);
+ if (val > 255) {
+ printf_P(PSTR("Start sector out of range: 0x%lX\n"), val);
+ return CMD_RET_FAILURE;
+ }
+ boot_param.sec_start = val;
+ break;
+ case 'c':
+ val = strtoul(optarg, NULL, 16);
+ if (val > 127) {
+ printf_P(PSTR("Sector count out of range: 0x%lX\n"), val);
+ return CMD_RET_FAILURE;
+ }
+ boot_param.sec_cnt = val;
+ break;
+ case 't':
+ val = strtoul(optarg, NULL, 10);
+ if (val < 0x1 || val > 0xFFFF) {
+ printf_P(PSTR("Timeout value out of range: 0x%lX\n"), val);
+ return CMD_RET_FAILURE;
+ }
+ boot_param.loadaddr = val;
+ break;
+ case 'i':
+ val = strtoul(optarg, NULL, 16);
+ if (val < 0x01 || val > 0xFF) {
+ printf_P(PSTR("Partition id out of range: 0x%lX\n"), val);
+ return CMD_RET_FAILURE;
+ }
+ boot_param.part_id = val;
+ break;
+ default: /* '?' */
+ return CMD_RET_USAGE;
+ }
+ }
+
+ /* remaining arguments */
+ argc -= optind;
+ if (argc) {
+ my_puts_P(PSTR("Argument error!\n"));
+ return CMD_RET_USAGE;
+ }
+
+ if ((val = (uint32_t) boot_param.loadaddr + boot_param.sec_cnt * 512) >= 0xFF00) {
+ printf_P(PSTR("Top address out of range: 0x%.4lX\n"), val);
+ return CMD_RET_FAILURE;
+ }
+
+
+
+ if (z80_bus_state() & ZST_RUNNING) {
+ my_puts_P(PSTR("CPU is allready running!\n"));
+ return CMD_RET_FAILURE;
+ }
+ if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) {
+ my_puts_P(PSTR("Bus timeout\n"));
+ return CMD_RET_FAILURE;
+ }
+ z80_load_mem(verbosity, cfboot,
+ &cfboot_sections,
+ cfboot_address,
+ cfboot_length_of_sections);
+
+ z80_write_block((const uint8_t *) &boot_param,
+ cfboot_address[0], sizeof boot_param);
+ z80_bus_cmd(Release);
+
+ if (boot_param.stop_stage == 0) {
+ printf_P(PSTR("Bootloader loaded at: 0x%.4X\n"), (uint16_t) cfboot_address[0]);
+ } else {
+ printf_P(PSTR("Executing %d of %d Bootloader stages...\n"),
+ boot_param.stop_stage, stages);
+
+ z80_bus_cmd(Run);
+ z80_bus_cmd(Release);
+
+ clear_ctrlc(); /* forget any previous Control C */
+ for (boot_res.done = 0; boot_res.done != 0xFF;) {
+ _delay_ms(8);
+ /* check for ctrl-c to abort... */
+ if (had_ctrlc() || ctrlc()) {
+ break;
+ }
+ z80_bus_cmd(Request);
+ z80_read_block((uint8_t *) &boot_res,
+ cfboot_address[0]+sizeof boot_param - 1, sizeof boot_res);
+ z80_bus_cmd(Release);
+ }
+
+ if (boot_res.done != 0xFF) {
+ z80_bus_cmd(Reset);
+ my_puts_P(PSTR("Abort\n"));
+ } else {
+ if (boot_param.stop_stage == stages) {
+ my_puts_P(PSTR("Booting...\n"));
+ } else {
+ z80_bus_cmd(Reset);
+ printf_P(PSTR("Bootloader stopped at stage %d, result: %d, IDE stat/error: 0x%.02x/0x%.02x\n"),
+ boot_param.stop_stage - boot_res.stop_stage,
+ boot_res.result, boot_res.ide_stat, boot_res.ide_error);
+ }
+ }
+ }
+
+ return CMD_RET_SUCCESS;
+}
+
command_ret_t do_busreq_pulse(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
uint16_t count=1;
diff --git a/avr/command_tbl.c b/avr/command_tbl.c
index 944c1c4..7ebc008 100644
--- a/avr/command_tbl.c
+++ b/avr/command_tbl.c
@@ -16,6 +16,7 @@ extern command_ret_t do_env_default(cmd_tbl_t *, int, int, char * const []);
extern command_ret_t do_env_set(cmd_tbl_t *, int, int, char * const []);
extern command_ret_t do_env_save(cmd_tbl_t *, int, int, char * const []);
extern command_ret_t do_loadf(cmd_tbl_t *, int, int, char * const []);
+extern command_ret_t do_bootcf(cmd_tbl_t *, int, int, char * const []);
extern command_ret_t do_loadcpm3(cmd_tbl_t *, int, int, char * const []);
extern command_ret_t do_loadihex(cmd_tbl_t *, int, int, char * const []);
#if defined(CONFIG_CMD_LOADB)
@@ -157,6 +158,27 @@ CMD_TBL_ITEM(
""
),
CMD_TBL_ITEM(
+ bootcf, CONFIG_SYS_MAXARGS, 0, do_bootcf,
+ "boot from cf card",
+ "[options]\n"
+ " Load a number of sectors from the first CP/M partition and jump to\n"
+ " the load address.\n"
+ " -a ADDRESS\n"
+ " Load and start address (default 100 hex)\n"
+ " -s NUM\n"
+ " First sector of partition to load (0..255, default 0)\n"
+ " -c NUM\n"
+ " Number of sectors to load (1..127, default 7)\n"
+ " -i NUM\n"
+ " Partition type to look for (default 52 hex)\n"
+ " -n\n"
+ " Load only, do not execute\n"
+ " -t NUM\n"
+ " Timeout for IDE commands (1..65535, default 10000)\n"
+ " -v verbose\n"
+ " TODO: be verbose"
+),
+CMD_TBL_ITEM(
loadcpm3, 3, 0, do_loadcpm3,
"load CPM3.SYS file",
"[filename [common-base [banked-base]]] \n"
@@ -364,7 +386,7 @@ CMD_TBL_ITEM(
detach, 2, 1, do_attach,
"detach file from CP/M drive",
"dsk<n>]\n"
- " - same as attach -d dsk<n>"
+ " - alias for 'attach -d dsk<n>'"
),
CMD_TBL_ITEM(
diff --git a/z180/Makefile b/z180/Makefile
index c6727b9..159371f 100644
--- a/z180/Makefile
+++ b/z180/Makefile
@@ -89,7 +89,7 @@ hdrom.hex : $(OBJ)
@#$(cpm-link)
ld80 -o $@ -ms $(@:.hex=.map) -P $(LN_PROG) -D $(LN_DATA) $^
-%.rel %lst: %.180
+%.rel %.lst: %.180
@$(cpm-asm)
hdrom.map: hdrom.hex
diff --git a/z180/Tupfile b/z180/Tupfile
index 50ede98..c7d79fb 100644
--- a/z180/Tupfile
+++ b/z180/Tupfile
@@ -1,6 +1,7 @@
include_rules
PROG = hdrom
+CFBOOT = cfboot
SRC = init.180
SRC += ddtz.180
@@ -48,6 +49,9 @@ rm -f ${OUTPUT}; exit ${ERROR} \
: $(PROG).hex |> srec_cat -o %o -c_array %B -C_COMpressed -include %f -Intel |> $(PROG).c | $(PROG).h
+: $(CFBOOT).180 |> $(AS) -%B/HFS |> %B.hex | %B.lst
+: $(CFBOOT).hex |> srec_cat -o %o -c_array %B -C_COMpressed -include %f -Intel |> $(CFBOOT).c | $(CFBOOT).h
+
#COMMAND="$(AS) -%B/$(AS_OPT)"; \
diff --git a/z180/cfboot.180 b/z180/cfboot.180
new file mode 100644
index 0000000..d8c39fd
--- /dev/null
+++ b/z180/cfboot.180
@@ -0,0 +1,351 @@
+ TITLE 'CF cold boot loader'
+
+ ; Port Address Equates
+
+ include config.inc
+ include z180reg.inc
+
+ ; IDE Task File Register Definitions
+
+IDEDat equ IDEBASE+0 ; Data Register
+IDEErr equ IDEBASE+1 ; Error Register
+IDEFeat equ IDEBASE+1 ; Feature Register
+IDESCnt equ IDEBASE+2 ; Sector Count
+IDESNum equ IDEBASE+3 ; Sector Number
+IDECLo equ IDEBASE+4 ; Cylinder Low
+IDECHi equ IDEBASE+5 ; Cylinder High
+IDESDH equ IDEBASE+6 ; Drive and Head
+IDECmd equ IDEBASE+7 ; Command / Status
+
+ ; IDE Hard disk commands:
+
+CmdNOP equ 00h ; NOP Command
+CmdHome equ 10h ; Recalibrate
+CmdRd equ 20h ; Read Sector
+CmdWr equ 30h ; Write Sector
+CmdId equ 0ECh ; Read ID
+CmdSF equ 0EFh ; Set Feature
+
+ ; Partition Table Structures
+
+PTYPE equ 4
+PSTART equ 8
+PSIZE equ 12
+
+ ; Partition table id
+ ; (see http://www.win.tue.nl/~aeb/partitions/partition_types-1.html)
+
+PARTID1_FAT16 equ 00EH
+PARTID2_FAT16 equ 006H
+PARTID_CPM equ 052H
+
+
+DEBUG equ false ; not used
+DO_WAIT_NBSY equ false
+RUN_TPA equ false
+
+ if RUN_TPA
+base equ 0100h
+ else
+base equ 0
+ endif
+
+;-------------------------------------------------------------------------------
+
+ aseg
+
+ org base
+
+ jr start
+
+para: equ $
+loadaddr: dw base+100h
+sec_start: db 0
+sec_cnt: db 7
+part_id: db PARTID_CPM
+timeout: dw 10000
+stop_stage: db number_of_stages
+done: db 0
+result: db 0
+ide_result: db 0,0
+
+o_part_id equ part_id - para
+o_stop_stage equ stop_stage - para
+o_done equ done - para
+o_result equ result - para
+
+;-------------------------------------------------------------------------------
+
+start:
+ ld sp,stack
+ pop ix
+ pop de
+loop:
+ pop hl
+ push de
+ push hl
+ exx
+ ld hl,(loadaddr)
+ ret
+continue:
+ exx
+ ld (ix+o_result),a
+ or a
+ jr nz,stop
+ dec (ix+o_stop_stage)
+ jr nz,loop
+stop:
+ in a,(Idecmd) ;2
+ ld l,a ;1
+ in a,(IdeErr) ;2
+ ld h,a ;1
+ ld (ide_result),hl ;3 9
+ dec (ix+o_done)
+ halt
+
+;-------------------------------------------------------------------------------
+
+chk_to:
+ xor a ;
+to_l:
+ dec a
+ ex (sp),hl
+ ex (sp),hl
+ jr nz,to_l ;
+ dec hl ; 4
+ ld a,h ; 4
+ or l ; 4
+ ret nz ; 10/5
+ ccf ; 3
+ ret ; 9
+
+ if base = 0
+ if 044h-$ > 0
+ rept 044h-$
+ db 0
+ endm
+ endif
+ endif
+
+part_start:
+; dw 0
+; dw 0 ; part_start is 4 byte long, but stack gets free
+stack:
+ dw para
+ dw continue
+stages:
+ if DO_WAIT_NBSY
+ dw s_wait_not_bsy
+ endif
+ dw s_wait_rdy
+ dw s_check_io
+ dw s_set_xfermode8
+ dw s_read_parttbl
+ dw s_check_signature
+ dw s_find_partition
+ dw s_read_sectors
+ dw s_go
+number_of_stages equ ($-stages)/2
+
+ if DO_WAIT_NBSY
+;-------------------------------------------------------------------------------
+; Wait while device is busy with time out
+; return:
+; a = 0 if ok
+; a = ff in timeout
+; destroys hl
+
+s_wait_not_bsy:
+ ld hl,(timeout)
+wnb_l:
+ in a,(IdeCmd)
+ rla
+ jr nc,wnb_e
+ call chk_to
+ jr nc,wnb_l
+wnb_e:
+ sbc a,a
+ ret
+ endif
+
+;-------------------------------------------------------------------------------
+; Wait for ready signal with time out
+; return:
+; a = 0 if ok
+; a = ff in timeout
+; destroys hl
+
+s_wait_rdy:
+wait_rdy_to:
+ ld hl,(timeout)
+wrdy_l:
+ in a,(IdeCmd)
+ xor 01000000b
+ and 11000000b ; clears carry
+ jr z,wrdy_e
+ call chk_to
+ jr nc,wrdy_l
+wrdy_e:
+ sbc a,a
+ ret
+
+;-------------------------------------------------------------------------------
+
+s_check_io:
+ ld a,0E0h ; unit 0, lba mode
+ out (IdeSDH),a ;
+
+ xor a ; execute NOP command
+ call do_ide_cmd ; should return error
+ ret c
+ xor 1
+ ret nz
+ ld a,CmdHome ; execute RECALIBRATE command
+ jr do_ide_cmd
+
+;-------------------------------------------------------------------------------
+
+s_set_xfermode8:
+ ld a,1 ; Enable 8-bit data transfer.
+ out (IDEFeat),a
+ ld a,CmdSF ; Set feature command
+
+; fall thru
+; jr do_ide_cmd
+
+;-------------------------------------------------------------------------------
+
+do_ide_cmd:
+ out (IdeCmd),a ;
+ call wait_rdy_to
+ ret c
+ in a,(IdeCmd)
+ and 10001001b ;
+ ret
+
+;-------------------------------------------------------------------------------
+
+s_check_signature:
+; ld hl,(loadaddr)
+ inc h ; Point to last byte of MBR
+ inc h
+ dec hl
+ ld a,0aah
+ cp (hl) ; Test, if it has a valid MBR
+ ret nz
+ dec hl
+ cpl ; a=055h
+ sub (hl) ;
+ ret ; should be 0
+
+;-------------------------------------------------------------------------------
+; Read partition table (lbr)
+
+s_read_parttbl:
+; ld hl,(loadaddr)
+ ld bc,1*256 + 0 ; sector 0 (lba)
+ ld e,c
+ ld d,c
+ jr read_sectors
+
+;-------------------------------------------------------------------------------
+; Find CP/M paartition
+; Look for first CP/M partition
+; and save partition offset
+
+s_find_partition:
+; ld hl,(loadaddr)
+ ld de,512-2-64+PTYPE ; Point to partition type of first first partition table entry
+ add hl,de
+ ld de,16
+ ld b,4 ; Max # of partition table entries
+ploop:
+ ld a,(ix+o_part_id)
+ sub (HL) ; Test for CP/M Partition
+ jr nz,pnext
+ ld bc,4
+ add hl,bc ; Point to partition start (lba)
+ ld de,part_start
+ ldir
+ ret ;a=0
+pnext:
+ add hl,de
+ djnz ploop
+ ret
+
+
+;-------------------------------------------------------------------------------
+; Read sec_count sectors, beginning at part_start+sec_start
+
+s_read_sectors:
+; ld hl,(loadaddr)
+ push hl
+ ld bc,(sec_start) ;b=sec_count, c=sec_start
+ ld e,c
+ ld d,0
+ ld hl,(part_start) ;add partition offset to sector number
+ add hl,de
+ ld a,(part_start+2)
+ adc a,d ;d=0
+ ld c,a
+ ex de,hl
+ pop hl
+
+; fall thru
+
+;-------------------------------------------------------------------------------
+; Read a number of sectors
+; hl: memory address
+; cde: sector number (24 bit)
+; b: sector count
+
+read_sectors:
+ ld a,e ; lba 0..7
+ out (IdeSNum),a
+ ld a,d ; lba 0..7
+ out (IdeClo),a
+ ld a,c ; lba 0..7
+ out (IdeCHi),a
+ ld a,b ; number of sectors to read
+ out (IdeSCnt),a ; set sector count
+
+ ld a,CmdRd
+ out (IdeCmd),a ; command: read sector data
+ ld d,b
+ ld bc,IdeDat ; I/O address
+wdrq:
+ in a,(IdeCmd) ; wait for DRQ to become active
+ bit 3,a
+ jr z,wdrq
+ inir ; read 512 data bytes (2 x 256)
+ inir
+wnb: ; wait while busy
+ in a,(IdeCmd) ;
+ rlca
+ jr c,wnb
+ rrca ; restore status
+ bit 0,a
+ jr nz,err_out
+ dec d
+ jr nz,wdrq
+err_out:
+ and 10001001b ; Busy, DRQ, or Error?
+ ret ; return 0, if everything is ok
+
+;-------------------------------------------------------------------------------
+
+s_go:
+; ld hl,(loadaddr)
+ dec (ix+o_done)
+ jp (hl)
+
+
+;-------------------------------------------------------------------------------
+ if base = 0
+ if $ > 100h
+ .printx Error: Program to large to fit in page 0!
+ db "Stop
+ endif
+ endif
+
+ end
diff --git a/z180/config.inc b/z180/config.inc
index 4421478..bea6e05 100644
--- a/z180/config.inc
+++ b/z180/config.inc
@@ -3,8 +3,6 @@ FALSE equ 0
TRUE equ NOT FALSE
-DEBUG equ true
-
banked equ true
;-----------------------------------------------------
@@ -159,6 +157,8 @@ AVRINT5 equ 4Fh
AVRINT6 equ 5Fh
;PMSG equ 80h
+IDEBASE equ 60h
+
;-----------------------------------------------------
; Definition of (logical) top 2 memory pages