/* * VARCem Virtual ARchaeological Computer EMulator. * An emulator of (mostly) x86-based PC systems and devices, * using the ISA,EISA,VLB,MCA and PCI system buses, roughly * spanning the era between 1981 and 1995. * * This file is part of the VARCem Project. * * Implementation of the Toshiba T3100e. * * The Toshiba 3100e is a 286-based portable. * * To bring up the BIOS setup screen hold down the 'Fn' key * on booting. * * Memory management * ~~~~~~~~~~~~~~~~~ * * Motherboard memory is divided into: * - Conventional memory: Either 512k or 640k * - Upper memory: Either 512k or 384k, depending on * amount of conventional memory. * Upper memory can be used as EMS or XMS. * - High memory: 0-4Mb, depending on RAM installed. * The BIOS setup screen allows some or * all of this to be used as EMS; the * remainder is XMS. * * Additional memory (either EMS or XMS) can also be provided * by ISA expansion cards. * * Under test in PCem, the BIOS will boot with up to 65368Kb * of memory in total (16Mb less 16k). However it will give * an error with RAM sizes above 8Mb, if any of the high * memory is allocated as EMS, because the builtin EMS page * registers can only access up to 8Mb. * * Memory is controlled by writes to I/O port 8084h: * Bit 7: Always 0 } * Bit 6: Always 1 } These bits select which motherboard * Bit 5: Always 0 } function to access. * Bit 4: Set to treat upper RAM as XMS * Bit 3: Enable external RAM boards? * Bit 2: Set for 640k conventional memory, clear for 512k * Bit 1: Enable RAM beyond 1Mb. * Bit 0: Enable EMS. * * The last value written to this port is saved at 0040:0093h, * and in CMOS memory at offset 0x37. If the top bit of the * CMOS byte is set, then high memory is being provided by * an add-on card rather than the mainboard; accordingly, * the BIOS will not allow high memory to be used as EMS. * * EMS is controlled by 16 page registers: * * Page mapped at 0xD000 0xD400 0xD800 0xDC00 * ------------------------------------------------------ * Pages 0x00-0x7F 0x208 0x4208 0x8208 0xc208 * Pages 0x80-0xFF 0x218 0x4218 0x8218 0xc218 * Pages 0x100-0x17F 0x258 0x4258 0x8258 0xc258 * Pages 0x180-0x1FF 0x268 0x4268 0x8268 0xc268 * * The value written has bit 7 set to enable EMS, reset to * disable it. * * So: * OUT 0x208, 0x80 will page in the first 16k page at 0xD0000. * OUT 0x208, 0x00 will page out EMS, leaving nothing at 0xD0000. * OUT 0x4208, 0x80 will page in the first 16k page at 0xD4000. * OUT 0x218, 0x80 will page in the 129th 16k page at 0xD0000. * etc. * * To use EMS from DOS, you will need the Toshiba EMS driver * (TOSHEMM.ZIP). This supports the above system, plus further * ranges of ports at 0x_2A8, 0x_2B8, 0x_2C8. * * Features not implemented: * > Four video fonts. * > BIOS-controlled mapping of serial ports to IRQs. * > Custom keyboard controller. This has a number of extra * commands in the 0xB0-0xBC range, for such things as turbo * on/off, and switching the keyboard between AT and PS/2 * modes. Currently I have only implemented command 0xBB, * so that self-test completes successfully. Commands include: * * 0xB0: Turbo on * 0xB1: Turbo off * 0xB2: Internal display on? * 0xB3: Internal display off? * 0xB5: Get settings byte (bottom bit is color/mono setting) * 0xB6: Set settings byte * 0xB7: Behave as 101-key PS/2 keyboard * 0xB8: Behave as 84-key AT keyboard * 0xBB: Return a byte, bit 2 is Fn key state, other bits unknown. * * The other main I/O port needed to POST is: * 0x8084: System control. * Top 3 bits give command, bottom 5 bits give parameters. * 000 => set serial port IRQ / addresses * bit 4: IRQ5 serial port base: 1 => 0x338, 0 => 0x3E8 * bits 3, 2, 0 specify serial IRQs for COM1, COM2, COM3: * 00 0 => 4, 3, 5 * 00 1 => 4, 5, 3 * 01 0 => 3, 4, 5 * 01 1 => 3, 5, 4 * 10 0 => 4, -, 3 * 10 1 => 3, -, 4 * 010 => set memory mappings * bit 4 set if upper RAM is XMS * bit 3 enable add-on memory boards beyond 5Mb? * bit 2 set for 640k sysram, clear for 512k sysram * bit 1 enable mainboard XMS * bit 0 enable mainboard EMS * 100 => set parallel mode / LCD settings * bit 4 set for bidirectional parallel port * bit 3 set to disable internal CGA * bit 2 set for single-pixel LCD font * bits 0,1 for display font * * Version: @(#)m_at_t3100e.c 1.0.6 2018/10/22 * * Authors: Fred N. van Kempen, * Miran Grca, * Sarah Walker, * * Copyright 2017,2018 Fred N. van Kempen. * Copyright 2016-2018 Miran Grca. * Copyright 2008-2018 Sarah Walker. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the: * * Free Software Foundation, Inc. * 59 Temple Place - Suite 330 * Boston, MA 02111-1307 * USA. */ #include #include #include #include #include #include #define HAVE_STDARG_H #include "../86box.h" #include "../timer.h" #include "../io.h" #include "../mouse.h" #include "../mem.h" #include "../device.h" #include "../keyboard.h" #include "../rom.h" #include "../cpu/cpu.h" #include "../floppy/fdd.h" #include "../floppy/fdc.h" #include "machine.h" #include "m_at_t3100e.h" extern uint8_t *ram; /* Physical RAM */ void at_init(); /* The T3100e motherboard can (and does) dynamically reassign RAM between * conventional, XMS and EMS. This translates to monkeying with the mappings. */ extern mem_mapping_t base_mapping; extern mem_mapping_t ram_low_mapping; /* This is to switch conventional RAM * between 512k and 640k */ extern mem_mapping_t ram_mid_mapping; /* This will not be used */ extern mem_mapping_t ram_high_mapping; /* This is RAM beyond 1Mb if any */ extern uint8_t *ram; static unsigned t3100e_ems_page_reg[] = { 0x208, 0x4208, 0x8208, 0xc208, /* The first four map the first 2Mb */ /* of RAM into the page frame */ 0x218, 0x4218, 0x8218, 0xc218, /* The next four map the next 2Mb */ /* of RAM */ 0x258, 0x4258, 0x8258, 0xc258, /* and so on. */ 0x268, 0x4268, 0x8268, 0xc268, }; struct t3100e_ems_regs { uint8_t page[16]; mem_mapping_t mapping[4]; uint32_t page_exec[4]; /* Physical location of memory pages */ uint32_t upper_base; /* Start of upper RAM */ uint8_t upper_pages; /* Pages of EMS available from upper RAM */ uint8_t upper_is_ems; /* Upper RAM is EMS? */ mem_mapping_t upper_mapping; uint8_t notify; /* Notification from keyboard controller */ uint8_t turbo; /* 0 for 6MHz, else full speed */ uint8_t mono; /* Emulates PC/AT 'mono' motherboard switch */ /* Bit 0 is 0 for colour, 1 for mono */ } t3100e_ems; void t3100e_ems_out(uint16_t addr, uint8_t val, void *p); #ifdef ENABLE_T3100E_LOG int t3100e_do_log = ENABLE_T3100E_LOG; static void t3100e_log(const char *fmt, ...) { va_list ap; if (t3100e_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else #define t3100e_log(fmt, ...) #endif /* Given a memory address (which ought to be in the page frame at 0xD0000), * which page does it relate to? */ static int addr_to_page(uint32_t addr) { if ((addr & 0xF0000) == 0xD0000) { return ((addr >> 14) & 3); } return -1; } /* And vice versa: Given a page slot, which memory address does it * correspond to? */ static uint32_t page_to_addr(int pg) { return 0xD0000 + ((pg & 3) * 16384); } /* Given an EMS page ID, return its physical address in RAM. */ uint32_t t3100e_ems_execaddr(struct t3100e_ems_regs *regs, int pg, uint16_t val) { uint32_t addr; if (!(val & 0x80)) return 0; /* Bit 7 reset => not mapped */ val &= 0x7F; val += (0x80 * (pg >> 2)); /* The high bits of the register bank */ /* are used to extend val to allow up */ /* to 8Mb of EMS to be accessed */ /* Is it in the upper memory range? */ if (regs->upper_is_ems) { if (val < regs->upper_pages) { addr = regs->upper_base + 0x4000 * val; return addr; } val -= regs->upper_pages; } /* Otherwise work down from the top of high RAM (so, the more EMS, * the less XMS) */ if ((val * 0x4000) + 0x100000 >= (mem_size * 1024)) { return 0; /* Not enough high RAM for this page */ } /* High RAM found */ addr = (mem_size * 1024) - 0x4000 * (val + 1); return addr; } /* The registers governing the EMS ports are in rather a nonintuitive order */ static int port_to_page(uint16_t addr) { switch (addr) { case 0x208: return 0; case 0x4208: return 1; case 0x8208: return 2; case 0xC208: return 3; case 0x218: return 4; case 0x4218: return 5; case 0x8218: return 6; case 0xC218: return 7; case 0x258: return 8; case 0x4258: return 9; case 0x8258: return 10; case 0xC258: return 11; case 0x268: return 12; case 0x4268: return 13; case 0x8268: return 14; case 0xC268: return 15; } return -1; } /* Used to dump the memory mapping table, for debugging void dump_mappings() { mem_mapping_t *mm = base_mapping.next; if (!t3100e_log) return; while (mm) { const char *name = ""; uint32_t offset = (uint32_t)(mm->exec - ram); if (mm == &ram_low_mapping ) name = "LOW "; if (mm == &ram_mid_mapping ) name = "MID "; if (mm == &ram_high_mapping) name = "HIGH"; if (mm == &t3100e_ems.upper_mapping) name = "UPPR"; if (mm == &t3100e_ems.mapping[0]) { name = "EMS0"; offset = t3100e_ems.page_exec[0]; } if (mm == &t3100e_ems.mapping[1]) { name = "EMS1"; offset = t3100e_ems.page_exec[1]; } if (mm == &t3100e_ems.mapping[2]) { name = "EMS2"; offset = t3100e_ems.page_exec[2]; } if (mm == &t3100e_ems.mapping[3]) { name = "EMS3"; offset = t3100e_ems.page_exec[3]; } t3100e_log(" %p | base=%05x size=%05x %c @ %06x %s\n", mm, mm->base, mm->size, mm->enable ? 'Y' : 'N', offset, name); mm = mm->next; } }*/ void t3100e_map_ram(uint8_t val) { int n; int32_t upper_len; t3100e_log("OUT 0x8084, %02x [ set memory mapping :", val | 0x40); if (val & 1) t3100e_log("ENABLE_EMS "); if (val & 2) t3100e_log("ENABLE_XMS "); if (val & 4) t3100e_log("640K "); if (val & 8) t3100e_log("X8X "); if (val & 16) t3100e_log("UPPER_IS_XMS "); t3100e_log("\n"); /* Bit 2 controls size of conventional memory */ if (val & 4) { t3100e_ems.upper_base = 0xA0000; t3100e_ems.upper_pages = 24; } else { t3100e_ems.upper_base = 0x80000; t3100e_ems.upper_pages = 32; } upper_len = t3100e_ems.upper_pages * 16384; mem_mapping_set_addr(&ram_low_mapping, 0, t3100e_ems.upper_base); /* Bit 0 set if upper RAM is EMS */ t3100e_ems.upper_is_ems = (val & 1); /* Bit 1 set if high RAM is enabled */ if (val & 2) { mem_mapping_enable(&ram_high_mapping); } else { mem_mapping_disable(&ram_high_mapping); } /* Bit 4 set if upper RAM is mapped to high memory * (and bit 1 set if XMS enabled) */ if ((val & 0x12) == 0x12) { mem_mapping_set_addr(&t3100e_ems.upper_mapping, mem_size * 1024, upper_len); mem_mapping_enable(&t3100e_ems.upper_mapping); mem_mapping_set_exec(&t3100e_ems.upper_mapping, ram + t3100e_ems.upper_base); } else { mem_mapping_disable(&t3100e_ems.upper_mapping); } /* Recalculate EMS mappings */ for (n = 0; n < 4; n++) { t3100e_ems_out(t3100e_ems_page_reg[n], t3100e_ems.page[n], &t3100e_ems); } //dump_mappings(); } void t3100e_notify_set(uint8_t value) { t3100e_ems.notify = value; } void t3100e_mono_set(uint8_t value) { t3100e_ems.mono = value; } uint8_t t3100e_mono_get(void) { return t3100e_ems.mono; } void t3100e_turbo_set(uint8_t value) { t3100e_ems.turbo = value; if (!value) { cpu_dynamic_switch(0); /* 286/6 */ } else { cpu_dynamic_switch(cpu); } } uint8_t t3100e_sys_in(uint16_t addr, void *p) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)p; /* The low 4 bits always seem to be 0x0C. The high 4 are a * notification sent by the keyboard controller when it detects * an [Fn] key combination */ t3100e_log("IN 0x8084\n"); return 0x0C | (regs->notify << 4); } /* Handle writes to the T3100e system control port at 0x8084 */ void t3100e_sys_out(uint16_t addr, uint8_t val, void *p) { // struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)p; switch (val & 0xE0) { case 0x00: /* Set serial port IRQs. Not implemented */ t3100e_log("OUT 0x8084, %02x [ set serial port IRQs]\n", val); break; case 0x40: /* Set RAM mappings. */ t3100e_map_ram(val & 0x1F); break; case 0x80: /* Set video options. */ t3100e_video_options_set(val & 0x1F); break; /* Other options not implemented. */ default: t3100e_log("OUT 0x8084, %02x\n", val); break; } } uint8_t t3100e_config_get(void) { /* The byte returned: Bit 7: Set if internal plasma display enabled Bit 6: Set if running at 6MHz, clear at full speed Bit 5: Always 1? Bit 4: Set if the FD2MB jumper is present (internal floppy is ?tri-mode) Bit 3: Clear if the FD2 jumper is present (two internal floppies) Bit 2: Set if the internal drive is A:, clear if B: Bit 1: Set if the parallel port is configured as a floppy connector for the second drive. Bit 0: Set if the F2HD jumper is present (internal floppy is 720k) */ uint8_t value = 0x28; /* Start with bits 5 and 3 set. */ int type_a = fdd_get_type(0); int type_b = fdd_get_type(1); int prt_switch; /* External drive type: 0=> none, 1=>A, 2=>B */ /* Get display setting */ if (t3100e_display_get()) value |= 0x80; if (!t3100e_ems.turbo) value |= 0x40; /* Try to determine the floppy types.*/ prt_switch = (type_b ? 2 : 0); switch(type_a) { /* Since a T3100e cannot have an internal 5.25" drive, mark 5.25" A: drive as * being external, and set the internal type based on type_b. */ case 1: /* 360k */ case 2: /* 1.2Mb */ case 3: /* 1.2Mb RPMx2*/ prt_switch = 1; /* External drive is A: */ switch (type_b) { case 1: /* 360k */ case 4: value |= 1; break; /* 720k */ case 6: value |= 0x10; break; /* Tri-mode */ /* All others will be treated as 1.4M */ } break; case 4: value |= 0x01; /* 720k */ if (type_a == type_b) { value &= (~8); /* Two internal drives */ prt_switch = 0; /* No external drive */ } break; case 5: /* 1.4M */ case 7: /* 2.8M */ if (type_a == type_b) { value &= (~8); /* Two internal drives */ prt_switch = 0; /* No external drive */ } break; case 6: /* 3-mode */ value |= 0x10; if (type_a == type_b) { value &= (~8); /* Two internal drives */ prt_switch = 0; /* No external drive */ } break; } /* End switch */ switch (prt_switch) { case 0: value |= 4; break; /* No external floppy */ case 1: value |= 2; break; /* External floppy is A: */ case 2: value |= 6; break; /* External floppy is B: */ } return value; } /* Read EMS page register */ uint8_t t3100e_ems_in(uint16_t addr, void *p) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)p; int page = port_to_page(addr); if (page >= 0) return regs->page[page]; else { fatal("t3100e_ems_in(): invalid address"); return 0xff; } } /* Write EMS page register */ void t3100e_ems_out(uint16_t addr, uint8_t val, void *p) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)p; int pg = port_to_page(addr); if (pg == -1) return; regs->page_exec[pg & 3] = t3100e_ems_execaddr(regs, pg, val); t3100e_log("EMS: page %d %02x -> %02x [%06x]\n", pg, regs->page[pg], val, regs->page_exec[pg & 3]); regs->page[pg] = val; pg &= 3; /* Bit 7 set if page is enabled, reset if page is disabled */ if (regs->page_exec[pg]) { t3100e_log("Enabling EMS RAM at %05x\n", page_to_addr(pg)); mem_mapping_enable(®s->mapping[pg]); mem_mapping_set_exec(®s->mapping[pg], ram + regs->page_exec[pg]); } else { t3100e_log("Disabling EMS RAM at %05x\n", page_to_addr(pg)); mem_mapping_disable(®s->mapping[pg]); } } /* Read RAM in the EMS page frame */ static uint8_t ems_read_ram(uint32_t addr, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; int pg = addr_to_page(addr); if (pg < 0) return 0xFF; addr = regs->page_exec[pg] + (addr & 0x3FFF); return ram[addr]; } static uint16_t ems_read_ramw(uint32_t addr, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; int pg = addr_to_page(addr); if (pg < 0) return 0xFF; //t3100e_log("ems_read_ramw addr=%05x ", addr); addr = regs->page_exec[pg] + (addr & 0x3FFF); //t3100e_log("-> %06x val=%04x\n", addr, *(uint16_t *)&ram[addr]); return *(uint16_t *)&ram[addr]; } static uint32_t ems_read_raml(uint32_t addr, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; int pg = addr_to_page(addr); if (pg < 0) return 0xFF; addr = regs->page_exec[pg] + (addr & 0x3FFF); return *(uint32_t *)&ram[addr]; } /* Write RAM in the EMS page frame */ static void ems_write_ram(uint32_t addr, uint8_t val, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; int pg = addr_to_page(addr); if (pg < 0) return; addr = regs->page_exec[pg] + (addr & 0x3FFF); ram[addr] = val; } static void ems_write_ramw(uint32_t addr, uint16_t val, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; int pg = addr_to_page(addr); if (pg < 0) return; //t3100e_log("ems_write_ramw addr=%05x ", addr); addr = regs->page_exec[pg] + (addr & 0x3FFF); //t3100e_log("-> %06x val=%04x\n", addr, val); *(uint16_t *)&ram[addr] = val; } static void ems_write_raml(uint32_t addr, uint32_t val, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; int pg = addr_to_page(addr); if (pg < 0) return; addr = regs->page_exec[pg] + (addr & 0x3FFF); *(uint32_t *)&ram[addr] = val; } /* Read RAM in the upper area. This is basically what the 'remapped' * mapping in mem.c does, except that the upper area can move around */ static uint8_t upper_read_ram(uint32_t addr, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; addr = (addr - (1024 * mem_size)) + regs->upper_base; return ram[addr]; } static uint16_t upper_read_ramw(uint32_t addr, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; addr = (addr - (1024 * mem_size)) + regs->upper_base; return *(uint16_t *)&ram[addr]; } static uint32_t upper_read_raml(uint32_t addr, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; addr = (addr - (1024 * mem_size)) + regs->upper_base; return *(uint32_t *)&ram[addr]; } static void upper_write_ram(uint32_t addr, uint8_t val, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; addr = (addr - (1024 * mem_size)) + regs->upper_base; ram[addr] = val; } static void upper_write_ramw(uint32_t addr, uint16_t val, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; addr = (addr - (1024 * mem_size)) + regs->upper_base; *(uint16_t *)&ram[addr] = val; } static void upper_write_raml(uint32_t addr, uint32_t val, void *priv) { struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv; addr = (addr - (1024 * mem_size)) + regs->upper_base; *(uint32_t *)&ram[addr] = val; } int machine_at_t3100e_init(const machine_t *model) { int ret; ret = bios_load_linear(L"roms/machines/t3100e/t3100e.rom", 0x000f0000, 65536, 0); if (bios_only || !ret) return ret; int pg; memset(&t3100e_ems, 0, sizeof(t3100e_ems)); machine_at_common_ide_init(model); device_add(&keyboard_at_toshiba_device); device_add(&fdc_at_device); /* Hook up system control port */ io_sethandler(0x8084, 0x0001, t3100e_sys_in, NULL, NULL, t3100e_sys_out, NULL, NULL, &t3100e_ems); /* Start monitoring all 16 EMS registers */ for (pg = 0; pg < 16; pg++) { io_sethandler(t3100e_ems_page_reg[pg], 0x0001, t3100e_ems_in, NULL, NULL, t3100e_ems_out, NULL, NULL, &t3100e_ems); } /* Map the EMS page frame */ for (pg = 0; pg < 4; pg++) { t3100e_log("Adding memory map at %x for page %d\n", page_to_addr(pg), pg); mem_mapping_add(&t3100e_ems.mapping[pg], page_to_addr(pg), 16384, ems_read_ram, ems_read_ramw, ems_read_raml, ems_write_ram, ems_write_ramw, ems_write_raml, NULL, MEM_MAPPING_EXTERNAL, &t3100e_ems); /* Start them all off disabled */ mem_mapping_disable(&t3100e_ems.mapping[pg]); } /* Mapping for upper RAM when in use as XMS*/ mem_mapping_add(&t3100e_ems.upper_mapping, mem_size * 1024, 384 * 1024, upper_read_ram, upper_read_ramw, upper_read_raml, upper_write_ram, upper_write_ramw, upper_write_raml, NULL, MEM_MAPPING_INTERNAL, &t3100e_ems); mem_mapping_disable(&t3100e_ems.upper_mapping); device_add(&t3100e_device); return ret; }