diff --git a/data/webui/config/macfuji-rev0.yaml b/data/webui/config/fujimac-rev0.yaml similarity index 100% rename from data/webui/config/macfuji-rev0.yaml rename to data/webui/config/fujimac-rev0.yaml diff --git a/data/webui/device_specific/BUILD_ATARI/picoboot.bin b/data/webui/device_specific/BUILD_ATARI/picoboot.bin index f11286d48..63858ce22 100644 Binary files a/data/webui/device_specific/BUILD_ATARI/picoboot.bin and b/data/webui/device_specific/BUILD_ATARI/picoboot.bin differ diff --git a/data/webui/device_specific/BUILD_MAC/autorun.moof b/data/webui/device_specific/BUILD_MAC/autorun.moof new file mode 100644 index 000000000..c9c82c3cb Binary files /dev/null and b/data/webui/device_specific/BUILD_MAC/autorun.moof differ diff --git a/include/pinmap/mac_rev0.h b/include/pinmap/mac_rev0.h index 765b6f12f..d878884e9 100644 --- a/include/pinmap/mac_rev0.h +++ b/include/pinmap/mac_rev0.h @@ -55,7 +55,6 @@ #define SP_ACK GPIO_NUM_27 #define SP_RDDATA GPIO_NUM_4 // tri-state gate enable line #define SP_WRDATA GPIO_NUM_22 -// TODO: go through each line and make sure the code is OK for each one before moving to next #define SP_WREQ GPIO_NUM_26 #define SP_DRIVE1 GPIO_NUM_36 #define SP_DRIVE2 GPIO_NUM_21 @@ -69,4 +68,5 @@ #endif // MASTERIES_SPI_FIX #define SP_EXTRA SP_DRIVE2 // For extra debugging with logic analyzer -#endif /* PINMAP_A2_REV0 */ +#endif /* PINMAP_MAC_REV0 */ + diff --git a/lib/bus/mac/mac.cpp b/lib/bus/mac/mac.cpp new file mode 100644 index 000000000..3d8dae19f --- /dev/null +++ b/lib/bus/mac/mac.cpp @@ -0,0 +1,818 @@ +#ifdef BUILD_MAC +#include "mac.h" +#include "../../include/debug.h" + + + +void macBus::setup(void) +{ + Debug_printf(("\r\nMAC FujiNet based on FujiApple\r\n")); + fnUartBUS.begin(_mac_baud_rate); +} + +void macBus::service(void) +{ + if (fnUartBUS.available()) + { + + } +} + +void macBus::shutdown(void) +{ + shuttingDown = true; + + for (auto devicep : _daisyChain) + { + // Debug_printf("Shutting down device %02x\n", devicep.second->id()); + // devicep.second->shutdown(); + Debug_printf("Shutting down device %02x\n", devicep->id()); + devicep->shutdown(); + } + Debug_printf("All devices shut down.\n"); +} + +macBus MAC; // global smartport bus variable + +#endif // BUILD_MAC + +#if 0 +#include "iwm.h" +#include "fnSystem.h" +#include "fnHardwareTimer.h" +// #include "fnFsTNFS.h" // do i need this? +#include +// #include "driver/timer.h" // contains the hardware timer register data structure +#include "../../include/debug.h" +#include "utils.h" +#include "led.h" + +#include "../device/iwm/disk.h" +#include "../device/iwm/disk2.h" +#include "../device/iwm/fuji.h" +#include "../device/iwm/cpm.h" +#include "../device/iwm/clock.h" + +/****************************************************************************** +Based on: +Apple //c Smartport Compact Flash adapter +Written by Robert Justice email: rjustice(at)internode.on.net +Ported to Arduino UNO with SD Card adapter by Andrea Ottaviani email: andrea.ottaviani.69(at)gmail.com +SD FAT support added by Katherine Stark at https://gitlab.com/nyankat/smartportsd/ + ***************************************************************************** + * Written for FujiNet ESP32 by @jeffpiep + * search for "todo" to find things to work on +*/ + +/* pin assignments for Arduino UNO +from http://www.users.on.net/~rjustice/SmartportCFA/SmartportSD.htm +IDC20 Disk II 20-pin pins based on +https://www.bigmessowires.com/2015/04/09/more-fun-with-apple-iigs-disks/ +*/ + +// only used when looking for tricky things - will crash after first packet most likely +#undef VERBOSE_IWM +// #define VERBOSE_IWM + +//------------------------------------------------------------------------------ + +//***************************************************************************** +// Function: print_packet +// Parameters: pointer to data, number of bytes to be printed +// Returns: none +// +// Description: prints packet data for debug purposes to the serial port +//***************************************************************************** +void print_packet(uint8_t *data, int bytes) +{ + int row; + char tbs[12]; + char xx; + + Debug_printf(("\n")); + for (int count = 0; count < bytes; count = count + 16) + { + sprintf(tbs, ("%04X: "), count); + Debug_print(tbs); + for (row = 0; row < 16; row++) + { + if (count + row >= bytes) + Debug_print((" ")); + else + { + Debug_printf("%02x ", data[count + row]); + } + } + Debug_print(("-")); + for (row = 0; row < 16; row++) + { + if ((data[count + row] > 31) && (count + row < bytes) && (data[count + row] < 128)) + { + xx = data[count + row]; + Debug_printf("%c", xx); + } + else + { + Debug_print((".")); + } + } + Debug_printf(("\n")); + } +} + +void print_packet(uint8_t *data) +{ + Debug_printf("\n"); + for (int i = 0; i < 40; i++) + { + if (data[i] != 0 || i == 0) + Debug_printf("%02x ", data[i]); + else + break; + } + // Debug_printf("\r\n"); +} + +void print_packet_wave(uint8_t *data, int bytes) +{ + int row; + char tbs[12]; + + Debug_printf(("\n")); + for (int count = 0; count < bytes; count = count + 12) + { + sprintf(tbs, ("%04X: "), count); + Debug_print(tbs); + for (row = 0; row < 12; row++) + { + if (count + row >= bytes) + Debug_print((" ")); + else + { + uint8_t b = data[count + row]; + for (int bnum = 0; bnum < 8; bnum++) + { + if (b & 0x80) + { + Debug_print("#"); + } + else + { + Debug_print("_"); + } + b <<= 1; + } + Debug_print("."); + } + } + Debug_printf(("\r\n")); + } +} + +//------------------------------------------------------------------------------ + +// uint8_t iwmDevice::packet_buffer[BLOCK_PACKET_LEN] = { 0 }; +// uint16_t iwmDevice::packet_len = 0; +// uint16_t iwmDevice::num_decoded = 0; + +uint8_t iwmDevice::data_buffer[MAX_DATA_LEN] = {0}; +int iwmDevice::data_len = 0; + +void iwmBus::iwm_ack_deassert() +{ + smartport.iwm_ack_set(); // go hi-z +} + +void iwmBus::iwm_ack_assert() +{ + smartport.iwm_ack_clr(); + smartport.spi_end(); +} + +bool iwmBus::iwm_phase_val(uint8_t p) +{ + uint8_t phases = _phases; // smartport.iwm_phase_vector(); + if (p < 4) + return (phases >> p) & 0x01; + Debug_printf("\r\nphase number out of range"); + return false; +} + +iwmBus::iwm_phases_t iwmBus::iwm_phases() +{ + iwm_phases_t phasestate = iwm_phases_t::idle; + // phase lines for smartport bus reset + // ph3=0 ph2=1 ph1=0 ph0=1 + // phase lines for smartport bus enable + // ph3=1 ph2=x ph1=1 ph0=x + uint8_t phases = smartport.iwm_phase_vector(); + switch (phases) + { + case 0b1010: + case 0b1011: + phasestate = iwm_phases_t::enable; + break; + case 0b0101: + phasestate = iwm_phases_t::reset; + break; + default: + phasestate = iwm_phases_t::idle; + break; + } + +#ifdef VERBOSE_IWM + if (phasestate != oldphase) + { + // Debug_printf("\r\n%d%d%d%d",iwm_phase_val(0),iwm_phase_val(1),iwm_phase_val(2),iwm_phase_val(3)); + switch (phasestate) + { + case iwm_phases_t::idle: + Debug_printf("\r\nidle"); + break; + case iwm_phases_t::reset: + Debug_printf("\r\nreset"); + break; + case iwm_phases_t::enable: + Debug_printf("\r\nenable"); + } + oldphase = phasestate; + } +#endif + + return phasestate; +} + +//------------------------------------------------------ + +int iwmBus::iwm_send_packet(uint8_t source, iwm_packet_type_t packet_type, uint8_t status, const uint8_t *data, uint16_t num) +{ + int r; + int retry = 5; // host seems to control the retries, this is here so we don't get stuck + + smartport.encode_packet(source, packet_type, status, data, num); +#ifdef DEBUG + // print_packet(smartport.packet_buffer,BLOCK_PACKET_LEN); // print raw packet contents to be sent +#endif + do + { + r = smartport.iwm_send_packet_spi(); + retry--; + } while (r && retry); // retry if we get an error and haven't tried to many times + + return r; +} + +bool iwmBus::iwm_decode_data_packet(uint8_t *data, int &n) +{ + n = smartport.decode_data_packet(data); + return false; +} +/* + { + memset(data, 0, n); + int nn = 17 + n % 7 + (n % 7 != 0) + n * 8 / 7; + Debug_printf("\r\nAttempting to receive %d length packet", nn); + portDISABLE_INTERRUPTS(); + iwm_ack_deassert(); + for (int i = 0; i < attempts; i++) + { + int error = smartport.iwm_read_packet_spi(nn); + if (!error) + { + iwm_ack_assert(); + portENABLE_INTERRUPTS(); +#ifdef DEBUG + //if (smartport.packet_buffer[8] == 0x82) // packet type // limit debug packet print if needed to data packets + // print_packet(smartport.packet_buffer,BLOCK_PACKET_LEN); // print raw received packet contents +#endif + n = smartport.decode_data_packet(data); + return false; + } + else if (error == 2) // checksum nok + { + smartport.spi_end(); // when this ends, we might be in the middle of receiving the next resent packet + portENABLE_INTERRUPTS(); + Debug_printf("\r\nChksum error, calc %02x, pkt %02x", smartport.calc_checksum, smartport.pkt_checksum); +#ifdef DEBUG + //print_packet(smartport.packet_buffer,BLOCK_PACKET_LEN); // print raw received packet contents +#endif + portDISABLE_INTERRUPTS(); + smartport.req_wait_for_falling_timeout(1000000); // wait up to 100ms to catch REQ going low and line up with end of the resent packet + } // if + } +#ifdef DEBUG + Debug_printf("\r\nERROR: Read Packet tries exceeds %d attempts", attempts); + // print_packet(data); +#endif + portENABLE_INTERRUPTS(); + return true; +} +*/ + +void iwmBus::setup(void) +{ + Debug_printf(("\r\nIWM FujiNet based on SmartportSD v1.15\r\n")); + + fnTimer.config(); + Debug_printf("\r\nFujiNet Hardware timer started"); + + diskii_xface.setup_rmt(); + Debug_printf("\r\nRMT configured for Disk ][ Output"); + + smartport.setup_spi(); + Debug_printf("\r\nSPI configured for smartport I/O"); + + smartport.setup_gpio(); + Debug_printf("\r\nIWM GPIO configured"); +} + +//***************************************************************************** +// Function: encode_data_packet +// Parameters: source id +// Returns: none +// +// Description: encode 512 byte data packet for read block command from host +// requires the data to be in the packet buffer, and builds the smartport +// packet IN PLACE in the packet buffer +//***************************************************************************** + +//***************************************************************************** +// Function: send_init_reply_packet +// Parameters: source +// Returns: none +// +// Description: this is the reply to the init command packet. A reply indicates +// the original dest id has a device on the bus. If the STAT byte is 0, (0x80) +// then this is not the last device in the chain. This is written to support up +// to 4 partions, i.e. devices, so we need to specify when we are doing the last +// init reply. +//***************************************************************************** +void iwmDevice::send_init_reply_packet(uint8_t source, uint8_t status) +{ + IWM.iwm_send_packet(source, iwm_packet_type_t::status, status, nullptr, 0); +} + +void iwmDevice::send_reply_packet(uint8_t status) +{ + IWM.iwm_send_packet(id(), iwm_packet_type_t::status, status, nullptr, 0); +} + +void iwmDevice::iwm_return_badcmd(iwm_decoded_cmd_t cmd) +{ + // Handle possible data packet to avoid crash extended and non-extended + switch (cmd.command) + { + case 0x42: + case 0x44: + case 0x49: + case 0x4a: + case 0x4b: + case 0x02: + case 0x04: + case 0x09: + case 0x0a: + case 0x0b: + data_len = 512; + IWM.iwm_decode_data_packet((uint8_t *)data_buffer, data_len); + Debug_printf("\r\nUnit %02x Bad Command with data packet %02x\r\n", id(), cmd.command); + print_packet((uint8_t *)data_buffer, data_len); + break; + default: // just send the response and return like before + send_reply_packet(SP_ERR_BADCMD); + Debug_printf("\r\nUnit %02x Bad Command %02x", id(), cmd.command); + return; + } + if (cmd.command == 0x04) // Decode command control code + { + send_reply_packet(SP_ERR_BADCTL); // we may be required to accept some control commands + // but for now just report bad control if it's a control + // command + uint8_t control_code = get_status_code(cmd); + Debug_printf("\r\nbad command was a control command with control code %02x", control_code); + } + else + { + send_reply_packet(SP_ERR_BADCMD); // response for Any other command with a data packet + } +} + +void iwmDevice::iwm_return_ioerror() +{ + // Debug_printf("\r\nUnit %02x Bad Command %02x", id(), cmd.command); + send_reply_packet(SP_ERR_IOERROR); +} + +void iwmDevice::iwm_return_noerror() +{ + send_reply_packet(SP_ERR_NOERROR); +} + +void iwmDevice::iwm_status(iwm_decoded_cmd_t cmd) // override; +{ + uint8_t status_code = cmd.params[2]; // cmd.g7byte3 & 0x7f; // (packet_buffer[19] & 0x7f); // | (((unsigned short)packet_buffer[16] << 3) & 0x80); + Debug_printf("\r\nTarget Device: %02x", id()); + // add a switch case statement for ALL THE STATUSESESESESS + if (status_code == 0x03) + { // if statcode=3, then status with device info block + Debug_printf("\r\n******** Sending DIB! ********"); + send_status_dib_reply_packet(); + // print_packet ((unsigned char*) packet_buffer,get_packet_length()); + // fnSystem.delay(50); + } + else + { // else just return device status + Debug_printf("\r\nSending Status"); + send_status_reply_packet(); + } +} + +//***************************************************************************** +// Function: main loop +/* + * notes: + * with individual devices, like disk.cpp, + * we need to hand off control to the device to service + * the command packet. + * + * Disk II/3.5 selection is determined by the ENABLE lines + * from BMOW - https://www.bigmessowires.com/2015/04/09/more-fun-with-apple-iigs-disks/ + * On an Apple II, things are more complicated. The Apple 5.25 controller card was the first to use a DB19 connector, and it supported two daisy-chained 5.25 inch drives. Pin 17 is /DRIVE1 enable, and pin 9 (unconnected on the Macintosh) is /DRIVE2 enable. Within each drive, internal circuitry routes the signal from input pin 9 to output pin 17 on the daisy-chain connector. Drive #2 doesn’t actually know that it’s drive #2 – it enables itself by observing /DRIVE1 on pin 17, just like the first drive – only the first drive has sneakily rerouted /DRIVE2 to /DRIVE1. This allows for two drives to be daisy chained. + * On an Apple IIgs, it’s even more complicated. Its DB19 connector supports daisy-chaining two 3.5 inch drives, and two 5.25 inch drives – as well as even more SmartPort drives, which I won’t discuss now. Pin 4 (GND on the Macintosh) is /EN3.5, a new signal that enables the 3.5 inch drives when it’s low, or the 5.25 inch drives when it’s high. The 3.5 inch drives must appear before any 5.25 inch drives in the daisy chain. When /EN3.5 is low, the 3.5 inch drives use pins 17 and 9 to enable themselves, and when /EN3.5 is high, the 3.5 inch drives pass through the signals on pins 17 and 9 unmodified to the 5.25 drives behind them. + * This is getting complicated, but there’s one final kick in the nuts: when the first 3.5 drive is enabled, by the IIgs setting /EN3.5 and /DRIVE1 both low, you would think the drive would disable the next 3.5 drive behind it by setting both /DRIVE1 and /DRIVE2 high at the daisy-chain connector. But no, the first 3.5 drive disables the second 3.5 drive by setting both /DRIVE1 and /DRIVE2 low! This looks like both are enabled at the same time, which would be a definite no-no, but the Apple 3.5 Drive contains circuitry that recognizes this “double enable” as being equivalent to a disable. Why it’s done this way, I don’t know, but I’m sure it has some purpose. + * + * So for starters FN will look at the /DRIVEx line (not sure which one because IIc has internal floppy drive connected right now) + * If floppy is enabled, the motor is spinning and FN needs to track the phases and spit out data (unless writereq is activated) + * If floppy is disabled, smartport should be in control instead. + * + * The smartport algorithm is something like: + * check for 0x85 init and do a bus initialization: + * BUS INIT + * after a reset, all devices no longer have an address + * and they are gating some signal (REQ?) so devices + * down the chain cannot respond to commands. So the + * first device responds to INIT. During this, it checks + * the sense line (still not sure which pin this is) to see + * if it is low (grounded) or high (floating or pulled up?). + * It will be low if there's another device in the chain + * after it. If it is the last device it will be high. + * It sends this state in the response to INIT. It also + * ungates whatever the magic line is so the next device + * in the chain can receive the INIT command that is + * coming next. This repeats until the last device in the + * chain says it's so and the A2 will stop sending INITs. + * + * Every other command: + * The bus class checks the target device and should pass + * on the command packet to the device service routine. + * Then the device can respond accordingly. + * + * When device ID is not FujiNet's: + * If the device ID does not belong to any of the FujiNet + * devices (disks, printers, modem, network, etc) then FN + * should not respond. The SmartPortSD code runs through + * the states for the packets that should come next. I'm + * not sure this is the best because what happens in case + * of a malfunction. I suppose there could be a time out + * that takes us back to idle. This will take more + * investigation. + */ +//***************************************************************************** +void IRAM_ATTR iwmBus::service() +{ + // process smartport before diskII + // read phase lines to check for smartport reset or enable + switch (iwm_phases()) + { + case iwm_phases_t::idle: + break; + case iwm_phases_t::reset: + Debug_printf(("\r\nReset")); + + // clear all the device addresses + for (auto devicep : _daisyChain) + devicep->_devnum = 0; + + while (iwm_phases() == iwm_phases_t::reset) + portYIELD(); // no timeout needed because the IWM must eventually clear reset. + // even if it doesn't, we would just come back to here, so might as + // well wait until reset clears. + + Debug_printf(("\r\nReset Cleared")); + + // if /EN35 is high, we must be on a host that supports 3.5 dumb drives + // lets sample it here in case the host is not on when the FN is powered on/reset + (GPIO.in1.val & (0x01 << (SP_EN35 - 32))) ? en35Host = true : en35Host = false; + Debug_printf("\r\nen35Host = %d", en35Host); + + break; + case iwm_phases_t::enable: + // expect a command packet + // should not ACK unless we know this is our Command + + if (sp_command_mode != sp_cmd_state_t::command) + { + // iwm_ack_deassert(); // go hi-Z + return; + } + + if (command_packet.command == 0x85) + { + // wait for REQ to go low + if (iwm_req_deassert_timeout(50000)) + { + // iwm_ack_deassert(); // go hi-Z + return; + } + +#ifdef DEBUG + print_packet(command_packet.data); + Debug_printf("\r\nhandling init command"); +#endif + handle_init(); + } + else + { + for (auto devicep : _daisyChain) + { + if (command_packet.dest == devicep->_devnum) + { + // wait for REQ to go low + if (iwm_req_deassert_timeout(50000)) + { + Debug_printf("\nREQ timeout in command processing"); + iwm_ack_deassert(); // go hi-Z + return; + } + // need to take time here to service other ESP processes so they can catch up + taskYIELD(); // Allow other tasks to run + Debug_printf("\nCommand Packet:"); + print_packet(command_packet.data); + + _activeDev = devicep; + // handle command + memset(command.decoded, 0, sizeof(command.decoded)); + smartport.decode_data_packet(command_packet.data, command.decoded); + print_packet(command.decoded, 9); + _activeDev->process(command); + } + } + } + + sp_command_mode = sp_cmd_state_t::standby; + memset(command_packet.data, 0, sizeof(command_packet)); + + iwm_ack_deassert(); // go hi-Z + } // switch (phasestate) + +#if (defined(DISKII_DRIVE1) || defined(DISKII_DRIVE2)) + // check on the diskii status + switch (iwm_drive_enabled()) + { + case iwm_enable_state_t::off: + break; + case iwm_enable_state_t::off2on: + // need to start a counter and wait to turn on enable output after 1 ms only iff enable state is on + if (theFuji._fnDisk2s[diskii_xface.iwm_enable_states() - 1].device_active) + { + fnSystem.delay(1); // need a better way to figure out persistence + if (iwm_drive_enabled() == iwm_enable_state_t::on) + { + diskii_xface.start(); // start it up + } + } // make a call to start the RMT stream + else + { + diskii_xface.set_output_to_low(); // not sure if best way to trick IIc into booting SP + // alternative approach is to enable RMT to spit out PRN bits + } + // make sure the state machine moves on to iwm_enable_state_t::on + return; // return so the SP code doesn't get checked + case iwm_enable_state_t::on: +#ifdef DEBUG + new_track = theFuji._fnDisk2s[diskii_xface.iwm_enable_states() - 1].get_track_pos(); + if (old_track != new_track) + { + Debug_printf("\ntrk pos %03d on d%d", new_track, diskii_xface.iwm_enable_states()); + old_track = new_track; + } +#endif + return; + case iwm_enable_state_t::on2off: + fnSystem.delay(1); // need a better way to figure out persistence + diskii_xface.stop(); + iwm_ack_deassert(); + return; + } +#endif +} + +iwm_enable_state_t IRAM_ATTR iwmBus::iwm_drive_enabled() +{ + uint8_t phases = smartport.iwm_phase_vector(); + uint8_t newstate = diskii_xface.iwm_enable_states(); + + if (!((phases & 0b1000) && (phases & 0b0010))) // SP bus not enabled + { + switch (_old_enable_state) + { + case iwm_enable_state_t::off: + _new_enable_state = (newstate != 0) ? iwm_enable_state_t::off2on : iwm_enable_state_t::off; + break; + case iwm_enable_state_t::off2on: + _new_enable_state = (newstate != 0) ? iwm_enable_state_t::on : iwm_enable_state_t::on2off; + break; + case iwm_enable_state_t::on: + _new_enable_state = (newstate != 0) ? iwm_enable_state_t::on : iwm_enable_state_t::on2off; + break; + case iwm_enable_state_t::on2off: + _new_enable_state = (newstate != 0) ? iwm_enable_state_t::off2on : iwm_enable_state_t::off; + break; + } + if (_old_enable_state != _new_enable_state) + Debug_printf("\ndisk ii enable states: %02x", newstate); + + _old_enable_state = _new_enable_state; + + return _new_enable_state; + } + else + { + return iwm_enable_state_t::off; + } +} + +void iwmBus::handle_init() +{ + uint8_t status = 0; + iwmDevice *pDevice = nullptr; + + fnLedManager.set(LED_BUS, true); + + // iwm_rddata_clr(); + + // to do - get the next device in the daisy chain and assign ID + for (auto it = _daisyChain.begin(); it != _daisyChain.end(); ++it) + { + // tell the Fuji it's device no. + if (it == _daisyChain.begin()) + { + theFuji._devnum = command_packet.dest; + } + // assign dev numbers + pDevice = (*it); + pDevice->switched = false; // reset switched condition on init + if (pDevice->id() == 0) + { + pDevice->_devnum = command_packet.dest; // assign address + if (++it == _daisyChain.end()) + status = 0xff; // end of the line, so status=non zero - to do: check GPIO for another device in the physical daisy chain + Debug_printf("\r\nSending INIT Response Packet..."); + pDevice->send_init_reply_packet(command_packet.dest, status); + + // smartport.iwm_send_packet_spi((uint8_t *)pDevice->packet_buffer); // timeout error return is not handled here (yet?) + + // print_packet ((uint8_t*) packet_buffer,get_packet_length()); + + Debug_printf(("\r\nDrive: %02x\r\n"), pDevice->id()); + fnLedManager.set(LED_BUS, false); + return; + } + } + + fnLedManager.set(LED_BUS, false); +} + +// Add device to SIO bus +void iwmBus::addDevice(iwmDevice *pDevice, iwm_fujinet_type_t deviceType) +{ + // SmartPort interface assigns device numbers to the devices in the daisy chain one at a time + // as opposed to using standard or fixed device ID's like Atari SIO. Therefore, an emulated + // device cannot rely on knowing its device number until it is assigned. + // Instead of using device_id's to know what kind a specific device is, smartport + // uses a Device Information Block (DIB) that is returned in a status call for DIB. The + // DIB includes a 16-character string, Device type byte, and Device subtype byte. + // In the IIgs firmware reference, the following device types are defined: + // 0 - memory cards (internal to the machine) + // 1 - Apple and Uni 3.5 drives + // 2 - harddisk + // 3 - SCSI disk + // The subtype uses the 3 msb's to indicate the following: + // 0x80 == 1 -> support extended smartport + // 0x40 == 1 -> supprts disk-switched errors + // 0x20 == 0 -> removable media (1 means non removable) + + // todo: work out how to use addDevice + // we can add devices and indicate they are not initialized and have no device ID - call it a value of 0 + // when the SP bus goes into RESET, we would rip through the list setting initialized to false and + // setting device id's to 0. Then on each INIT command, we iterate through the list, setting + // initialized to true and assigning device numbers as assigned by the smartport controller in the A2. + // so I need "reset()" and "initialize()" functions. + + // todo: I need a way to internally keep track of what kind of device each one is. I'm thinking an + // enumerated class type might work well here. It can be expanded as needed and an extra case added + // below. I can also make this a switch case structure to ensure each case of the class is handled. + + // assign dedicated pointers to certain devices + switch (deviceType) + { + case iwm_fujinet_type_t::BlockDisk: + break; + case iwm_fujinet_type_t::FujiNet: + _fujiDev = (iwmFuji *)pDevice; + break; + case iwm_fujinet_type_t::Modem: + _modemDev = (iwmModem *)pDevice; + break; + case iwm_fujinet_type_t::Network: + // todo: work out how to assign different network devices - idea: + // include a number in the DIB name, e.g., "NETWORK 1" + // and extract that number from the DIB and use it as the index + //_netDev[device_id - SIO_DEVICEID_FN_NETWORK] = (iwmNetwork *)pDevice; + break; + case iwm_fujinet_type_t::CPM: + _cpmDev = (iwmCPM *)pDevice; + break; + case iwm_fujinet_type_t::Printer: + _printerdev = (iwmPrinter *)pDevice; + break; + case iwm_fujinet_type_t::Voice: + // not yet implemented: todo - take SAM and implement as a special block device. Also then available for disk rotate annunciation. + break; + case iwm_fujinet_type_t::Clock: + _clockDev = (iwmClock *)pDevice; + break; + case iwm_fujinet_type_t::Other: + break; + } + + pDevice->_devnum = 0; + pDevice->_initialized = false; + + _daisyChain.push_front(pDevice); +} + +// Removes device from the SIO bus. +// Note that the destructor is called on the device! +void iwmBus::remDevice(iwmDevice *p) +{ + _daisyChain.remove(p); +} + +// Should avoid using this as it requires counting through the list +int iwmBus::numDevices() +{ + int i = 0; + __BEGIN_IGNORE_UNUSEDVARS + for (auto devicep : _daisyChain) + i++; + return i; + __END_IGNORE_UNUSEDVARS +} + +void iwmBus::changeDeviceId(iwmDevice *p, int device_id) +{ + for (auto devicep : _daisyChain) + { + if (devicep == p) + devicep->_devnum = device_id; + } +} + +iwmDevice *iwmBus::deviceById(int device_id) +{ + for (auto devicep : _daisyChain) + { + if (devicep->_devnum == device_id) + return devicep; + } + return nullptr; +} + +void iwmBus::enableDevice(uint8_t device_id) +{ + iwmDevice *p = deviceById(device_id); + p->device_active = true; +} + +void iwmBus::disableDevice(uint8_t device_id) +{ + iwmDevice *p = deviceById(device_id); + p->device_active = false; +} + +// Give devices an opportunity to clean up before a reboot +void iwmBus::shutdown() +{ + shuttingDown = true; + + for (auto devicep : _daisyChain) + { + Debug_printf("Shutting down device %02x\n", (unsigned)devicep->id()); + devicep->shutdown(); + } + Debug_printf("All devices shut down.\n"); +} + +iwmBus IWM; // global smartport bus variable + +#endif /* BUILD_APPLE */ diff --git a/lib/bus/mac/mac.h b/lib/bus/mac/mac.h new file mode 100644 index 000000000..6fb409bad --- /dev/null +++ b/lib/bus/mac/mac.h @@ -0,0 +1,489 @@ +#ifdef BUILD_MAC +#ifndef MAC_H +#define MAC_H + +#include "bus.h" +#include +#include +#include + +#include "../../include/debug.h" +#include "fnFS.h" + +// class def'ns +class macBus; // forward declare for macDevice +class macFuji; // declare here so can reference it, but define in fuji.h + +typedef char mac_cmd_t; + +// Sorry, this is the protocol adapter's fault. -Thom +union cmdFrame_t +{ + struct + { + uint8_t device; + uint8_t comnd; + uint8_t aux1; + uint8_t aux2; + uint8_t cksum; + }; + struct + { + uint32_t commanddata; + uint8_t checksum; + } __attribute__((packed)); +}; + +enum class mac_fujinet_type_t +{ + FujiNet, + Floppy, + HardDisk, + Modem, + Network, + CPM, + Printer, + Voice, + Clock, + Other +}; + +class macDevice +{ + friend macBus; + +protected: + uint8_t _devnum; // assigned by Apple II during INIT + bool _initialized; + +public: + bool device_active; + + virtual void shutdown() = 0; + virtual void process(mac_cmd_t cmd) = 0; + + uint8_t id() { return _devnum; }; +}; + +class macBus +{ +private: + macDevice *_activeDev = nullptr; + + macFuji *_fujiDev = nullptr; + + // iwmModem *_modemDev = nullptr; + // iwmNetwork *_netDev[4] = {nullptr}; + // // sioMIDIMaze *_midiDev = nullptr; + // // sioCassette *_cassetteDev = nullptr; + // iwmCPM *_cpmDev = nullptr; + // iwmPrinter *_printerdev = nullptr; + // iwmClock *_clockDev = nullptr; + + const int _mac_baud_rate = 115200; + +public: + std::forward_list _daisyChain; + + void setup(); + void service(); + void shutdown(); + + int numDevices(); + void addDevice(macDevice *pDevice, mac_fujinet_type_t deviceType); // todo: probably get called by handle_init() + void remDevice(macDevice *pDevice); + macDevice *deviceById(int device_id); + macDevice *firstDev() { return _daisyChain.front(); } + // uint8_t *devBuffer() { return (uint8_t *)iwmDevice::data_buffer; } + void enableDevice(uint8_t device_id); + void disableDevice(uint8_t device_id); + void changeDeviceId(macDevice *p, int device_id); + // iwmPrinter *getPrinter() { return _printerdev; } + bool shuttingDown = false; // TRUE if we are in shutdown process + bool getShuttingDown() { return shuttingDown; }; + // bool en35Host = false; // TRUE if we are connected to a host that supports the /EN35 signal +}; + +extern macBus MAC; + +#endif // guard +#endif // BUILD_MAC + +#if 0 +#ifndef IWM_H +#define IWM_H + +#include "../../include/debug.h" + +#include "bus.h" +#include "iwm_ll.h" + +#include +#include +#include + +#include "fnFS.h" + +// see page 81-82 in Apple IIc ROM reference and Table 7-5 in IIgs firmware ref +#define SP_ERR_NOERROR 0x00 // no error +#define SP_ERR_BADCMD 0x01 // invalid command +#define SP_ERR_BUSERR 0x06 // communications error +#define SP_ERR_BADCTL 0x21 // invalid status or control code +#define SP_ERR_BADCTLPARM 0x22 // invalid parameter list +#define SP_ERR_IOERROR 0x27 // i/o error on device side +#define SP_ERR_NODRIVE 0x28 // no device connected +#define SP_ERR_NOWRITE 0x2b // disk write protected +#define SP_ERR_BADBLOCK 0x2d // invalid block number +#define SP_ERR_DISKSW 0x2e // media has been swapped - extended calls only +#define SP_ERR_OFFLINE 0x2f // device offline or no disk in drive +// $30-$3F are for device specific errors +#define SP_ERR_BADWIFI 0x30 // error connecting to new SSID - todo: implement usage + +#define STATCODE_BLOCK_DEVICE 0x01 << 7 // block device = 1, char device = 0 +#define STATCODE_WRITE_ALLOWED 0x01 << 6 +#define STATCODE_READ_ALLOWED 0x01 << 5 +#define STATCODE_DEVICE_ONLINE 0x01 << 4 // or disk in drive +#define STATCODE_FORMAT_ALLOWED 0x01 << 3 +#define STATCODE_WRITE_PROTECT 0x01 << 2 // block devices only +#define STATCODE_INTERRUPTING 0x01 << 1 // apple IIc only +#define STATCODE_DEVICE_OPEN 0x01 << 0 // char devices only +#define STATCODE_DISK_SWITCHED 0x01 << 0 // disk switched status for block devices, same bit as device open for char devices + +// valid types and subtypes for block devices per smartport documentation +#define SP_TYPE_BYTE_35DISK 0x01 +#define SP_SUBTYPE_BYTE_UNI35 0x00 +#define SP_SUBTYPE_BYTE_APPLE35 0xC0 + +#define SP_TYPE_BYTE_HARDDISK 0x02 +#define SP_SUBTYPE_BYTE_REMOVABLE 0x00 +#define SP_SUBTYPE_BYTE_HARDDISK 0x20 // fixed media +#define SP_SUBTYPE_BYTE_SWITCHED 0x40 // removable and supports disk switched errors +#define SP_SUBTYPE_BYTE_HARDDISK_EXTENDED 0xA0 +#define SP_SUBTYPE_BYTE_REMOVABLE_EXTENDED 0xC0 // removable and extended and supports disk switched errors + +#define SP_TYPE_BYTE_SCSI 0x03 +#define SP_SUBTYPE_BYTE_SCSI_REMOVABLE 0xC0 // removable and extended and supports disk switched errors + +#define SP_TYPE_BYTE_FUJINET 0x10 +#define SP_TYPE_BYTE_FUJINET_NETWORK 0x11 +#define SP_TYPE_BYTE_FUJINET_CPM 0x12 +#define SP_TYPE_BYTE_FUJINET_CLOCK 0x13 +#define SP_TYPE_BYTE_FUJINET_PRINTER 0x14 +#define SP_TYPE_BYTE_FUJINET_MODEM 0x15 + +#define SP_SUBTYPE_BYTE_FUJINET 0x00 +#define SP_SUBTYPE_BYTE_FUJINET_NETWORK 0x00 +#define SP_SUBTYPE_BYTE_FUJINET_CPM 0x00 +#define SP_SUBTYPE_BYTE_FUJINET_CLOCK 0x00 +#define SP_SUBTYPE_BYTE_FUJINET_PRINTER 0x00 +#define SP_SUBTYPE_BYTE_FUJINET_MODEM 0x00 + +#define IWM_CTRL_RESET 0x00 +#define IWM_CTRL_SET_DCB 0x01 +#define IWM_CTRL_SET_NEWLINE 0x02 +#define IWM_CTRL_SERVICE_INT 0x03 +#define IWM_CTRL_EJECT_DISK 0x04 +#define IWM_CTRL_RUN_ROUTINE 0x05 +#define IWM_CTRL_DWNLD_ADDRESS 0x06 +#define IWM_CTRL_DOWNLOAD 0x07 + +#define IWM_STATUS_STATUS 0x00 +#define IWM_STATUS_DCB 0x01 +#define IWM_STATUS_NEWLINE 0x02 +#define IWM_STATUS_DIB 0x03 +#define IWM_STATUS_UNI35 0x05 + +// class def'ns +class iwmFuji; // declare here so can reference it, but define in fuji.h +class iwmModem; // declare here so can reference it, but define in modem.h +class iwmNetwork; // declare here so can reference it, but define in network.h +class iwmPrinter; // Printer device +class iwmDisk; // disk device cause I need to use "iwmDisk smort" for prototyping in iwmBus::service() +class iwmCPM; // CPM Virtual Device +class iwmClock; // Real Time Clock Device +class iwmBus; // forward declare bus so can be friend + +// Sorry, this is the protocol adapter's fault. -Thom +union cmdFrame_t +{ + struct + { + uint8_t device; + uint8_t comnd; + uint8_t aux1; + uint8_t aux2; + uint8_t cksum; + }; + struct + { + uint32_t commanddata; + uint8_t checksum; + } __attribute__((packed)); +}; + +#define COMMAND_PACKET_LEN 27 // 28 - max length changes suggested by robj +#define BLOCK_DATA_LEN 512 +#define MAX_DATA_LEN 767 +#define MAX_SP_PACKET_LEN 891 +// to do - make block packet compatible up to 767 data bytes? + +union cmdPacket_t +{ + /* + C3 PBEGIN MARKS BEGINNING OF PACKET 32 micro Sec. + 81 DEST DESTINATION UNIT NUMBER 32 micro Sec. + 80 SRC SOURCE UNIT NUMBER 32 micro Sec. + 80 TYPE PACKET TYPE FIELD 32 micro Sec. + 80 AUX PACKET AUXILLIARY TYPE FIELD 32 micro Sec. + 80 STAT DATA STATUS FIELD 32 micro Sec. + 82 ODDCNT ODD BYTES COUNT 32 micro Sec. + 81 GRP7CNT GROUP OF 7 BYTES COUNT 32 micro Sec. + 80 ODDMSB ODD BYTES MSB's 32 micro Sec. + 81 COMMAND 1ST ODD BYTE = Command Byte 32 micro Sec. + 83 PARMCNT 2ND ODD BYTE = Parameter Count 32 micro Sec. + 80 GRP7MSB MSB's FOR 1ST GROUP OF 7 32 micro Sec. + 80 G7BYTE1 BYTE 1 FOR 1ST GROUP OF 7 32 micro Sec. + 98 G7BYTE2 BYTE 2 FOR 1ST GROUP OF 7 32 micro Sec. + 82 G7BYTE3 BYTE 3 FOR 1ST GROUP OF 7 32 micro Sec. + 80 G7BYTE4 BYTE 4 FOR 1ST GROUP OF 7 32 micro Sec. + 80 G7BYTE5 BYTE 5 FOR 1ST GROUP OF 7 32 micro Sec. + 80 G7BYTE5 BYTE 6 FOR 1ST GROUP OF 7 32 micro Sec. + 80 G7BYTE6 BYTE 7 FOR 1ST GROUP OF 7 32 micro Sec. + BB CHKSUM1 1ST BYTE OF CHECKSUM 32 micro Sec. + EE CHKSUM2 2ND BYTE OF CHECKSUM 32 micro Sec. + C8 PEND PACKET END BYTE 32 micro Sec. + 00 CLEAR zero after packet for FujiNet use + */ + struct + { + uint8_t sync1; // 0 + uint8_t sync2; // 1 + uint8_t sync3; // 2 + uint8_t sync4; // 3 + uint8_t sync5; // 4 + uint8_t pbegin; // 5 + uint8_t dest; // 6 + uint8_t source; // 7 + uint8_t type; // 8 + uint8_t aux; // 9 + uint8_t stat; // 10 + uint8_t oddcnt; // 11 + uint8_t grp7cnt; // 12 + uint8_t oddmsb; // 13 + uint8_t command; // 14 + uint8_t parmcnt; // 15 + uint8_t grp7msb; // 16 + uint8_t g7byte1; // 17 + uint8_t g7byte2; // 18 + uint8_t g7byte3; // 19 + uint8_t g7byte4; // 20 + uint8_t g7byte5; // 21 + uint8_t g7byte6; // 22 + uint8_t g7byte7; // 23 + uint8_t chksum1; // 24 + uint8_t chksum2; // 25 + uint8_t pend; // 26 + uint8_t clear; // 27 + }; + uint8_t data[COMMAND_PACKET_LEN + 1]; +}; + +union iwm_decoded_cmd_t +{ + struct + { + uint8_t command; + uint8_t count; + uint8_t params[7]; + }; + uint8_t decoded[9]; +}; + +enum class iwm_smartport_type_t +{ + Block_Device, + Character_Device +}; + +enum class iwm_fujinet_type_t +{ + BlockDisk, + FujiNet, + Modem, + Network, + CPM, + Printer, + Voice, + Clock, + Other +}; + +enum class iwm_enable_state_t +{ + off, + off2on, + on, + on2off +}; + +struct iwm_device_info_block_t +{ + uint8_t stat_code; // byte with 8 flags indicating device status + std::string device_name; // limited to 16 chars std ascii (<128), no zero terminator + uint8_t device_type; + uint8_t device_subtype; + uint8_t firmware_rev; +}; + +// #ifdef DEBUG +void print_packet(uint8_t *data, int bytes); +void print_packet(uint8_t *data); +// #endif + +class iwmDevice +{ + friend iwmBus; // put here for prototype, not sure if will need to keep it + +protected: + // set these things in constructor or initializer? + iwm_smartport_type_t device_type; + iwm_fujinet_type_t internal_type; + iwm_device_info_block_t dib; // device information block + uint8_t _devnum; // assigned by Apple II during INIT + bool _initialized; + + // void send_data_packet(); //encode smartport 512 byte data packet + // void encode_data_packet(uint16_t num = 512); //encode smartport "num" byte data packet + void send_init_reply_packet(uint8_t source, uint8_t status); + virtual void send_status_reply_packet() = 0; + void send_reply_packet(uint8_t status); + // void send_reply_packet(uint8_t source, uint8_t status) { send_reply_packet(status); }; + virtual void send_status_dib_reply_packet() = 0; + + virtual void send_extended_status_reply_packet() = 0; + virtual void send_extended_status_dib_reply_packet() = 0; + + virtual void shutdown() = 0; + virtual void process(iwm_decoded_cmd_t cmd) = 0; + + // these are good for the high level device + virtual void iwm_status(iwm_decoded_cmd_t cmd); + virtual void iwm_readblock(iwm_decoded_cmd_t cmd){}; + virtual void iwm_writeblock(iwm_decoded_cmd_t cmd){}; + // virtual void iwm_handle_eject(iwm_decoded_cmd_t cmd) {}; + virtual void iwm_format(iwm_decoded_cmd_t cmd){}; + virtual void iwm_ctrl(iwm_decoded_cmd_t cmd){}; + virtual void iwm_open(iwm_decoded_cmd_t cmd){}; + virtual void iwm_close(iwm_decoded_cmd_t cmd){}; + virtual void iwm_read(iwm_decoded_cmd_t cmd){}; + virtual void iwm_write(iwm_decoded_cmd_t cmd){}; + + uint8_t get_status_code(iwm_decoded_cmd_t cmd) { return cmd.params[2]; } + uint16_t get_numbytes(iwm_decoded_cmd_t cmd) { return cmd.params[2] + (cmd.params[3] << 8); }; + uint32_t get_address(iwm_decoded_cmd_t cmd) { return cmd.params[4] + (cmd.params[5] << 8) + (cmd.params[6] << 16); } + + void iwm_return_badcmd(iwm_decoded_cmd_t cmd); + void iwm_return_ioerror(); + void iwm_return_noerror(); + + // iwm packet handling + static uint8_t data_buffer[MAX_DATA_LEN]; // un-encoded binary data (512 bytes for a block) + static int data_len; // how many bytes in the data buffer + +public: + bool device_active; + uint8_t prevtype = SP_TYPE_BYTE_HARDDISK; // preserve previous device type when offline + bool switched = false; // indicate disk switched condition + bool readonly = true; // write protected + bool is_config_device; + /** + * @brief get the IWM device Number (1-255) + * @return The device number registered for this device + */ + void set_id(uint8_t dn) { _devnum = dn; }; + int id() { return _devnum; }; + // void assign_id(uint8_t n) { _devnum = n; }; + + void assign_name(std::string name) { dib.device_name = name; } + + /** + * @brief Get the iwmBus object that this iwmDevice is attached to. + */ + iwmBus iwm_get_bus(); +}; + +class iwmBus +{ +private: + iwmDevice *_activeDev = nullptr; + + iwmFuji *_fujiDev = nullptr; + iwmModem *_modemDev = nullptr; + iwmNetwork *_netDev[4] = {nullptr}; + // sioMIDIMaze *_midiDev = nullptr; + // sioCassette *_cassetteDev = nullptr; + iwmCPM *_cpmDev = nullptr; + iwmPrinter *_printerdev = nullptr; + iwmClock *_clockDev = nullptr; + + bool iwm_phase_val(uint8_t p); + + enum class iwm_phases_t + { + idle = 0, + reset, + enable + }; + iwm_phases_t iwm_phases(); +#ifdef DEBUG + iwm_phases_t oldphase; +#endif + + iwm_enable_state_t iwm_drive_enabled(); + iwm_enable_state_t _old_enable_state; + iwm_enable_state_t _new_enable_state; + // uint8_t enable_values; + + void iwm_ack_deassert(); + void iwm_ack_assert(); + bool iwm_req_deassert_timeout(int t) { return smartport.req_wait_for_falling_timeout(t); }; + bool iwm_req_assert_timeout(int t) { return smartport.req_wait_for_rising_timeout(t); }; + + iwm_decoded_cmd_t command; + + void handle_init(); + + int old_track = -1; + int new_track; + +public: + std::forward_list _daisyChain; + + cmdPacket_t command_packet; + bool iwm_decode_data_packet(uint8_t *a, int &n); + int iwm_send_packet(uint8_t source, iwm_packet_type_t packet_type, uint8_t status, const uint8_t *data, uint16_t num); + + // these things stay for the most part + void setup(); + void service(); + void shutdown(); + + int numDevices(); + void addDevice(iwmDevice *pDevice, iwm_fujinet_type_t deviceType); // todo: probably get called by handle_init() + void remDevice(iwmDevice *pDevice); + iwmDevice *deviceById(int device_id); + iwmDevice *firstDev() { return _daisyChain.front(); } + uint8_t *devBuffer() { return (uint8_t *)iwmDevice::data_buffer; } + void enableDevice(uint8_t device_id); + void disableDevice(uint8_t device_id); + void changeDeviceId(iwmDevice *p, int device_id); + iwmPrinter *getPrinter() { return _printerdev; } + bool shuttingDown = false; // TRUE if we are in shutdown process + bool getShuttingDown() { return shuttingDown; }; + bool en35Host = false; // TRUE if we are connected to a host that supports the /EN35 signal +}; + +extern iwmBus IWM; + +#endif // guard +#endif /* BUILD_APPLE */ \ No newline at end of file diff --git a/lib/device/device.h b/lib/device/device.h index ab9420cd1..9713ee14c 100644 --- a/lib/device/device.h +++ b/lib/device/device.h @@ -102,13 +102,22 @@ iwmModem *sioR; #endif +#ifdef BUILD_MAC +#include "mac/floppy.h" +#include "mac/fuji.h" +#include "mac/modem.h" +#include "mac/printer.h" +#include "mac/printerlist.h" + macModem *sioR; +#endif + #ifdef BUILD_S100 -# include "s100spi/disk.h" -# include "s100spi/network.h" -# include "s100spi/modem.h" -# include "s100spi/printer.h" -# include "s100spi/printerlist.h" -# include "s100spi/fuji.h" +#include "s100spi/disk.h" +#include "s100spi/network.h" +#include "s100spi/modem.h" +#include "s100spi/printer.h" +#include "s100spi/printerlist.h" +#include "s100spi/fuji.h" s100spiModem *sioR; #endif diff --git a/lib/device/disk.h b/lib/device/disk.h index 732068183..3adb1b492 100644 --- a/lib/device/disk.h +++ b/lib/device/disk.h @@ -16,6 +16,11 @@ #define DEVICE_TYPE iwmDisk #endif +#ifdef BUILD_MAC +#include "mac/floppy.h" +#define DEVICE_TYPE macFloppy +#endif + #ifdef BUILD_IEC # include "iec/disk.h" # define DEVICE_TYPE iecDisk diff --git a/lib/device/fuji.h b/lib/device/fuji.h index 054171518..813f09f1a 100644 --- a/lib/device/fuji.h +++ b/lib/device/fuji.h @@ -33,6 +33,10 @@ # include "iwm/fuji.h" #endif +#ifdef BUILD_MAC +# include "mac/fuji.h" +#endif + #ifdef BUILD_CX16 #include "cx16_i2c/fuji.h" #endif diff --git a/lib/device/mac/floppy.cpp b/lib/device/mac/floppy.cpp new file mode 100644 index 000000000..5e52390d8 --- /dev/null +++ b/lib/device/mac/floppy.cpp @@ -0,0 +1,197 @@ +#ifdef BUILD_MAC +#include "floppy.h" + +mediatype_t macFloppy::mount(FILE *f, mediatype_t disk_type) //, const char *filename), uint32_t disksize, mediatype_t disk_type) +{ + + mediatype_t mt = MEDIATYPE_UNKNOWN; + // mediatype_t disk_type = MEDIATYPE_WOZ; + + // Debug_printf("disk MOUNT %s\n", filename); + + // Destroy any existing MediaType + if (_disk != nullptr) + { + delete _disk; + _disk = nullptr; + } + + switch (disk_type) + { + case MEDIATYPE_MOOF: + Debug_printf("\nMounting Media Type WOZ"); + device_active = true; + _disk = new MediaTypeMOOF(); + mt = ((MediaTypeMOOF *)_disk)->mount(f); + change_track(0); // initialize spi buffer + break; + // case MEDIATYPE_DSK: + // Debug_printf("\nMounting Media Type DSK"); + // device_active = true; + // _disk = new MediaTypeDSK(); + // mt = ((MediaTypeDSK *)_disk)->mount(f); + // change_track(0); // initialize spi buffer + // break; + default: + Debug_printf("\nMedia Type UNKNOWN - no mount in disk2.cpp"); + device_active = false; + break; + } + + return mt; +} + +void macFloppy::unmount() +{ +} + + +#endif // BUILD_MAC + +#if 0 +#include "disk2.h" + +#include "fnSystem.h" +#include "fuji.h" +#include "fnHardwareTimer.h" + +#define NS_PER_BIT_TIME 125 +#define BLANK_TRACK_LEN 6400 + +const int8_t phase2seq[16] = {-1, 0, 2, 1, 4, -1, 3, -1, 6, 7, -1, -1, 5, -1, -1, -1}; +const int8_t seq2steps[8] = {0, 1, 2, 3, 0, -3, -2, -1}; + +iwmDisk2::~iwmDisk2() +{ +} + +void iwmDisk2::shutdown() +{ +} + +iwmDisk2::iwmDisk2() +{ + track_pos = 80; + old_pos = 0; + oldphases = 0; + Debug_printf("\nNew Disk ][ object"); + device_active = false; +} + +void iwmDisk2::init() +{ + track_pos = 80; + old_pos = 0; + oldphases = 0; + device_active = false; +} + +mediatype_t iwmDisk2::mount(FILE *f, mediatype_t disk_type) //, const char *filename), uint32_t disksize, mediatype_t disk_type) +{ + + mediatype_t mt = MEDIATYPE_UNKNOWN; + // mediatype_t disk_type = MEDIATYPE_WOZ; + + // Debug_printf("disk MOUNT %s\n", filename); + + // Destroy any existing MediaType + if (_disk != nullptr) + { + delete _disk; + _disk = nullptr; + } + + switch (disk_type) + { + case MEDIATYPE_WOZ: + Debug_printf("\nMounting Media Type WOZ"); + device_active = true; + _disk = new MediaTypeWOZ(); + mt = ((MediaTypeWOZ *)_disk)->mount(f); + change_track(0); // initialize spi buffer + break; + case MEDIATYPE_DSK: + Debug_printf("\nMounting Media Type DSK"); + device_active = true; + _disk = new MediaTypeDSK(); + mt = ((MediaTypeDSK *)_disk)->mount(f); + change_track(0); // initialize spi buffer + break; + default: + Debug_printf("\nMedia Type UNKNOWN - no mount in disk2.cpp"); + device_active = false; + break; + } + + return mt; +} + +void iwmDisk2::unmount() +{ +} + +bool iwmDisk2::write_blank(FILE *f, uint16_t sectorSize, uint16_t numSectors) +{ + return false; +} + +bool IRAM_ATTR iwmDisk2::phases_valid(uint8_t phases) +{ + return (phase2seq[phases] != -1); +} + +bool IRAM_ATTR iwmDisk2::move_head() +{ + int delta = 0; + uint8_t newphases = smartport.iwm_phase_vector(); // could access through IWM instead + if (phases_valid(newphases)) + { + int idx = (phase2seq[newphases] - phase2seq[oldphases] + 8) % 8; + delta = seq2steps[idx]; + + // phases_lut[oldphases][newphases]; + old_pos = track_pos; + track_pos += delta; + if (track_pos < 0) + { + track_pos = 0; + } + else if (track_pos > MAX_TRACKS - 1) + { + track_pos = MAX_TRACKS - 1; + } + oldphases = newphases; + } + return (delta != 0); +} + +void IRAM_ATTR iwmDisk2::change_track(int indicator) +{ + if (!device_active) + return; + + if (old_pos == track_pos) + return; + + // should only copy track data over if it's changed + if (((MediaTypeWOZ *)_disk)->trackmap(old_pos) == ((MediaTypeWOZ *)_disk)->trackmap(track_pos)) + return; + + // need to tell diskii_xface the number of bits in the track + // and where the track data is located so it can convert it + if (((MediaTypeWOZ *)_disk)->trackmap(track_pos) != 255) + diskii_xface.copy_track( + ((MediaTypeWOZ *)_disk)->get_track(track_pos), + ((MediaTypeWOZ *)_disk)->track_len(track_pos), + ((MediaTypeWOZ *)_disk)->num_bits(track_pos), + NS_PER_BIT_TIME * ((MediaTypeWOZ *)_disk)->optimal_bit_timing); + else + diskii_xface.copy_track( + nullptr, + BLANK_TRACK_LEN, + BLANK_TRACK_LEN * 8, + NS_PER_BIT_TIME * ((MediaTypeWOZ *)_disk)->optimal_bit_timing); + // Since the empty track has no data, and therefore no length, using a fake length of 51,200 bits (6400 bytes) works very well. +} + +#endif /* BUILD_APPLE */ \ No newline at end of file diff --git a/lib/device/mac/floppy.h b/lib/device/mac/floppy.h new file mode 100644 index 000000000..75c8fe5c5 --- /dev/null +++ b/lib/device/mac/floppy.h @@ -0,0 +1,103 @@ +#ifdef BUILD_MAC +#ifndef FLOPPY_H +#define FLOPPY_H + +#include "bus.h" +#include "../media/media.h" + +class macFloppy : public macDevice +{ + +protected: + MediaType *_disk = nullptr; + + // unused because not a smartport device + // void send_status_reply_packet() override {}; + // void send_extended_status_reply_packet() override {}; + // void send_status_dib_reply_packet() override {}; + // void send_extended_status_dib_reply_packet() override {}; + // void process(iwm_decoded_cmd_t cmd) override {}; + // void iwm_readblock(iwm_decoded_cmd_t cmd) override {}; + // void iwm_writeblock(iwm_decoded_cmd_t cmd) override {}; + + + char disk_num; + bool enabled; + int track_pos; + int old_pos; + uint8_t oldphases; + +public: + macFloppy() {}; + ~macFloppy() {}; + + void init() {}; + mediatype_t mount(FILE *f, mediatype_t disk_type = MEDIATYPE_UNKNOWN); + mediatype_t mount(FILE *f, const char *filename, uint32_t disksize, mediatype_t disk_type = MEDIATYPE_UNKNOWN) { return mount(f, disk_type); }; + void unmount(); + bool write_blank(FILE *f, uint16_t sectorSize, uint16_t numSectors) { return false; }; + int get_track_pos() { return track_pos; }; + // bool phases_valid(uint8_t phases); + bool move_head() { return false; }; + void change_track(int indicator) {}; + + // void set_disk_number(char c) { disk_num = c; } + // char get_disk_number() { return disk_num; }; + mediatype_t disktype() { return _disk == nullptr ? MEDIATYPE_UNKNOWN : _disk->_mediatype; }; + + void shutdown() override {}; + void process(mac_cmd_t cmd) override {}; +}; + +#endif // guard +#endif // BUILD_MAC + +#if 0 +#ifndef DISK2_H +#define DISK2_H + +#include "bus.h" +#include "../media/media.h" + +class iwmDisk2 : public iwmDevice +{ + +protected: + MediaType *_disk = nullptr; + + // unused because not a smartport device + void send_status_reply_packet() override{}; + void send_extended_status_reply_packet() override{}; + void send_status_dib_reply_packet() override{}; + void send_extended_status_dib_reply_packet() override{}; + void process(iwm_decoded_cmd_t cmd) override{}; + void iwm_readblock(iwm_decoded_cmd_t cmd) override{}; + void iwm_writeblock(iwm_decoded_cmd_t cmd) override{}; + + void shutdown() override; + char disk_num; + bool enabled; + int track_pos; + int old_pos; + uint8_t oldphases; + +public: + iwmDisk2(); + void init(); + mediatype_t mount(FILE *f, mediatype_t disk_type = MEDIATYPE_UNKNOWN); + void unmount(); + bool write_blank(FILE *f, uint16_t sectorSize, uint16_t numSectors); + int get_track_pos() { return track_pos; }; + bool phases_valid(uint8_t phases); + bool move_head(); + void change_track(int indicator); + + // void set_disk_number(char c) { disk_num = c; } + // char get_disk_number() { return disk_num; }; + mediatype_t disktype() { return _disk == nullptr ? MEDIATYPE_UNKNOWN : _disk->_mediatype; }; + + ~iwmDisk2(); +}; + +#endif +#endif /* BUILD_APPLE */ \ No newline at end of file diff --git a/lib/device/mac/fuji.cpp b/lib/device/mac/fuji.cpp new file mode 100644 index 000000000..c8b5ab0fe --- /dev/null +++ b/lib/device/mac/fuji.cpp @@ -0,0 +1,1550 @@ +#ifdef BUILD_MAC + +#include "fuji.h" + +#include "fnSystem.h" +#include "fnConfig.h" +#include "led.h" +#include "fnWiFi.h" +#include "fnFsSPIFFS.h" +#include "utils.h" + +#include + +#define ADDITIONAL_DETAILS_BYTES 12 +#define DIR_MAX_LEN 40 + +macFuji theFuji; // Global fuji object. + +macFuji::macFuji() +{ + Debug_printf("Announcing the MacFuji!!!\n"); + // Helpful for debugging + for (int i = 0; i < MAX_HOSTS; i++) + _fnHosts[i].slotid = i; +} + +macFloppy *macFuji::bootdisk() +{ + return _bootDisk; +} + +// Initializes base settings and adds our devices to the SIO bus +void macFuji::setup(macBus *macbus) +{ + // set up Fuji device + _mac_bus = macbus; + + _populate_slots_from_config(); + + // Disable booting from CONFIG if our settings say to turn it off + boot_config = false; // to do - understand? + + // Disable status_wait if our settings say to turn it off + status_wait_enabled = false; // to do - understand? + + // theNetwork = new iwmNetwork(); + // _iwm_bus->addDevice(theNetwork,iwm_fujinet_type_t::Network); + + // theClock = new iwmClock(); + // _iwm_bus->addDevice(theClock, iwm_fujinet_type_t::Clock); + + // theCPM = new iwmCPM(); + // _iwm_bus->addDevice(theCPM, iwm_fujinet_type_t::CPM); + + // for (int i = MAX_DISK_DEVICES - MAX_DISK2_DEVICES -1; i >= 0; i--) + // { + // _fnDisks[i].disk_dev.set_disk_number('0' + i); + // _iwm_bus->addDevice(&_fnDisks[i].disk_dev, iwm_fujinet_type_t::BlockDisk); + // } + + // Debug_printf("\nConfig General Boot Mode: %u\n",Config.get_general_boot_mode()); + // if (Config.get_general_boot_mode() == 0) + // { + FILE *f = fsFlash.file_open("/autorun.moof"); + _fnDisks[0].disk_dev.mount(f, "/autorun.moof", MEDIATYPE_MOOF); + // } + // else + // { + // FILE *f = fnSPIFFS.file_open("/mount-and-boot.po"); + // _fnDisks[0].disk_dev.mount(f, "/mount-and-boot.po", 512*256, MEDIATYPE_PO); + // } + + // theNetwork = new adamNetwork(); + // theSerial = new adamSerial(); + // _iwm_bus->addDevice(theNetwork, 0x09); // temporary. + // _iwm_bus->addDevice(theSerial, 0x0e); // Serial port + // _iwm_bus->addDevice(&theFuji, 0x0F); // Fuji becomes the gateway device. + + // Add our devices to the AdamNet bus + // for (int i = 0; i < 4; i++) + // _adamnet_bus->addDevice(&_fnDisks[i].disk_dev, ADAMNET_DEVICEID_DISK + i); + + // for (int i = 0; i < MAX_NETWORK_DEVICES; i++) + // _adamnet_bus->addDevice(&sioNetDevs[i], ADAMNET_DEVICEID_FN_NETWORK + i); +} + +#endif // BUILD_MAC + +#if 0 +#include "fuji.h" + +#include "fnSystem.h" +#include "fnConfig.h" +#include "led.h" +#include "fnWiFi.h" +#include "fnFsSPIFFS.h" +#include "utils.h" + +#include + +#define ADDITIONAL_DETAILS_BYTES 12 +#define DIR_MAX_LEN 40 + +iwmFuji theFuji; // Global fuji object. + +iwmFuji::iwmFuji() +{ + Debug_printf("Announcing the iwmFuji::iwmFuji()!!!\n"); + // Helpful for debugging + for (int i = 0; i < MAX_HOSTS; i++) + _fnHosts[i].slotid = i; +} + +void iwmFuji::iwm_dummy_command() // SP CTRL command +{ + Debug_printf("\r\nData Received: "); + for (int i=0; i sizeof(cfg.ssid) ? sizeof(cfg.ssid) : s.length()); + Debug_printf("\r\nReturning SSID: %s",cfg.ssid); + + s = Config.get_wifi_passphrase(); + memcpy(cfg.password, s.c_str(), + s.length() > sizeof(cfg.password) ? sizeof(cfg.password) : s.length()); + + // Move into response. + memcpy(data_buffer, &cfg, sizeof(cfg)); + data_len = sizeof(cfg); +} // 0xFE + +void iwmFuji::iwm_stat_net_scan_networks() // SP STATUS command +{ + Debug_printf("\r\nFuji cmd: SCAN NETWORKS"); + + isReady = false; + + //if (scanStarted == false) + //{ + _countScannedSSIDs = fnWiFi.scan_networks(); + // scanStarted = true; + setSSIDStarted = false; + //} + + isReady = true; + + data_buffer[0] = _countScannedSSIDs; + data_len = 1; +} // 0xFD + +void iwmFuji::iwm_ctrl_net_scan_result() //SP STATUS command +{ + Debug_print("\r\nFuji cmd: GET SCAN RESULT"); + //scanStarted = false; + + uint8_t n = data_buffer[0]; + + memset(&detail, 0, sizeof(detail)); + + if (n < _countScannedSSIDs) + fnWiFi.get_scan_result(n, detail.ssid, &detail.rssi); + + Debug_printf("SSID: %s - RSSI: %u\n", detail.ssid, detail.rssi); +} // 0xFC + +void iwmFuji::iwm_stat_net_scan_result() //SP STATUS command +{ + Debug_printf("SSID: %s - RSSI: %u\n", detail.ssid, detail.rssi); + + memset(data_buffer, 0, sizeof(data_buffer)); + memcpy(data_buffer, &detail, sizeof(detail)); + data_len = sizeof(detail); +} // 0xFC + +void iwmFuji::iwm_ctrl_net_set_ssid() // SP CTRL command +{ + Debug_printf("\r\nFuji cmd: SET SSID"); + // if (!fnWiFi.connected() && setSSIDStarted == false) + // { + + // uint16_t s = data_len; + // s--; + + // Data for FUJICMD_SET_SSID + struct + { + char ssid[MAX_SSID_LEN + 1]; + char password[MAX_WIFI_PASS_LEN]; + } cfg; + + // to do - copy data over to cfg + memcpy(cfg.ssid, &data_buffer, sizeof(cfg.ssid)); + memcpy(cfg.password, &data_buffer[sizeof(cfg.ssid)], sizeof(cfg.password)); + // adamnet_recv_buffer((uint8_t *)&cfg, s); + + bool save = false; // for now don't save - to do save if connection was succesful + + Debug_printf("\r\nConnecting to net: %s password: %s\n", cfg.ssid, cfg.password); + + if (fnWiFi.connect(cfg.ssid, cfg.password) == ESP_OK) + { + Config.store_wifi_ssid(cfg.ssid, sizeof(cfg.ssid)); + Config.store_wifi_passphrase(cfg.password, sizeof(cfg.password)); + } + setSSIDStarted = true; // gets reset to false by scanning networks ... hmmm + // Only save these if we're asked to, otherwise assume it was a test for connectivity + // should only save if connection was successful - i think + if (save) + { + Config.save(); + } + // } +} // 0xFB + +// Get WiFi Status +void iwmFuji::iwm_stat_net_get_wifi_status() // SP Status command +{ + Debug_printf("\r\nFuji cmd: GET WIFI STATUS"); + // WL_CONNECTED = 3, WL_DISCONNECTED = 6 + uint8_t wifiStatus = fnWiFi.connected() ? 3 : 6; + data_buffer[0] = wifiStatus; + data_len = 1; + Debug_printf("\r\nReturning Status: %d", wifiStatus); +} + +// Mount Server +void iwmFuji::iwm_ctrl_mount_host() // SP CTRL command +{ + unsigned char hostSlot = data_buffer[0]; // adamnet_recv(); + Debug_printf("\r\nFuji cmd: MOUNT HOST no. %d", hostSlot); + + if ((hostSlot < 8) && (hostMounted[hostSlot] == false)) + { + _fnHosts[hostSlot].mount(); + hostMounted[hostSlot] = true; + } +} + +// UnMount Server +void iwmFuji::iwm_ctrl_unmount_host() // SP CTRL command +{ + unsigned char hostSlot = data_buffer[0]; // adamnet_recv(); + Debug_printf("\r\nFuji cmd: UNMOUNT HOST no. %d", hostSlot); + + if ((hostSlot < 8) && (hostMounted[hostSlot] == false)) + { + _fnHosts[hostSlot].umount(); + hostMounted[hostSlot] = true; + } +} + +// Disk Image Mount +void iwmFuji::iwm_ctrl_disk_image_mount() // SP CTRL command +{ + Debug_printf("\r\nFuji cmd: MOUNT IMAGE"); + + uint8_t deviceSlot = data_buffer[0]; //adamnet_recv(); + uint8_t options = data_buffer[1]; //adamnet_recv(); // DISK_ACCESS_MODE + + // TODO: Implement FETCH? + char flag[3] = {'r', 0, 0}; + if (options == DISK_ACCESS_MODE_WRITE) + flag[1] = '+'; + + // A couple of reference variables to make things much easier to read... + fujiDisk &disk = _fnDisks[deviceSlot]; + fujiHost &host = _fnHosts[disk.host_slot]; + + Debug_printf("\r\nSelecting '%s' from host #%u as %s on D%u:\n", + disk.filename, disk.host_slot, flag, deviceSlot + 1); + + disk.fileh = host.file_open(disk.filename, disk.filename, sizeof(disk.filename), flag); + + // We've gotten this far, so make sure our bootable CONFIG disk is disabled + boot_config = false; + + // We need the file size for loading XEX files and for CASSETTE, so get that too + disk.disk_size = host.file_size(disk.fileh); + + // special handling for Disk ][ .woz images + // mediatype_t mt = MediaType::discover_mediatype(disk.filename); + // if (mt == mediatype_t::MEDIATYPE_PO) + // { // And now mount it + disk.disk_type = disk.disk_dev.mount(disk.fileh, disk.filename, disk.disk_size); + + if(options == DISK_ACCESS_MODE_WRITE) {disk.disk_dev.readonly = false;} + +} + + +// Toggle boot config on/off, aux1=0 is disabled, aux1=1 is enabled +void iwmFuji::iwm_ctrl_set_boot_config() // SP CTRL command +{ + boot_config = data_buffer[0]; // adamnet_recv(); + + if (!boot_config) + { + fujiDisk &disk = _fnDisks[0]; + if (disk.host_slot == 0xFF) + { + _fnDisks[0].disk_dev.unmount(); + _fnDisks[0].reset(); + } + } +} + + +// Do SIO copy +void iwmFuji::iwm_ctrl_copy_file() +{ + std::string copySpec; + std::string sourcePath; + std::string destPath; + FILE *sourceFile; + FILE *destFile; + char *dataBuf; + unsigned char sourceSlot; + unsigned char destSlot; + + sourceSlot = data_buffer[0]; // adamnet_recv(); + destSlot = data_buffer[0]; //adamnet_recv(); + copySpec = std::string((char *)&data_buffer[2]); + Debug_printf("copySpec: %s\n", copySpec.c_str()); + + // Chop up copyspec. + sourcePath = copySpec.substr(0, copySpec.find_first_of("|")); + destPath = copySpec.substr(copySpec.find_first_of("|") + 1); + + // At this point, if last part of dest path is / then copy filename from source. + if (destPath.back() == '/') + { + Debug_printf("append source file\n"); + std::string sourceFilename = sourcePath.substr(sourcePath.find_last_of("/") + 1); + destPath += sourceFilename; + } + + // Mount hosts, if needed. + _fnHosts[sourceSlot].mount(); + _fnHosts[destSlot].mount(); + + // Open files... + sourceFile = _fnHosts[sourceSlot].file_open(sourcePath.c_str(), (char *)sourcePath.c_str(), sourcePath.size() + 1, "r"); + destFile = _fnHosts[destSlot].file_open(destPath.c_str(), (char *)destPath.c_str(), destPath.size() + 1, "w"); + + dataBuf = (char *)malloc(532); + size_t count = 0; + do + { + count = fread(dataBuf, 1, 532, sourceFile); + fwrite(dataBuf, 1, count, destFile); + } while (count > 0); + + // copyEnd: + fclose(sourceFile); + fclose(destFile); + free(dataBuf); +} + +// Mount all +bool iwmFuji::mount_all() +{ + bool nodisks = true; // Check at the end if no disks are in a slot and disable config + + for (int i = 0; i < 8; i++) + { + fujiDisk &disk = _fnDisks[i]; + fujiHost &host = _fnHosts[disk.host_slot]; + char flag[3] = {'r', 0, 0}; + + if (disk.access_mode == DISK_ACCESS_MODE_WRITE) + flag[1] = '+'; + + if (disk.host_slot != 0xFF) + { + nodisks = false; // We have a disk in a slot + + if (host.mount() == false) + { + return true; + } + + Debug_printf("Selecting '%s' from host #%u as %s on D%u:\n", + disk.filename, disk.host_slot, flag, i + 1); + + disk.fileh = host.file_open(disk.filename, disk.filename, sizeof(disk.filename), flag); + + if (disk.fileh == nullptr) + { + return true; + } + + // We've gotten this far, so make sure our bootable CONFIG disk is disabled + boot_config = false; + + // We need the file size for loading XEX files and for CASSETTE, so get that too + disk.disk_size = host.file_size(disk.fileh); + + // And now mount it + disk.disk_type = disk.disk_dev.mount(disk.fileh, disk.filename, disk.disk_size); + if(disk.access_mode == DISK_ACCESS_MODE_WRITE) {disk.disk_dev.readonly = false;} + } + } + + if (nodisks){ + // No disks in a slot, disable config + boot_config = false; + } + + // Go ahead and respond ok + return false; +} + + +// Set boot mode +void iwmFuji::iwm_ctrl_set_boot_mode() +{ + uint8_t bm = data_buffer[0]; // adamnet_recv(); + + insert_boot_device(bm); + boot_config = true; +} + +char *_generate_appkey_filename(appkey *info) +{ + static char filenamebuf[30]; + + snprintf(filenamebuf, sizeof(filenamebuf), "/FujiNet/%04hx%02hhx%02hhx.key", info->creator, info->app, info->key); + return filenamebuf; +} + +/* + Write an "app key" to SD (ONLY!) storage. +*/ +void iwmFuji::iwm_ctrl_write_app_key() +{ + uint16_t creator = data_len;//adamnet_recv_length(); + int idx = 0; + uint8_t app = data_buffer[idx++];//adamnet_recv(); + uint8_t key = data_buffer[idx++];//adamnet_recv(); + uint8_t data[64]; + char appkeyfilename[30]; + FILE *fp; + + snprintf(appkeyfilename, sizeof(appkeyfilename), "/FujiNet/%04hx%02hhx%02hhx.key",creator,app,key); + + memcpy(data, &data_buffer[idx], 64); // adamnet_recv_buffer(data,64); + + Debug_printf("Fuji Cmd: WRITE APPKEY %s\n",appkeyfilename); + + fp = fnSDFAT.file_open(appkeyfilename, "w"); + + if (fp == nullptr) + { + Debug_printf("Could not open.\n"); + return; + } + + // size_t l = fwrite(data,sizeof(uint8_t),sizeof(data),fp); + fwrite(data,sizeof(uint8_t),sizeof(data),fp); + fclose(fp); +} + +/* + Read an "app key" from SD (ONLY!) storage + // to do - Apple 2 should send a CTRL command to read app key then a STATUS command to get the app key + // can use the same CMD code to make it easier but maybe confusing - a little different protocol than adamnet +*/ +void iwmFuji::iwm_ctrl_read_app_key() +{ + uint16_t creator = data_len;//adamnet_recv_length(); + int idx = 0; + uint8_t app = data_buffer[idx++];//adamnet_recv(); + uint8_t key = data_buffer[idx++];//adamnet_recv(); + + char appkeyfilename[30]; + FILE *fp; + + snprintf(appkeyfilename, sizeof(appkeyfilename), "/FujiNet/%04hx%02hhx%02hhx.key",creator,app,key); + + fp = fnSDFAT.file_open(appkeyfilename, "r"); + + if (fp == nullptr) + { + Debug_printf("Could not open key."); + return; + } + + memset(ctrl_stat_buffer,0,sizeof(ctrl_stat_buffer)); + ctrl_stat_len = fread(ctrl_stat_buffer, sizeof(char),64,fp); + fclose(fp); +} + +void iwmFuji::iwm_stat_read_app_key() // return the app key that was just read by the read app key control command +{ + Debug_printf("\r\nFuji cmd: READ APP KEY"); + memset(data_buffer, 0, sizeof(data_buffer)); + memcpy(data_buffer, ctrl_stat_buffer, ctrl_stat_len); + data_len = ctrl_stat_len; +} + +// DEBUG TAPE +void iwmFuji::debug_tape() +{ +} + +// Disk Image Unmount +void iwmFuji::iwm_ctrl_disk_image_umount() +{ + unsigned char ds = data_buffer[0];//adamnet_recv(); + if(_fnDisks[ds].disk_dev.device_active) + _fnDisks[ds].disk_dev.switched = true; + _fnDisks[ds].disk_dev.unmount(); + _fnDisks[ds].reset(); +} + + +//============================================================================================================================== + +// Disk Image Rotate +/* + We rotate disks my changing their disk device ID's. That prevents + us from having to unmount and re-mount devices. +*/ +void iwmFuji::image_rotate() +{ + Debug_printf("\r\nFuji cmd: IMAGE ROTATE"); + + int count = 0; + // Find the first empty slot + while (_fnDisks[count].fileh != nullptr) + count++; + + if (count > 1) + { + count--; + + // Save the device ID of the disk in the last slot + int last_id = _fnDisks[count].disk_dev.id(); + + for (int n = count; n > 0; n--) + { + int swap = _fnDisks[n - 1].disk_dev.id(); + Debug_printf("setting slot %d to ID %hx\n", n, swap); + _iwm_bus->changeDeviceId(&_fnDisks[n].disk_dev, swap); // to do! + } + + // The first slot gets the device ID of the last slot + _iwm_bus->changeDeviceId(&_fnDisks[0].disk_dev, last_id); + } +} + +// This gets called when we're about to shutdown/reboot +void iwmFuji::shutdown() +{ + for (int i = 0; i < MAX_DISK_DEVICES; i++) + _fnDisks[i].disk_dev.unmount(); +} + + + +void iwmFuji::iwm_ctrl_open_directory() +{ + Debug_printf("\r\nFuji cmd: OPEN DIRECTORY"); + + int idx = 0; + uint8_t hostSlot = data_buffer[idx++];// adamnet_recv(); + + uint16_t s = data_len - 1; // two strings but not the slot number + + memcpy((uint8_t *)&dirpath, (uint8_t *)&data_buffer[idx], s); // adamnet_recv_buffer((uint8_t *)&dirpath, s); + + if (_current_open_directory_slot == -1) + { + // See if there's a search pattern after the directory path + const char *pattern = nullptr; + int pathlen = strnlen(dirpath, s); + if (pathlen < s - 3) // Allow for two NULLs and a 1-char pattern + { + pattern = dirpath + pathlen + 1; + int patternlen = strnlen(pattern, s - pathlen - 1); + if (patternlen < 1) + pattern = nullptr; + } + + // Remove trailing slash + if (pathlen > 1 && dirpath[pathlen - 1] == '/') + dirpath[pathlen - 1] = '\0'; + + Debug_printf("Opening directory: \"%s\", pattern: \"%s\"\n", dirpath, pattern ? pattern : ""); + + if (_fnHosts[hostSlot].dir_open(dirpath, pattern, 0)) + _current_open_directory_slot = hostSlot; + else + err_result = 0x30; // bad device specific error + // to do - error reutrn if cannot open directory? + } + // else + // { + // // to do - return true or false? + // AdamNet.start_time = esp_timer_get_time(); + // adamnet_response_ack(); + // } + // // to do - return false or true? + // response_len = 1; +} + +void _set_additional_direntry_details(fsdir_entry_t *f, uint8_t *dest, uint8_t maxlen) +{ + // File modified date-time + struct tm *modtime = localtime(&f->modified_time); + modtime->tm_mon++; + modtime->tm_year -= 100; + + dest[0] = modtime->tm_year; + dest[1] = modtime->tm_mon; + dest[2] = modtime->tm_mday; + dest[3] = modtime->tm_hour; + dest[4] = modtime->tm_min; + dest[5] = modtime->tm_sec; + + // File size + uint32_t fsize = f->size; + dest[6] = fsize & 0xFF; + dest[7] = (fsize >> 8) & 0xFF; + dest[8] = (fsize >> 16) & 0xFF; + dest[9] = (fsize >> 24) & 0xFF; + + // File flags +#define FF_DIR 0x01 +#define FF_TRUNC 0x02 + + dest[10] = f->isDir ? FF_DIR : 0; + + maxlen -= ADDITIONAL_DETAILS_BYTES; // Adjust the max return value with the number of additional bytes we're copying + if (f->isDir) // Also subtract a byte for a terminating slash on directories + maxlen--; + if (strlen(f->filename) >= maxlen) + dest[11] |= FF_TRUNC; + + // File type + dest[12] = MediaType::discover_mediatype(f->filename); + + Debug_printf("Addtl: "); + for (int i = 0; i < ADDITIONAL_DETAILS_BYTES; i++) + Debug_printf("%02x ", dest[i]); + Debug_printf("\n"); +} + +void iwmFuji::iwm_ctrl_read_directory_entry() +{ + uint8_t maxlen = data_buffer[0]; + uint8_t addtl = data_buffer[1]; + + // if (response[0] == 0x00) // to do - figure out the logic here? + // { + Debug_printf("Fuji cmd: READ DIRECTORY ENTRY (max=%hu)\n", maxlen); + + fsdir_entry_t *f = _fnHosts[_current_open_directory_slot].dir_nextfile(); + + if (f != nullptr) + { + Debug_printf("::read_direntry \"%s\"\n", f->filename); + + int bufsize = sizeof(dirpath); + char *filenamedest = dirpath; + + // If 0x80 is set on AUX2, send back additional information + if (addtl & 0x80) + { + _set_additional_direntry_details(f, (uint8_t *)dirpath, maxlen); + // Adjust remaining size of buffer and file path destination + bufsize = sizeof(dirpath) - ADDITIONAL_DETAILS_BYTES; + filenamedest = dirpath + ADDITIONAL_DETAILS_BYTES; + } + else + { + bufsize = maxlen; + } + + int filelen; + // int filelen = strlcpy(filenamedest, f->filename, bufsize); + if (maxlen < 128) + { + filelen = util_ellipsize(f->filename, filenamedest, bufsize - 1); + } + else + { + filelen = strlcpy(filenamedest, f->filename, bufsize); + } + + // Add a slash at the end of directory entries + if (f->isDir && filelen < (bufsize - 2)) + { + dirpath[filelen] = '/'; + dirpath[filelen + 1] = '\0'; + Debug_printf("::entry is dir - %s\n", dirpath); + } + // Hack-o-rama to add file type character to beginning of path. - this was for Adam, but must keep for CONFIG compatability + // in Apple 2 config will somehow have to work around these extra char's + if (maxlen == DIR_MAX_LEN) + { + memmove(&dirpath[2], dirpath, 254); + // if (strstr(dirpath, ".DDP") || strstr(dirpath, ".ddp")) + // { + // dirpath[0] = 0x85; + // dirpath[1] = 0x86; + // } + // else if (strstr(dirpath, ".DSK") || strstr(dirpath, ".dsk")) + // { + // dirpath[0] = 0x87; + // dirpath[1] = 0x88; + // } + // else if (strstr(dirpath, ".ROM") || strstr(dirpath, ".rom")) + // { + // dirpath[0] = 0x89; + // dirpath[1] = 0x8a; + // } + // else if (strstr(dirpath, "/")) + // { + // dirpath[0] = 0x83; + // dirpath[1] = 0x84; + // } + // else + dirpath[0] = dirpath[1] = 0x20; + } + } + else + { + Debug_println("Reached end of of directory"); + dirpath[0] = 0x7F; + dirpath[1] = 0x7F; + } + memset(ctrl_stat_buffer, 0, sizeof(ctrl_stat_buffer)); + memcpy(ctrl_stat_buffer, dirpath, maxlen); + ctrl_stat_len = maxlen; + // } + // else + // { + // AdamNet.start_time = esp_timer_get_time(); + // adamnet_response_ack(); + // } +} + +void iwmFuji::iwm_stat_read_directory_entry() +{ + Debug_printf("\r\nFuji cmd: READ DIRECTORY ENTRY"); + memcpy(data_buffer, ctrl_stat_buffer, ctrl_stat_len); + data_len = ctrl_stat_len; +} + +void iwmFuji::iwm_stat_get_directory_position() +{ + Debug_printf("\r\nFuji cmd: GET DIRECTORY POSITION"); + + uint16_t pos = _fnHosts[_current_open_directory_slot].dir_tell(); + + data_len = sizeof(pos); + memcpy(data_buffer, &pos, sizeof(pos)); +} + +void iwmFuji::iwm_ctrl_set_directory_position() +{ + Debug_printf("\nFuji cmd: SET DIRECTORY POSITION"); + + // DAUX1 and DAUX2 hold the position to seek to in low/high order + uint16_t pos = 0; + + // adamnet_recv_buffer((uint8_t *)&pos, sizeof(uint16_t)); + memcpy((uint8_t *)&pos, (uint8_t *)&data_buffer, sizeof(uint16_t)); + + Debug_printf("\npos is now %u", pos); + + _fnHosts[_current_open_directory_slot].dir_seek(pos); +} + +void iwmFuji::iwm_ctrl_close_directory() +{ + Debug_printf("\nFuji cmd: CLOSE DIRECTORY"); + + if (_current_open_directory_slot != -1) + _fnHosts[_current_open_directory_slot].dir_close(); + + _current_open_directory_slot = -1; + fnSystem.delay(100); // add delay because bad traces +} + +// Get network adapter configuration +void iwmFuji::iwm_stat_get_adapter_config() +{ + Debug_printf("\nFuji cmd: GET ADAPTER CONFIG"); + + // Response to FUJICMD_GET_ADAPTERCONFIG + AdapterConfig cfg; + + memset(&cfg, 0, sizeof(cfg)); + + strlcpy(cfg.fn_version, fnSystem.get_fujinet_version(true), sizeof(cfg.fn_version)); + + if (!fnWiFi.connected()) + { + strlcpy(cfg.ssid, "NOT CONNECTED", sizeof(cfg.ssid)); + } + else + { + strlcpy(cfg.hostname, fnSystem.Net.get_hostname().c_str(), sizeof(cfg.hostname)); + strlcpy(cfg.ssid, fnWiFi.get_current_ssid().c_str(), sizeof(cfg.ssid)); + fnWiFi.get_current_bssid(cfg.bssid); + fnSystem.Net.get_ip4_info(cfg.localIP, cfg.netmask, cfg.gateway); + fnSystem.Net.get_ip4_dns_info(cfg.dnsIP); + } + + fnWiFi.get_mac(cfg.macAddress); + + memcpy(data_buffer, &cfg, sizeof(cfg)); + data_len = sizeof(cfg); +} + +// Make new disk and shove into device slot +void iwmFuji::iwm_ctrl_new_disk() +{ + int idx = 0; + uint8_t hs = data_buffer[idx++]; //adamnet_recv(); + uint8_t ds = data_buffer[idx++]; //adamnet_recv(); + uint8_t t = data_buffer[idx++]; // added for apple2; + uint32_t numBlocks; + uint8_t *c = (uint8_t *)&numBlocks; + uint8_t p[256]; + + + //adamnet_recv_buffer(c, sizeof(uint32_t)); + memcpy((uint8_t *)c, (uint8_t *)&data_buffer[idx],sizeof(uint32_t) ); + idx += sizeof(uint32_t); + + memcpy(p, (uint8_t *)&data_buffer[idx], sizeof(p)); + //adamnet_recv_buffer(p, 256); + + fujiDisk &disk = _fnDisks[ds]; + fujiHost &host = _fnHosts[hs]; + + if (host.file_exists((const char *)p)) + { + return; + } + + disk.host_slot = hs; + disk.access_mode = DISK_ACCESS_MODE_WRITE; + strlcpy(disk.filename, (const char *)p, 256); + + disk.fileh = host.file_open(disk.filename, disk.filename, sizeof(disk.filename), "w"); + + Debug_printf("Creating file %s on host slot %u mounting in disk slot %u numblocks: %lu\n", disk.filename, hs, ds, numBlocks); + + disk.disk_dev.blank_header_type = t; + disk.disk_dev.write_blank(disk.fileh, numBlocks); + + fclose(disk.fileh); +} + +// Send host slot data to computer +void iwmFuji::iwm_stat_read_host_slots() +{ + Debug_printf("\nFuji cmd: READ HOST SLOTS"); + + //adamnet_recv(); // ck + + char hostSlots[MAX_HOSTS][MAX_HOSTNAME_LEN]; + memset(hostSlots, 0, sizeof(hostSlots)); + + for (int i = 0; i < MAX_HOSTS; i++) + strlcpy(hostSlots[i], _fnHosts[i].get_hostname(), MAX_HOSTNAME_LEN); + + memcpy(data_buffer, hostSlots, sizeof(hostSlots)); + data_len = sizeof(hostSlots); +} + +// Read and save host slot data from computer +void iwmFuji::iwm_ctrl_write_host_slots() +{ + Debug_printf("\nFuji cmd: WRITE HOST SLOTS"); + + char hostSlots[MAX_HOSTS][MAX_HOSTNAME_LEN]; + //adamnet_recv_buffer((uint8_t *)hostSlots, sizeof(hostSlots)); + memcpy((uint8_t *)hostSlots, data_buffer, sizeof(hostSlots)); + + for (int i = 0; i < MAX_HOSTS; i++) + { + hostMounted[i] = false; + _fnHosts[i].set_hostname(hostSlots[i]); + } + _populate_config_from_slots(); + Config.save(); +} + +// Store host path prefix +void iwmFuji::iwm_ctrl_set_host_prefix() +{ + Debug_printf("\nFuji cmd: SET HOST PREFIX - NOT IMPLEMENTED"); +} + +// Retrieve host path prefix +void iwmFuji::iwm_stat_get_host_prefix() +{ + Debug_printf("\nFuji cmd: GET HOST PREFIX - NOT IMPLEMENTED"); +} + +// Send device slot data to computer +void iwmFuji::iwm_stat_read_device_slots() +{ + Debug_printf("\nFuji cmd: READ DEVICE SLOTS"); + + struct disk_slot + { + uint8_t hostSlot; + uint8_t mode; + char filename[MAX_DISPLAY_FILENAME_LEN]; + }; + disk_slot diskSlots[MAX_DISK_DEVICES]; + + memset(&diskSlots, 0, sizeof(diskSlots)); + + int returnsize; + + // Load the data from our current device array + for (int i = 0; i < MAX_DISK_DEVICES; i++) + { + diskSlots[i].mode = _fnDisks[i].access_mode; + diskSlots[i].hostSlot = _fnDisks[i].host_slot; + strlcpy(diskSlots[i].filename, _fnDisks[i].filename, MAX_DISPLAY_FILENAME_LEN); + } + + returnsize = sizeof(disk_slot) * MAX_DISK_DEVICES; + + memcpy(data_buffer, &diskSlots, returnsize); + data_len = returnsize; +} + +// Read and save disk slot data from computer +void iwmFuji::iwm_ctrl_write_device_slots() +{ + Debug_printf("\nFuji cmd: WRITE DEVICE SLOTS"); + + struct + { + uint8_t hostSlot; + uint8_t mode; + char filename[MAX_DISPLAY_FILENAME_LEN]; + } diskSlots[MAX_DISK_DEVICES]; + + // adamnet_recv_buffer((uint8_t *)&diskSlots, sizeof(diskSlots)); + memcpy((uint8_t *)&diskSlots, data_buffer, sizeof(diskSlots)); + + // Load the data into our current device array + for (int i = 0; i < MAX_DISK_DEVICES; i++) + _fnDisks[i].reset(diskSlots[i].filename, diskSlots[i].hostSlot, diskSlots[i].mode); + + // Save the data to disk + _populate_config_from_slots(); + Config.save(); +} + +// Temporary(?) function while we move from old config storage to new +void iwmFuji::_populate_slots_from_config() +{ + for (int i = 0; i < MAX_HOSTS; i++) + { + if (Config.get_host_type(i) == fnConfig::host_types::HOSTTYPE_INVALID) + _fnHosts[i].set_hostname(""); + else + _fnHosts[i].set_hostname(Config.get_host_name(i).c_str()); + } + + for (int i = 0; i < MAX_DISK_DEVICES; i++) + { + _fnDisks[i].reset(); + + if (Config.get_mount_host_slot(i) != HOST_SLOT_INVALID) + { + if (Config.get_mount_host_slot(i) >= 0 && Config.get_mount_host_slot(i) <= MAX_HOSTS) + { + strlcpy(_fnDisks[i].filename, + Config.get_mount_path(i).c_str(), sizeof(fujiDisk::filename)); + _fnDisks[i].host_slot = Config.get_mount_host_slot(i); + if (Config.get_mount_mode(i) == fnConfig::mount_modes::MOUNTMODE_WRITE) + _fnDisks[i].access_mode = DISK_ACCESS_MODE_WRITE; + else + _fnDisks[i].access_mode = DISK_ACCESS_MODE_READ; + } + } + } +} + +// Temporary(?) function while we move from old config storage to new +void iwmFuji::_populate_config_from_slots() +{ + for (int i = 0; i < MAX_HOSTS; i++) + { + fujiHostType htype = _fnHosts[i].get_type(); + const char *hname = _fnHosts[i].get_hostname(); + + if (hname[0] == '\0') + { + Config.clear_host(i); + } + else + { + Config.store_host(i, hname, + htype == HOSTTYPE_TNFS ? fnConfig::host_types::HOSTTYPE_TNFS : fnConfig::host_types::HOSTTYPE_SD); + } + } + + for (int i = 0; i < MAX_DISK_DEVICES; i++) + { + if (_fnDisks[i].host_slot >= MAX_HOSTS || _fnDisks[i].filename[0] == '\0') + Config.clear_mount(i); + else + Config.store_mount(i, _fnDisks[i].host_slot, _fnDisks[i].filename, + _fnDisks[i].access_mode == DISK_ACCESS_MODE_WRITE ? fnConfig::mount_modes::MOUNTMODE_WRITE : fnConfig::mount_modes::MOUNTMODE_READ); + } +} + +// Write a 256 byte filename to the device slot +void iwmFuji::iwm_ctrl_set_device_filename() +{ + char f[MAX_FILENAME_LEN]; + int idx = 0; + unsigned char ds = data_buffer[idx++];// adamnet_recv(); + uint16_t s = data_len; + s--; + + + Debug_printf("\nSET DEVICE SLOT %d", ds); + + // adamnet_recv_buffer((uint8_t *)&f, s); + memcpy((uint8_t *)&f, &data_buffer[idx], s); + Debug_printf("\nfilename: %s", f); + + memcpy(_fnDisks[ds].filename, f, MAX_FILENAME_LEN); + _populate_config_from_slots(); // this one maybe unnecessary? +} + +// Get a 256 byte filename from device slot +void iwmFuji::iwm_ctrl_get_device_filename() +{ + unsigned char ds = data_buffer[0];//adamnet_recv(); + + ctrl_stat_len = MAX_FILENAME_LEN; + memcpy(ctrl_stat_buffer, _fnDisks[ds].filename, ctrl_stat_len); +} + +void iwmFuji::iwm_stat_get_device_filename() +{ + Debug_printf("\nFuji cmd: GET DEVICE FILENAME"); + memcpy(data_buffer, ctrl_stat_buffer, ctrl_stat_len); + data_len = 256; +} + +// Mounts the desired boot disk number +void iwmFuji::insert_boot_device(uint8_t d) +{ + const char *config_atr = "/autorun.po"; + const char *mount_all_atr = "/mount-and-boot.po"; + FILE *fBoot; + + switch (d) + { + case 0: + fBoot = fnSPIFFS.file_open(config_atr); + _fnDisks[0].disk_dev.mount(fBoot, config_atr, 143360, MEDIATYPE_PO); + break; + case 1: + + fBoot = fnSPIFFS.file_open(mount_all_atr); + _fnDisks[0].disk_dev.mount(fBoot, mount_all_atr, 143360, MEDIATYPE_PO); + break; + } + + _fnDisks[0].disk_dev.is_config_device = true; + _fnDisks[0].disk_dev.device_active = true; +} + +void iwmFuji::iwm_ctrl_enable_device() +{ + unsigned char d = data_buffer[0]; // adamnet_recv(); + + Debug_printf("\nFuji cmd: ENABLE DEVICE"); + IWM.enableDevice(d); +} + +void iwmFuji::iwm_ctrl_disable_device() +{ + unsigned char d = data_buffer[0]; // adamnet_recv(); + + Debug_printf("\nFuji cmd: DISABLE DEVICE"); + IWM.disableDevice(d); +} + +iwmDisk *iwmFuji::bootdisk() +{ + return _bootDisk; +} + +// Initializes base settings and adds our devices to the SIO bus +void iwmFuji::setup(iwmBus *iwmbus) +{ + // set up Fuji device + _iwm_bus = iwmbus; + + _populate_slots_from_config(); + + // Disable booting from CONFIG if our settings say to turn it off + boot_config = false; // to do - understand? + + // Disable status_wait if our settings say to turn it off + status_wait_enabled = false; // to do - understand? + + theNetwork = new iwmNetwork(); + _iwm_bus->addDevice(theNetwork,iwm_fujinet_type_t::Network); + + theClock = new iwmClock(); + _iwm_bus->addDevice(theClock, iwm_fujinet_type_t::Clock); + + theCPM = new iwmCPM(); + _iwm_bus->addDevice(theCPM, iwm_fujinet_type_t::CPM); + + for (int i = MAX_DISK_DEVICES - MAX_DISK2_DEVICES -1; i >= 0; i--) + { + _fnDisks[i].disk_dev.set_disk_number('0' + i); + _iwm_bus->addDevice(&_fnDisks[i].disk_dev, iwm_fujinet_type_t::BlockDisk); + } + + Debug_printf("\nConfig General Boot Mode: %u\n",Config.get_general_boot_mode()); + if (Config.get_general_boot_mode() == 0) + { + FILE *f = fnSPIFFS.file_open("/autorun.po"); + _fnDisks[0].disk_dev.mount(f, "/autorun.po", 512*256, MEDIATYPE_PO); + } + else + { + FILE *f = fnSPIFFS.file_open("/mount-and-boot.po"); + _fnDisks[0].disk_dev.mount(f, "/mount-and-boot.po", 512*256, MEDIATYPE_PO); + } + + // theNetwork = new adamNetwork(); + // theSerial = new adamSerial(); + // _iwm_bus->addDevice(theNetwork, 0x09); // temporary. + // _iwm_bus->addDevice(theSerial, 0x0e); // Serial port + // _iwm_bus->addDevice(&theFuji, 0x0F); // Fuji becomes the gateway device. + + // Add our devices to the AdamNet bus + // for (int i = 0; i < 4; i++) + // _adamnet_bus->addDevice(&_fnDisks[i].disk_dev, ADAMNET_DEVICEID_DISK + i); + + // for (int i = 0; i < MAX_NETWORK_DEVICES; i++) + // _adamnet_bus->addDevice(&sioNetDevs[i], ADAMNET_DEVICEID_FN_NETWORK + i); +} + +int iwmFuji::get_disk_id(int drive_slot) +{ + return -1; +} +std::string iwmFuji::get_host_prefix(int host_slot) +{ + return std::string(); +} + +void iwmFuji::send_status_reply_packet() +{ + + uint8_t data[4]; + + // Build the contents of the packet + data[0] = STATCODE_READ_ALLOWED | STATCODE_DEVICE_ONLINE; + data[1] = 0; // block size 1 + data[2] = 0; // block size 2 + data[3] = 0; // block size 3 + IWM.iwm_send_packet(id(), iwm_packet_type_t::status, SP_ERR_NOERROR, data, 4); +} + +void iwmFuji::send_status_dib_reply_packet() +{ + uint8_t data[25]; + + //* write data buffer first (25 bytes) 3 grp7 + 4 odds + // General Status byte + // Bit 7: Block device + // Bit 6: Write allowed + // Bit 5: Read allowed + // Bit 4: Device online or disk in drive + // Bit 3: Format allowed + // Bit 2: Media write protected (block devices only) + // Bit 1: Currently interrupting (//c only) + // Bit 0: Currently open (char devices only) + data[0] = STATCODE_READ_ALLOWED | STATCODE_DEVICE_ONLINE; + data[1] = 0; // block size 1 + data[2] = 0; // block size 2 + data[3] = 0; // block size 3 + data[4] = 0x08; // ID string length - 11 chars + data[5] = 'T'; + data[6] = 'H'; + data[7] = 'E'; + data[8] = '_'; + data[9] = 'F'; + data[10] = 'U'; + data[11] = 'J'; + data[12] = 'I'; + data[13] = ' '; + data[14] = ' '; + data[15] = ' '; + data[16] = ' '; + data[17] = ' '; + data[18] = ' '; + data[19] = ' '; + data[20] = ' '; // ID string (16 chars total) + data[21] = SP_TYPE_BYTE_FUJINET; // Device type - 0x02 harddisk + data[22] = SP_SUBTYPE_BYTE_FUJINET; // Device Subtype - 0x0a + data[23] = 0x00; // Firmware version 2 bytes + data[24] = 0x01; // + IWM.iwm_send_packet(id(), iwm_packet_type_t::status, SP_ERR_NOERROR, data, 25); +} + +void iwmFuji::send_stat_get_enable() +{ + data_len = 1; + data_buffer[0] = 1; +} + + +void iwmFuji::iwm_open(iwm_decoded_cmd_t cmd) +{ + // Debug_printf("\r\nOpen FujiNet Unit # %02x",cmd.g7byte1); + send_status_reply_packet(); +} + +void iwmFuji::iwm_close(iwm_decoded_cmd_t cmd) +{ +} + + +void iwmFuji::iwm_read(iwm_decoded_cmd_t cmd) +{ +} + + +void iwmFuji::iwm_status(iwm_decoded_cmd_t cmd) +{ + // uint8_t source = cmd.dest; // we are the destination and will become the source // data_buffer[6]; + uint8_t status_code = get_status_code(cmd); // (cmd.g7byte3 & 0x7f) | ((cmd.grp7msb << 3) & 0x80); // status codes 00-FF + Debug_printf("\ntheFuji Device %02x Status Code %02x", id(), status_code); + // Debug_printf("\r\nStatus List is at %02x %02x", cmd.g7byte1 & 0x7f, cmd.g7byte2 & 0x7f); + + switch (status_code) + { + case 0xAA: + iwm_hello_world(); + break; + case IWM_STATUS_STATUS: // 0x00 + send_status_reply_packet(); + return; + break; + // case IWM_STATUS_DCB: // 0x01 + // case IWM_STATUS_NEWLINE: // 0x02 + case IWM_STATUS_DIB: // 0x03 + send_status_dib_reply_packet(); + return; + break; + // case FUJICMD_RESET: // 0xFF + case FUJICMD_GET_SSID: // 0xFE + iwm_stat_net_get_ssid(); + break; + case FUJICMD_SCAN_NETWORKS: // 0xFD + iwm_stat_net_scan_networks(); + break; + case FUJICMD_GET_SCAN_RESULT: // 0xFC + iwm_stat_net_scan_result(); + break; + // case FUJICMD_SET_SSID: // 0xFB + case FUJICMD_GET_WIFISTATUS: // 0xFA + iwm_stat_net_get_wifi_status(); + break; + // case FUJICMD_MOUNT_HOST: // 0xF9 + // case FUJICMD_MOUNT_IMAGE: // 0xF8 + // case FUJICMD_OPEN_DIRECTORY: // 0xF7 + case FUJICMD_READ_DIR_ENTRY: // 0xF6 + iwm_stat_read_directory_entry(); + break; + // case FUJICMD_CLOSE_DIRECTORY: // 0xF5 + case FUJICMD_READ_HOST_SLOTS: // 0xF4 + iwm_stat_read_host_slots(); + break; + // case FUJICMD_WRITE_HOST_SLOTS: // 0xF3 + case FUJICMD_READ_DEVICE_SLOTS: // 0xF2 + iwm_stat_read_device_slots(); + break; + // case FUJICMD_WRITE_DEVICE_SLOTS: // 0xF1 + // case FUJICMD_UNMOUNT_IMAGE: // 0xE9 + case FUJICMD_GET_ADAPTERCONFIG: // 0xE8 + iwm_stat_get_adapter_config(); // to do - set up as a DCB? + break; + // case FUJICMD_NEW_DISK: // 0xE7 + // case FUJICMD_UNMOUNT_HOST: // 0xE6 + case FUJICMD_GET_DIRECTORY_POSITION: // 0xE5 + iwm_stat_get_directory_position(); + break; + // case FUJICMD_SET_DIRECTORY_POSITION: // 0xE4 + // case FUJICMD_SET_DEVICE_FULLPATH: // 0xE2 + // case FUJICMD_SET_HOST_PREFIX: // 0xE1 + case FUJICMD_GET_HOST_PREFIX: // 0xE0 + iwm_stat_get_host_prefix(); + break; + // case FUJICMD_WRITE_APPKEY: // 0xDE + case FUJICMD_READ_APPKEY: // 0xDD + iwm_stat_read_app_key(); + break; + // case FUJICMD_OPEN_APPKEY: // 0xDC + // case FUJICMD_CLOSE_APPKEY: // 0xDB + case FUJICMD_GET_DEVICE_FULLPATH: // 0xDA + // to do? + break; + // case FUJICMD_CONFIG_BOOT: // 0xD9 + // case FUJICMD_COPY_FILE: // 0xD8 + // case FUJICMD_MOUNT_ALL: // 0xD7 + // case FUJICMD_SET_BOOT_MODE: // 0xD6 + // case FUJICMD_ENABLE_DEVICE: // 0xD5 + // case FUJICMD_DISABLE_DEVICE: // 0xD4 + case FUJICMD_DEVICE_ENABLE_STATUS: // 0xD1 + send_stat_get_enable(); + case FUJICMD_STATUS: // 0x53 + // to do? parallel to SP status? + break; + default: + Debug_printf("\nBad Status Code, sending error response"); + send_reply_packet(SP_ERR_BADCTL); + return; + break; + } + Debug_printf("\nStatus code complete, sending response"); + IWM.iwm_send_packet(id(), iwm_packet_type_t::data, 0, data_buffer, data_len); + } + +void iwmFuji::iwm_ctrl(iwm_decoded_cmd_t cmd) +{ + err_result = SP_ERR_NOERROR; + + // uint8_t source = cmd.dest; // we are the destination and will become the source // data_buffer[6]; + uint8_t control_code = get_status_code(cmd); // (cmd.g7byte3 & 0x7f) | ((cmd.grp7msb << 3) & 0x80); // ctrl codes 00-FF + Debug_printf("\ntheFuji Device %02x Control Code %02x", id(), control_code); + // already called by ISR + data_len = 512; + Debug_printf("\nDecoding Control Data Packet:"); + IWM.iwm_decode_data_packet((uint8_t *)data_buffer, data_len); + // data_len = decode_packet((uint8_t *)data_buffer); + print_packet((uint8_t *)data_buffer, data_len); + + switch (control_code) + { + case IWM_CTRL_SET_DCB: // 0x01 + case IWM_CTRL_SET_NEWLINE: // 0x02 + case 0xAA: + iwm_dummy_command(); + break; + case IWM_CTRL_RESET: // 0x00 + case FUJICMD_RESET: // 0xFF + send_reply_packet(err_result); + iwm_ctrl_reset_fujinet(); + break; + // case FUJICMD_GET_SSID: // 0xFE + // case FUJICMD_SCAN_NETWORKS: // 0xFD + case FUJICMD_GET_SCAN_RESULT: // 0xFC + iwm_ctrl_net_scan_result(); + break; + case FUJICMD_SET_SSID: + iwm_ctrl_net_set_ssid(); // 0xFB + break; + //case FUJICMD_GET_WIFISTATUS: // 0xFA + case FUJICMD_MOUNT_HOST: // 0xF9 + iwm_ctrl_mount_host(); + break; + case FUJICMD_MOUNT_IMAGE: // 0xF8 + iwm_ctrl_disk_image_mount(); + break; + case FUJICMD_OPEN_DIRECTORY: // 0xF7 + // print_packet((uint8_t *)data_buffer, 512); + iwm_ctrl_open_directory(); + break; + case FUJICMD_READ_DIR_ENTRY: // 0xF6 + iwm_ctrl_read_directory_entry(); + break; + case FUJICMD_CLOSE_DIRECTORY: // 0xF5 + iwm_ctrl_close_directory(); + break; + // case FUJICMD_READ_HOST_SLOTS: // 0xF4 + case FUJICMD_WRITE_HOST_SLOTS: // 0xF3 + iwm_ctrl_write_host_slots(); + break; + // case FUJICMD_READ_DEVICE_SLOTS: // 0xF2 + case FUJICMD_WRITE_DEVICE_SLOTS: // 0xF1 + iwm_ctrl_write_device_slots(); + break; + case FUJICMD_UNMOUNT_IMAGE: // 0xE9 + iwm_ctrl_disk_image_umount(); + break; + // case FUJICMD_GET_ADAPTERCONFIG: // 0xE8 + case FUJICMD_NEW_DISK: // 0xE7 + iwm_ctrl_new_disk(); + break; + case FUJICMD_UNMOUNT_HOST: // 0xE6 + iwm_ctrl_unmount_host(); + break; + // case FUJICMD_GET_DIRECTORY_POSITION: // 0xE5 + case FUJICMD_SET_DIRECTORY_POSITION: // 0xE4 + iwm_ctrl_set_directory_position(); + break; + case FUJICMD_SET_DEVICE_FULLPATH: // 0xE2 + iwm_ctrl_set_device_filename(); + break; + case FUJICMD_SET_HOST_PREFIX: // 0xE1 + iwm_ctrl_set_host_prefix(); + break; + // case FUJICMD_GET_HOST_PREFIX: // 0xE0 + case FUJICMD_WRITE_APPKEY: // 0xDE + iwm_ctrl_write_app_key(); + break; + case FUJICMD_READ_APPKEY: // 0xDD + iwm_ctrl_read_app_key(); // use before reading the key using statys + break; + // case FUJICMD_OPEN_APPKEY: // 0xDC + // case FUJICMD_CLOSE_APPKEY: // 0xDB + // case FUJICMD_GET_DEVICE_FULLPATH: // 0xDA + case FUJICMD_CONFIG_BOOT: // 0xD9 + iwm_ctrl_set_boot_config(); + break; + case FUJICMD_COPY_FILE: // 0xD8 + iwm_ctrl_copy_file(); + break; + case FUJICMD_MOUNT_ALL: // 0xD7 + mount_all(); + break; + case FUJICMD_SET_BOOT_MODE: // 0xD6 + iwm_ctrl_set_boot_mode(); + break; + case FUJICMD_ENABLE_DEVICE: // 0xD5 + iwm_ctrl_enable_device(); + break; + case FUJICMD_DISABLE_DEVICE: // 0xD4 + iwm_ctrl_disable_device(); + break; + // case FUJICMD_STATUS: // 0x53 + default: + err_result = SP_ERR_BADCTL; + break; + } + send_reply_packet(err_result); +} + + +void iwmFuji::process(iwm_decoded_cmd_t cmd) +{ + fnLedManager.set(LED_BUS, true); + switch (cmd.command) + { + case 0x00: // status + Debug_printf("\ntheFuji handling status command"); + iwm_status(cmd); + break; + case 0x01: // read block + iwm_return_badcmd(cmd); + break; + case 0x02: // write block + iwm_return_badcmd(cmd); + break; + case 0x03: // format + iwm_return_badcmd(cmd); + break; + case 0x04: // control + Debug_printf("\ntheFuji handling control command"); + iwm_ctrl(cmd); + break; + case 0x06: // open + Debug_printf("\ntheFuji handling open command"); + iwm_open(cmd); + break; + case 0x07: // close + Debug_printf("\ntheFuji handling close command"); + iwm_close(cmd); + break; + case 0x08: // read + Debug_printf("\ntheFuji handling read command"); + iwm_read(cmd); + break; + case 0x09: // write + iwm_return_badcmd(cmd); + break; + default: + iwm_return_badcmd(cmd); + break; + } // switch (cmd) + fnLedManager.set(LED_BUS, false); +} + +void iwmFuji::handle_ctl_eject(uint8_t spid) { + int ds = 255; + for(int i = 0; i < MAX_DISK_DEVICES; i++) { + if(theFuji.get_disks(i)->disk_dev.id() == spid) { + ds = i; + } + } + if(ds != 255 ) { + theFuji.get_disks(ds)->reset(); + Config.clear_mount(ds); + Config.save(); + theFuji._populate_slots_from_config(); + } +} +#endif /* BUILD_APPLE */ \ No newline at end of file diff --git a/lib/device/mac/fuji.h b/lib/device/mac/fuji.h new file mode 100644 index 000000000..7031c1a4b --- /dev/null +++ b/lib/device/mac/fuji.h @@ -0,0 +1,244 @@ +#ifdef BUILD_MAC +#ifndef FUJI_H +#define FUJI_H +#include + +#include "../../include/debug.h" +#include "bus.h" + +#include "mac/floppy.h" +// #include "mac/network.h" +#include "mac/printer.h" +// #include "iwm/cpm.h" +// #include "iwm/clock.h" +#include "mac/modem.h" + +#include "../fuji/fujiHost.h" +#include "../fuji/fujiDisk.h" +#include "../fuji/fujiCmd.h" + +#define MAX_HOSTS 8 +#define MAX_DISK_DEVICES 6 // 4 SP devices + 2 DiskII devices +#define MAX_DISK2_DEVICES 2 // for now until we add 3.5" disks +#define MAX_NETWORK_DEVICES 4 + +#define MAX_SSID_LEN 32 +#define MAX_WIFI_PASS_LEN 64 + +#define MAX_APPKEY_LEN 64 + +#define READ_DEVICE_SLOTS_DISKS1 0x00 +#define READ_DEVICE_SLOTS_TAPE 0x10 + +typedef struct +{ + char ssid[MAX_SSID_LEN + 1]; + char hostname[64]; + unsigned char localIP[4]; + unsigned char gateway[4]; + unsigned char netmask[4]; + unsigned char dnsIP[4]; + unsigned char macAddress[6]; + unsigned char bssid[6]; + char fn_version[15]; +} AdapterConfig; + +enum appkey_mode : uint8_t +{ + APPKEYMODE_READ = 0, + APPKEYMODE_WRITE, + APPKEYMODE_INVALID +}; + +struct appkey +{ + uint16_t creator = 0; + uint8_t app = 0; + uint8_t key = 0; + appkey_mode mode = APPKEYMODE_INVALID; + uint8_t reserved = 0; +} __attribute__((packed)); + +class macFuji : public macDevice +{ +private: + bool isReady = false; + bool alreadyRunning = false; // Replace isReady and scanStarted with THIS. + bool scanStarted = false; + bool hostMounted[MAX_HOSTS]; + bool setSSIDStarted = false; + // uint8_t err_result = SP_ERR_NOERROR; + + //uint8_t response[1024]; // use packet_buffer instead + //uint16_t response_len; + + // Response to SIO_FUJICMD_GET_SCAN_RESULT + struct + { + char ssid[MAX_SSID_LEN + 1]; + uint8_t rssi; + } detail; + + macBus *_mac_bus; + + fujiHost _fnHosts[MAX_HOSTS]; + + fujiDisk _fnDisks[MAX_DISK_DEVICES]; + + // iwmNetwork *theNetwork; + + // iwmCPM *theCPM; + + // iwmClock *theClock; + + int _current_open_directory_slot = -1; + + macFloppy *_bootDisk; // special disk drive just for configuration + // iwmDisk *_bootDisk; // special disk drive just for configuration + + uint8_t bootMode = 0; // Boot mode 0 = CONFIG, 1 = MINI-BOOT + + uint8_t _countScannedSSIDs = 0; + + appkey _current_appkey; + + // uint8_t ctrl_stat_buffer[767]; // what is proper length + // size_t ctrl_stat_len = 0; // max payload length is 767 + + char dirpath[256]; + +protected: +// void iwm_dummy_command(); // control 0xAA +// void iwm_hello_world(); // status 0xAA +// void iwm_ctrl_reset_fujinet(); // control 0xFF +// void iwm_stat_net_get_ssid(); // status 0xFE +// void iwm_stat_net_scan_networks(); // status 0xFD +// void iwm_ctrl_net_scan_result(); // control 0xFC +// void iwm_stat_net_scan_result(); // status 0xFC +// void iwm_ctrl_net_set_ssid(); // control 0xFB +// void iwm_stat_net_get_wifi_status(); // status 0xFA +// void iwm_ctrl_mount_host(); // 0xF9 +// void iwm_ctrl_disk_image_mount(); // 0xF8 +// void iwm_ctrl_open_directory(); // 0xF7 +// void iwm_ctrl_read_directory_entry(); // 0xF6 +// void iwm_stat_read_directory_entry(); // 0xF6 + +// void iwm_ctrl_close_directory(); // 0xF5 +// void iwm_stat_read_host_slots(); // 0xF4 +// void iwm_ctrl_write_host_slots(); // 0xF3 +// void iwm_stat_read_device_slots(); // 0xF2 +// void iwm_ctrl_write_device_slots(); // 0xF1 +// void iwm_ctrl_disk_image_umount(); // 0xE9 +// void iwm_stat_get_adapter_config(); // 0xE8 +// void iwm_ctrl_new_disk(); // 0xE7 +// void iwm_ctrl_unmount_host(); // 0xE6 + +// void iwm_stat_get_directory_position(); // 0xE5 +// void iwm_ctrl_set_directory_position(); // 0xE4 + /* + void adamnet_set_hadamnet_index(); // 0xE3 + */ +// void iwm_ctrl_set_device_filename(); // 0xE2 + +// void iwm_ctrl_set_host_prefix(); // 0xE1 +// void iwm_stat_get_host_prefix(); // 0xE0 + /* + void adamnet_set_adamnet_external_clock(); // 0xDF + */ + // void iwm_ctrl_write_app_key(); // 0xDE + // void iwm_ctrl_read_app_key(); // 0xDD - control + // void iwm_stat_read_app_key(); // 0xDD - status + /* + void adamnet_open_app_key(); // 0xDC + void adamnet_close_app_key(); // 0xDB + */ + // void iwm_ctrl_get_device_filename(); // 0xDA + // void iwm_stat_get_device_filename(); // 0xDA + + // void iwm_ctrl_set_boot_config(); // 0xD9 + // void iwm_ctrl_copy_file(); // 0xD8 + // void iwm_ctrl_set_boot_mode(); // 0xD6 + // void iwm_ctrl_enable_device(); // 0xD5 + // void iwm_ctrl_disable_device(); // 0xD4 + // void send_stat_get_enable(); // 0xD1 + + + + // void iwm_ctrl(iwm_decoded_cmd_t cmd) override; + // void iwm_open(iwm_decoded_cmd_t cmd) override; + // void iwm_close(iwm_decoded_cmd_t cmd) override; + // void iwm_read(iwm_decoded_cmd_t cmd) override; + // void iwm_status(iwm_decoded_cmd_t cmd) override; + + // void send_status_reply_packet() override; + // void send_status_dib_reply_packet() override; + + // void send_extended_status_reply_packet() override{}; + // void send_extended_status_dib_reply_packet() override{}; + +public: + bool boot_config = true; + + bool status_wait_enabled = true; + + // iwmDisk *bootdisk(); + macFloppy *bootdisk(); + + void debug_tape() {}; + + void insert_boot_device(uint8_t d) {}; + + void setup(macBus *macbus); + + void image_rotate() {}; + int get_disk_id(int drive_slot) { return -1; }; + void handle_ctl_eject(uint8_t spid) {}; + std::string get_host_prefix(int host_slot) { return std::string(); }; + + fujiHost *get_hosts(int i) { return &_fnHosts[i]; } + fujiDisk *get_disks(int i) { return &_fnDisks[i]; } + // iwmDisk2 _fnDisk2s[MAX_DISK2_DEVICES]; + + void _populate_slots_from_config() {}; + void _populate_config_from_slots() {}; + + bool mount_all() { return false; }; // 0xD7 + + // void FujiStatus(iwm_decoded_cmd_t cmd) { iwm_status(cmd); } + // void FujiControl(iwm_decoded_cmd_t cmd) { iwm_ctrl(cmd); } + + macFuji(); + ~macFuji(){}; + + // virtual void startup_hack() override { Debug_printf("\n Fuji startup hack"); } + void shutdown() override {}; + void process(mac_cmd_t cmd) override {}; + +}; + + + +extern macFuji theFuji; + +#endif // guard +#endif // BUILD_MAC + +#if 0 +#ifndef FUJI_H +#define FUJI_H +#include + +#include "../../include/debug.h" +#include "bus.h" +#include "iwm/disk2.h" +#include "iwm/network.h" +#include "iwm/printer.h" +#include "iwm/cpm.h" +#include "iwm/clock.h" +#include "iwm/modem.h" + + + + +#endif // FUJI_H +#endif /* BUILD_APPLE */ diff --git a/lib/device/mac/modem.cpp b/lib/device/mac/modem.cpp new file mode 100644 index 000000000..5f46bf6c2 --- /dev/null +++ b/lib/device/mac/modem.cpp @@ -0,0 +1,1610 @@ +#if BUILD_MAC + +#include +#include + +#include "../../include/atascii.h" +#include "modem.h" +#include "../hardware/fnUART.h" +#include "fnWiFi.h" +#include "fnFsSPIFFS.h" +#include "fnSystem.h" +#include "../utils/utils.h" +#include "fnConfig.h" +#include "led.h" + +macModem::macModem(FileSystem *_fs, bool snifferEnable) +{ + activeFS = _fs; + modemSniffer = new ModemSniffer(activeFS, snifferEnable); + // set_term_type("dumb"); + // telnet = telnet_init(telopts, _telnet_event_handler, 0, this); + // mrxq = xQueueCreate(16384, sizeof(char)); + // mtxq = xQueueCreate(16384, sizeof(char)); + // xTaskCreatePinnedToCore(_modem_task, "modemTask", 4096, this, MODEM_TASK_PRIORITY, &modemTask, MODEM_TASK_CPU); +} + +macModem::~macModem() +{ + if (modemSniffer != nullptr) + { + delete modemSniffer; + } + + // if (telnet != nullptr) + // { + // telnet_free(telnet); + // } + + // vTaskDelete(modemTask); + // vQueueDelete(mrxq); + // vQueueDelete(mtxq); +} + + + +#endif + +#if 0 + +#include +#include + +#include "../../include/atascii.h" +#include "modem.h" +#include "../hardware/fnUART.h" +#include "fnWiFi.h" +#include "fnFsSPIFFS.h" +#include "fnSystem.h" +#include "../utils/utils.h" +#include "fnConfig.h" +#include "led.h" + +#define RECVBUFSIZE 512 + +#define MODEM_TASK_PRIORITY 10 +#define MODEM_TASK_CPU 0 + +/* Tested this delay several times on an 800 with Incognito + using HSIO routines. Anything much lower gave inconsistent + firmware loading. Delay is unnoticeable when running at + normal speed. +*/ +#define DELAY_FIRMWARE_DELIVERY 5000 + +/** + * List of Telnet options to process + */ +static const telnet_telopt_t telopts[] = { + {TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DO}, + {TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT}, + {TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO}, + {TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO}, + {-1, 0, 0}}; + +/** + * Event handler for libtelnet + */ +static void _telnet_event_handler(telnet_t *telnet, telnet_event_t *ev, void *user_data) +{ + iwmModem *modem = (iwmModem *)user_data; // somehow it thinks this is unused? + + switch (ev->type) + { + case TELNET_EV_DATA: + if (ev->data.size && modem->modem_write((uint8_t *)ev->data.buffer, ev->data.size) != ev->data.size) + Debug_printf("_telnet_event_handler(%d) - Could not write complete buffer to SIO.\n", ev->type); + break; + case TELNET_EV_SEND: + modem->get_tcp_client().write((uint8_t *)ev->data.buffer, ev->data.size); + break; + case TELNET_EV_WILL: + if (ev->neg.telopt == TELNET_TELOPT_ECHO) + modem->set_do_echo(false); + break; + case TELNET_EV_WONT: + if (ev->neg.telopt == TELNET_TELOPT_ECHO) + modem->set_do_echo(true); + break; + case TELNET_EV_DO: + break; + case TELNET_EV_DONT: + break; + case TELNET_EV_TTYPE: + if (ev->ttype.cmd == TELNET_TTYPE_SEND) + telnet_ttype_is(telnet, modem->get_term_type().c_str()); + break; + case TELNET_EV_SUBNEGOTIATION: + break; + case TELNET_EV_ERROR: + Debug_printf("_telnet_event_handler ERROR: %s\n", ev->error.msg); + break; + default: + Debug_printf("_telnet_event_handler: Uncaught event type: %d", ev->type); + break; + } +} + +static void _modem_task(void *arg) +{ + iwmModem *m = (iwmModem *)arg; + + while (true) + { + m->handle_modem(); + vTaskDelay(10); + } +} + +iwmModem::iwmModem(FileSystem *_fs, bool snifferEnable) +{ + activeFS = _fs; + modemSniffer = new ModemSniffer(activeFS, snifferEnable); + set_term_type("dumb"); + telnet = telnet_init(telopts, _telnet_event_handler, 0, this); + mrxq = xQueueCreate(16384, sizeof(char)); + mtxq = xQueueCreate(16384, sizeof(char)); + xTaskCreatePinnedToCore(_modem_task, "modemTask", 4096, this, MODEM_TASK_PRIORITY, &modemTask, MODEM_TASK_CPU); +} + +iwmModem::~iwmModem() +{ + if (modemSniffer != nullptr) + { + delete modemSniffer; + } + + if (telnet != nullptr) + { + telnet_free(telnet); + } + + vTaskDelete(modemTask); + vQueueDelete(mrxq); + vQueueDelete(mtxq); +} + +unsigned short iwmModem::modem_write(uint8_t *buf, unsigned short len) +{ + unsigned short l = 0; + + while (len > 0) + { + xQueueSend(mrxq, &buf[l++], portMAX_DELAY); + len--; + } + + return l; +} + +unsigned short iwmModem::modem_write(char c) +{ + xQueueSend(mrxq, &c, portMAX_DELAY); + return 1; +} + +unsigned short iwmModem::modem_print(const char *s) +{ + unsigned short l = 0; + + while (*s != 0x00) + { + xQueueSend(mrxq, s++, portMAX_DELAY); + l++; + } + + return l; +} + +unsigned short iwmModem::modem_print(std::string s) +{ + return modem_print(s.c_str()); +} + +unsigned short iwmModem::modem_print(int i) +{ + char out[80]; + + itoa(i, out, 10); + + return modem_print(out); +} + +unsigned short iwmModem::modem_read(uint8_t *buf, unsigned short len) +{ + unsigned short i, l = 0; + + for (i = 0; i < len; i++) + l += xQueueReceive(mtxq, &buf[i], portMAX_DELAY); + + return l; +} + +void iwmModem::at_connect_resultCode(int modemBaud) +{ + int resultCode = 0; + switch (modemBaud) + { + case 300: + resultCode = 1; + break; + case 1200: + resultCode = 5; + break; + case 2400: + resultCode = 10; + break; + case 4800: + resultCode = 18; + break; + case 9600: + resultCode = 13; + break; + case 19200: + resultCode = 85; + break; + default: + resultCode = 1; + break; + } + modem_print(resultCode); + modem_write(ASCII_CR); +} + +/** + * Emit result code if ATV0 + * No Atascii translation here, as this is intended for machine reading. + */ +void iwmModem::at_cmd_resultCode(int resultCode) +{ + modem_print(resultCode); + modem_write(ASCII_CR); + modem_write(ASCII_LF); +} + +/** + replacement println for AT that is CR/EOL aware +*/ +void iwmModem::at_cmd_println() +{ + if (cmdOutput == false) + return; + + if (cmdAtascii == true) + { + modem_write(ATASCII_EOL); + } + else + { + modem_write(ASCII_CR); + modem_write(ASCII_LF); + } +} + +void iwmModem::at_cmd_println(const char *s, bool addEol) +{ + if (cmdOutput == false) + return; + + modem_print(s); + if (addEol) + { + if (cmdAtascii == true) + { + modem_write(ATASCII_EOL); + } + else + { + modem_write(ASCII_CR); + modem_write(ASCII_LF); + } + } +} + +void iwmModem::at_cmd_println(int i, bool addEol) +{ + if (cmdOutput == false) + return; + + modem_print(i); + if (addEol) + { + if (cmdAtascii == true) + { + modem_write(ATASCII_EOL); + } + else + { + modem_write(ASCII_CR); + modem_write(ASCII_LF); + } + } +} + +void iwmModem::at_cmd_println(std::string s, bool addEol) +{ + if (cmdOutput == false) + return; + + modem_print(s); + if (addEol) + { + if (cmdAtascii == true) + { + modem_write(ATASCII_EOL); + } + else + { + modem_write(ASCII_CR); + modem_write(ASCII_LF); + } + } +} + +void iwmModem::at_handle_wificonnect() +{ + int keyIndex = cmd.find(','); + std::string ssid, key; + if (keyIndex != std::string::npos) + { + ssid = cmd.substr(13, keyIndex - 13 + 1); + key = cmd.substr(keyIndex + 1); + } + else + { + ssid = cmd.substr(6); + key = ""; + } + + at_cmd_println(HELPWIFICONNECTING, false); + at_cmd_println(ssid, false); + at_cmd_println("/", false); + at_cmd_println(key); + + fnWiFi.connect(ssid.c_str(), key.c_str()); + + int retries = 0; + while ((!fnWiFi.connected()) && retries < 20) + { + fnSystem.delay(1000); + retries++; + at_cmd_println(".", false); + } + if (retries >= 20) + { + if (numericResultCode == true) + { + at_cmd_resultCode(RESULT_CODE_ERROR); + } + else + { + at_cmd_println("ERROR"); + } + } + else + { + if (numericResultCode == true) + { + at_cmd_resultCode(RESULT_CODE_OK); + } + else + { + at_cmd_println("OK"); + } + } +} + +void iwmModem::at_handle_port() +{ + // int port = cmd.substring(6).toInt(); + int port = std::stoi(cmd.substr(6)); + if (port > 65535 || port < 0) + { + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_ERROR); + else + at_cmd_println("ERROR"); + } + else + { + if (listenPort != 0) + { + tcpClient.stop(); + tcpServer.stop(); + } + + listenPort = port; + tcpServer.setMaxClients(1); + tcpServer.begin(listenPort); + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + } +} + +void iwmModem::at_handle_get() +{ + // From the URL, acquire required variables + // (12 = "ATGEThttp://") + int portIndex = cmd.find(':', 12); // Index where port number might begin + int pathIndex = cmd.find('/', 12); // Index first host name and possible port ends and path begins + int port; + std::string path, host; + if (pathIndex < 0) + { + pathIndex = cmd.length(); + } + if (portIndex < 0) + { + port = 80; + portIndex = pathIndex; + } + else + { + // port = cmd.substring(portIndex + 1, pathIndex).toInt(); + port = std::stoi(cmd.substr(portIndex + 1, pathIndex - (portIndex + 1) + 1)); + } + // host = cmd.substring(12, portIndex); + host = cmd.substr(12, portIndex - 12 + 1); + // path = cmd.substring(pathIndex, cmd.length()); + path = cmd.substr(pathIndex); + if (path.empty()) + path = "/"; + + // Establish connection + if (!tcpClient.connect(host.c_str(), port)) + { + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_NO_CARRIER); + else + at_cmd_println("NO CARRIER"); + telnet_free(telnet); + telnet = telnet_init(telopts, _telnet_event_handler, 0, this); + CRX = false; + } + else + { + if (numericResultCode == true) + { + at_connect_resultCode(modemBaud); + CRX = true; + } + else + { + at_cmd_println("CONNECT ", false); + at_cmd_println(modemBaud); + CRX = true; + } + + cmdMode = false; + + // Send a HTTP request before continuing the connection as usual + std::string request = "GET "; + request += path; + request += " HTTP/1.1\r\nHost: "; + request += host; + request += "\r\nConnection: close\r\n\r\n"; + tcpClient.write(request); + } +} + +void iwmModem::at_handle_help() +{ + at_cmd_println(HELPL01); + at_cmd_println(HELPL02); + at_cmd_println(HELPL03); + at_cmd_println(HELPL04); + at_cmd_println(HELPL05); + at_cmd_println(HELPL06); + at_cmd_println(HELPL07); + at_cmd_println(HELPL08); + at_cmd_println(HELPL09); + at_cmd_println(HELPL10); + at_cmd_println(HELPL11); + at_cmd_println(HELPL12); + at_cmd_println(HELPL13); + at_cmd_println(HELPL14); + at_cmd_println(HELPL15); + at_cmd_println(HELPL16); + at_cmd_println(HELPL17); + at_cmd_println(HELPL18); + at_cmd_println(HELPL19); + at_cmd_println(HELPL20); + at_cmd_println(HELPL21); + at_cmd_println(HELPL22); + at_cmd_println(HELPL23); + at_cmd_println(HELPL24); + at_cmd_println(HELPL25); + at_cmd_println(HELPL26); + + at_cmd_println(); + + if (listenPort > 0) + { + at_cmd_println(HELPPORT1, false); + at_cmd_println(listenPort); + at_cmd_println(HELPPORT2); + at_cmd_println(HELPPORT3); + } + else + { + at_cmd_println(HELPPORT4); + } + at_cmd_println(); + + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); +} + +void iwmModem::at_handle_wifilist() +{ + at_cmd_println(); + at_cmd_println(HELPSCAN1); + + int n = fnWiFi.scan_networks(); + + at_cmd_println(); + + if (n == 0) + { + at_cmd_println(HELPSCAN2); + } + else + { + at_cmd_println(n, false); + at_cmd_println(HELPSCAN3); + at_cmd_println(); + + char ssid[33]; + char bssid[18]; + uint8_t rssi; + uint8_t channel; + uint8_t encryption; + + for (int i = 0; i < n; ++i) + { + // Print SSID and RSSI for each network found + fnWiFi.get_scan_result(i, ssid, &rssi, &channel, bssid, &encryption); + at_cmd_println(i + 1, false); + at_cmd_println(": ", false); + at_cmd_println(ssid, false); + at_cmd_println(" [", false); + at_cmd_println(channel, false); + at_cmd_println("/", false); + at_cmd_println(rssi, false); + at_cmd_println("]"); + at_cmd_println(" ", false); + at_cmd_println(bssid, false); + at_cmd_println(encryption == WIFI_AUTH_OPEN ? HELPSCAN4 : HELPSCAN5); + } + } + at_cmd_println(); + + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); +} + +void iwmModem::at_handle_answer() +{ + Debug_printf("HANDLE ANSWER !!!\n"); + if (tcpServer.hasClient()) + { + tcpClient = tcpServer.available(); + tcpClient.setNoDelay(true); // try to disable naggle + // tcpServer.stop(); + answerTimer = fnSystem.millis(); + answered = false; + CRX = true; + + cmdMode = false; + answerHack = false; + } +} + +void iwmModem::at_handle_dial() +{ + int portIndex = cmd.find(':'); + std::string host, port; + std::string hostpb; + if (portIndex != std::string::npos) + { + host = cmd.substr(4, portIndex - 4); + port = cmd.substr(portIndex + 1); + } + else + { + host = cmd.substr(4); + port = "23"; // Telnet default + } + + util_string_trim(host); // allow spaces or no spaces after AT command + + Debug_printf("DIALING: %s\n", host.c_str()); + + /*Phonebook Entry?, check first if the only numeric host*/ + if (host.find_first_not_of("0123456789") == std::string::npos) + { + hostpb = Config.get_pb_host_name(host.c_str()); + /*Check if the number is in the phonebook*/ + if (!hostpb.empty()) + { + + /*replace host:port with phonebook information*/ + port = Config.get_pb_host_port(host.c_str()); + host = hostpb; + } + } + + if (host == "5551234") // Fake it for BobTerm + { + CRX = true; + answered = false; + answerTimer = fnSystem.millis(); + // This is so macros in Bobterm can do the actual connect. + fnSystem.delay(ANSWER_TIMER_MS); + at_cmd_println("CONNECT ", false); + at_cmd_println(modemBaud); + } + else + { + at_cmd_println("Connecting to ", false); + at_cmd_println(host, false); + at_cmd_println(":", false); + at_cmd_println(port); + + int portInt = std::stoi(port); + + if (tcpClient.connect(host.c_str(), portInt)) + { + tcpClient.setNoDelay(true); // Try to disable naggle + answered = false; + answerTimer = fnSystem.millis(); + cmdMode = false; + } + else + { + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_NO_CARRIER); + else + at_cmd_println("NO CARRIER"); + CRX = false; + telnet_free(telnet); + telnet = telnet_init(telopts, _telnet_event_handler, 0, this); + } + } +} +/*Following functions manage the phonebook*/ +/*Display current Phonebook*/ +void iwmModem::at_handle_pblist() +{ + at_cmd_println(); + at_cmd_println("Phone# Host"); + for (int i = 0; i < MAX_PB_SLOTS; ++i) + { + // Check if empty + std::string pbEntry = Config.get_pb_entry(i); + if (!pbEntry.empty()) + at_cmd_println(pbEntry); + } + at_cmd_println(); + + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); +} + +/*Add and del entry in the phonebook*/ +void iwmModem::at_handle_pb() +{ + // From the AT command get the info to add. Ex: atpb4321=irata.online:8002 + // or delete ex: atpb4321 + // ("ATPB" length 4) + std::string phnumber, host, port; + int hostIndex = cmd.find('='); + int portIndex = cmd.find(':'); + + // Equal symbol found, so assume adding entry + if (hostIndex != std::string::npos) + { + phnumber = cmd.substr(4, hostIndex - 4); + // Check pure numbers entry + if (phnumber.find_first_not_of("0123456789") == std::string::npos) + { + if (portIndex != std::string::npos) + { + host = cmd.substr(hostIndex + 1, portIndex - hostIndex - 1); + port = cmd.substr(portIndex + 1); + } + else + { + host = cmd.substr(hostIndex + 1); + port = "23"; + } + if (Config.add_pb_number(phnumber.c_str(), host.c_str(), port.c_str())) + { + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + } + else + { + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_ERROR); + else + at_cmd_println("ERROR"); + } + } + else + { + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_ERROR); + else + at_cmd_println("ERROR"); + } + } + // No Equal symbol present, so Delete an entry + else + { + std::string phnumber = cmd.substr(4); + if (Config.del_pb_number(phnumber.c_str())) + { + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + } + else + { + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_ERROR); + else + at_cmd_println("ERROR"); + } + } +} + +/* + Perform a command given in AT Modem command mode +*/ +void iwmModem::modemCommand() +{ + /* Some of these are ignored; to see their meanings, + * review `modem.h`'s sioModem class's _at_cmds enums. */ + static const char *at_cmds[_at_cmds::AT_ENUMCOUNT] = + { + "AT", + "ATNET0", + "ATNET1", + "ATA", + "ATIP", + "AT?", + "ATH", + "+++ATH", + "ATDT", + "ATDP", + "ATDI", + "ATWIFILIST", + "ATWIFICONNECT", + "ATGET", + "ATPORT", + "ATV0", + "ATV1", + "AT&F", + "ATS0=0", + "ATS0=1", + "ATS2=43", + "ATS5=8", + "ATS6=2", + "ATS7=30", + "ATS12=20", + "ATE0", + "ATE1", + "ATM0", + "ATM1", + "ATX1", + "AT&C1", + "AT&D2", + "AT&W", + "ATH2", + "+++ATZ", + "ATS2=128 X1 M0", + "AT+SNIFF", + "AT-SNIFF", + "AT+TERM=VT52", + "AT+TERM=VT100", + "AT+TERM=ANSI", + "AT+TERM=DUMB", + "ATCPM", + "ATPBLIST", + "ATPBCLEAR", + "ATPB", + "ATO"}; + + // cmd.trim(); + util_string_trim(cmd); + if (cmd.empty()) + return; + + std::string upperCaseCmd = cmd; + // upperCaseCmd.toUpperCase(); + util_string_toupper(upperCaseCmd); + + if (commandEcho == true) + at_cmd_println(); + + Debug_printf("AT Cmd: %s\n", upperCaseCmd.c_str()); + + // Replace EOL with CR + // if (upperCaseCmd.indexOf(ATASCII_EOL) != 0) + // upperCaseCmd[upperCaseCmd.indexOf(ATASCII_EOL)] = ASCII_CR; + int eol1 = upperCaseCmd.find(ATASCII_EOL); + if (eol1 != std::string::npos) + upperCaseCmd[eol1] = ASCII_CR; + + // Just AT + int cmd_match = AT_ENUMCOUNT; + if (upperCaseCmd.compare("AT") == 0) + { + cmd_match = AT_AT; + } + else + { + // Make sure we skip the plain AT command when matching + for (cmd_match = _at_cmds::AT_AT + 1; cmd_match < _at_cmds::AT_ENUMCOUNT; cmd_match++) + if (upperCaseCmd.compare(0, strlen(at_cmds[cmd_match]), at_cmds[cmd_match]) == 0) + break; + } + + switch (cmd_match) + { + // plain AT + case AT_AT: + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_OFFHOOK: // Off hook, should be ignored. + // hangup + case AT_H: + case AT_H1: + if (tcpClient.connected() == true) + { + tcpClient.flush(); + tcpClient.stop(); + cmdMode = true; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_NO_CARRIER); + else + at_cmd_println("NO CARRIER"); + telnet_free(telnet); + telnet = telnet_init(telopts, _telnet_event_handler, 0, this); + + CRX = false; + + if (listenPort > 0) + { + // tcpServer.stop(); + // tcpServer.begin(listenPort); + } + } + else + { + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + } + break; + // dial to host + case AT_DT: + case AT_DP: + case AT_DI: + at_handle_dial(); + break; + case AT_WIFILIST: + at_handle_wifilist(); + break; + case AT_WIFICONNECT: + at_handle_wificonnect(); + break; + // Change telnet mode + case AT_NET0: + use_telnet = false; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_NET1: + use_telnet = true; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_A: + at_handle_answer(); + break; + // See my IP address + case AT_IP: + if (fnWiFi.connected()) + at_cmd_println(fnSystem.Net.get_ip4_address_str()); + else + at_cmd_println(HELPNOWIFI); + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_HELP: + at_handle_help(); + break; + case AT_GET: + at_handle_get(); + break; + case AT_PORT: + at_handle_port(); + break; + case AT_V0: + at_cmd_resultCode(RESULT_CODE_OK); + numericResultCode = true; + break; + case AT_V1: + at_cmd_println("OK"); + numericResultCode = false; + break; + case AT_S0E0: + autoAnswer = false; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_S0E1: + autoAnswer = true; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_E0: + commandEcho = false; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_E1: + commandEcho = true; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_ANDF_ignored: // These are all ignored. + case AT_S2E43_ignored: + case AT_S5E8_ignored: + case AT_S6E2_ignored: + case AT_S7E30_ignored: + case AT_S12E20_ignored: + case AT_M0_ignored: + case AT_M1_ignored: + case AT_X1_ignored: + case AT_AC1_ignored: + case AT_AD2_ignored: + case AT_AW_ignored: + case AT_ZPPP_ignored: + case AT_BBSX_ignored: + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_SNIFF: + get_modem_sniffer()->setEnable(true); + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_UNSNIFF: + get_modem_sniffer()->setEnable(false); + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_TERMVT52: + term_type = "VT52"; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_TERMVT100: + term_type = "VT100"; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_TERMDUMB: + term_type = "DUMB"; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_TERMANSI: + term_type = "ANSI"; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_CPM: + break; + case AT_PHONEBOOKLIST: + at_handle_pblist(); + break; + case AT_PHONEBOOK: + at_handle_pb(); + break; + case AT_PHONEBOOKCLR: + Config.clear_pb(); + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_OK); + else + at_cmd_println("OK"); + break; + case AT_O: + if (tcpClient.connected()) + { + if (numericResultCode == true) + { + at_cmd_resultCode(modemBaud); + } + else + { + at_cmd_println("CONNECT ", false); + at_cmd_println(modemBaud); + } + cmdMode = false; + } + else + { + if (numericResultCode == true) + { + at_cmd_resultCode(RESULT_CODE_OK); + } + else + { + at_cmd_println("OK"); + } + } + break; + default: + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_ERROR); + else + at_cmd_println("ERROR"); + break; + } + + cmd = ""; +} + +/* + Handle incoming & outgoing data for modem +*/ +void iwmModem::handle_modem() +{ + /**** AT command mode ****/ + if (cmdMode == true) + { + if (answerHack == true) + { + Debug_printf("XXX ANSWERHACK !!! SENDING ATA! "); + cmd = "ATA"; + modemCommand(); + answerHack = false; + return; + } + + // In command mode but new unanswered incoming connection on server listen socket + if ((listenPort > 0) && (tcpServer.hasClient())) + { + if (autoAnswer == true) + { + at_handle_answer(); + } + else + { + // Print RING every now and then while the new incoming connection exists + if ((fnSystem.millis() - lastRingMs) > RING_INTERVAL) + { + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_RING); + else + at_cmd_println("RING"); + lastRingMs = fnSystem.millis(); + } + } + } + + // In command mode - don't exchange with TCP but gather characters to a string + // if (SIO_UART.available() /*|| blockWritePending == true */ ) + if (uxQueueMessagesWaiting(mtxq)) + { + char chr; + + xQueueReceive(mtxq, &chr, portMAX_DELAY); + + // Return, enter, new line, carriage return.. anything goes to end the command + if ((chr == ASCII_LF) || (chr == ASCII_CR) || (chr == ATASCII_EOL)) + { + // flip which EOL to display based on last CR or EOL received. + if (chr == ATASCII_EOL) + cmdAtascii = true; + else + cmdAtascii = false; + + modemCommand(); + } + // Backspace or delete deletes previous character + else if ((chr == ASCII_BACKSPACE) || (chr == ASCII_DELETE)) + { + size_t len = cmd.length(); + + if (len > 0) + { + cmd.erase(len - 1); + // We don't assume that backspace is destructive + // Clear with a space + if (commandEcho == true) + { + modem_write(ASCII_BACKSPACE); + modem_write(' '); + modem_write(ASCII_BACKSPACE); + } + } + } + else if (chr == ATASCII_BACKSPACE) + { + size_t len = cmd.length(); + + // ATASCII backspace + if (len > 0) + { + cmd.erase(len - 1); + if (commandEcho == true) + modem_write(ATASCII_BACKSPACE); + } + } + // Take into account arrow key movement and clear screen + else if (chr == ATASCII_CLEAR_SCREEN || + ((chr >= ATASCII_CURSOR_UP) && (chr <= ATASCII_CURSOR_RIGHT))) + { + if (commandEcho == true) + modem_write(chr); + } + else + { + if (cmd.length() < MAX_CMD_LENGTH) + { + // cmd.concat(chr); + cmd += chr; + } + if (commandEcho == true) + modem_write(chr); + } + } + } + // Connected mode + else + { + // If another client is waiting, accept and turn away. + if (tcpServer.hasClient()) + { + fnTcpClient c = tcpServer.accept(); + c.write("The MODEM is currently serving another caller. Please try again later.\x0d\x0a\x9b"); + c.stop(); + } + + // Emit a CONNECT if we're connected, and a few seconds have passed. + if ((answered == false) && (answerTimer > 0) && ((fnSystem.millis() - answerTimer) > ANSWER_TIMER_MS)) + { + answered = true; + answerTimer = 0; + if (numericResultCode == true) + { + at_cmd_resultCode(modemBaud); + } + else + { + at_cmd_println("CONNECT ", false); + at_cmd_println(modemBaud); + } + } + + int sioBytesAvail = uxQueueMessagesWaiting(mtxq); + + // send from Atari to Fujinet + if (sioBytesAvail && tcpClient.connected()) + { + // In telnet in worst case we have to escape every uint8_t + // so leave half of the buffer always free + // int max_buf_size; + // if (telnet == true) + // max_buf_size = TX_BUF_SIZE / 2; + // else + // max_buf_size = TX_BUF_SIZE; + + // Read from serial, the amount available up to + // maximum size of the buffer + int sioBytesRead = modem_read(&txBuf[0], // SIO_UART.readBytes(&txBuf[0], + (sioBytesAvail > TX_BUF_SIZE) ? TX_BUF_SIZE : sioBytesAvail); + + // Disconnect if going to AT mode with "+++" sequence + for (int i = 0; i < (int)sioBytesRead; i++) + { + if (txBuf[i] == '+') + plusCount++; + else + plusCount = 0; + if (plusCount >= 3) + { + plusTime = fnSystem.millis(); + } + if (txBuf[i] != '+') + { + plusCount = 0; + } + } + + // Write the buffer to TCP finally + if (use_telnet == true) + { + telnet_send(telnet, (const char *)txBuf, sioBytesRead); + } + else + tcpClient.write(&txBuf[0], sioBytesRead); + + // And send it off to the sniffer, if enabled. + modemSniffer->dumpOutput(&txBuf[0], sioBytesRead); + _lasttime = fnSystem.millis(); + } + + // read from Fujinet to Atari + unsigned char buf[RECVBUFSIZE]; + int bytesAvail = 0; + + // check to see how many bytes are avail to read + while ((bytesAvail = tcpClient.available()) > 0) + { + // read as many as our buffer size will take (RECVBUFSIZE) + unsigned int bytesRead = + tcpClient.read(buf, (bytesAvail > RECVBUFSIZE) ? RECVBUFSIZE : bytesAvail); + + if (use_telnet == true) + { + telnet_recv(telnet, (const char *)buf, bytesRead); + } + else + { + modem_write(buf, bytesRead); + } + + // And dump to sniffer, if enabled. + modemSniffer->dumpInput(buf, bytesRead); + _lasttime = fnSystem.millis(); + } + } + + // If we have received "+++" as last bytes from serial port and there + // has been over a second without any more bytes, go back to command mode. + if (plusCount >= 3) + { + if (fnSystem.millis() - plusTime > 1000) + { + Debug_println("Going back to command mode"); + + at_cmd_println("OK"); + + cmdMode = true; + + plusCount = 0; + } + } + + // Go to command mode if TCP disconnected and not in command mode + if (!tcpClient.connected() && (cmdMode == false) && (DTR == 0)) + { + tcpClient.flush(); + tcpClient.stop(); + cmdMode = true; + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_NO_CARRIER); + else + at_cmd_println("NO CARRIER"); + telnet_free(telnet); + telnet = telnet_init(telopts, _telnet_event_handler, 0, this); + CRX = false; + if (listenPort > 0) + { + // tcpServer.stop(); + // tcpServer.begin(listenPort); + } + } + else if ((!tcpClient.connected()) && (cmdMode == false)) + { + cmdMode = true; + telnet_free(telnet); + telnet = telnet_init(telopts, _telnet_event_handler, 0, this); + if (numericResultCode == true) + at_cmd_resultCode(RESULT_CODE_NO_CARRIER); + else + at_cmd_println("NO CARRIER"); + telnet_free(telnet); + telnet = telnet_init(telopts, _telnet_event_handler, 0, this); + CRX = false; + if (listenPort > 0) + { + // tcpServer.stop(); + // tcpServer.begin(listenPort); + } + } +} + +void iwmModem::shutdown() +{ + if (modemSniffer != nullptr) + if (modemSniffer->getEnable()) + modemSniffer->closeOutput(); +} + +void iwmModem::send_status_reply_packet() +{ + uint8_t data[4]; + + // Build the contents of the packet + data[0] = STATCODE_READ_ALLOWED | STATCODE_WRITE_ALLOWED | STATCODE_DEVICE_ONLINE; + data[1] = 0; // block size 1 + data[2] = 0; // block size 2 + data[3] = 0; // block size 3 + IWM.iwm_send_packet(id(), iwm_packet_type_t::status, SP_ERR_NOERROR, data, 4); +} + +void iwmModem::send_status_dib_reply_packet() +{ + uint8_t data[25]; + + //* write data buffer first (25 bytes) 3 grp7 + 4 odds + // General Status byte + // Bit 7: Block device + // Bit 6: Write allowed + // Bit 5: Read allowed + // Bit 4: Device online or disk in drive + // Bit 3: Format allowed + // Bit 2: Media write protected (block devices only) + // Bit 1: Currently interrupting (//c only) + // Bit 0: Currently open (char devices only) + data[0] = STATCODE_READ_ALLOWED | STATCODE_DEVICE_ONLINE; + data[1] = 0; // block size 1 + data[2] = 0; // block size 2 + data[3] = 0; // block size 3 + data[4] = 0x05; // ID string length - 11 chars + data[5] = 'M'; + data[6] = 'O'; + data[7] = 'D'; + data[8] = 'E'; + data[9] = 'M'; + data[10] = ' '; + data[11] = ' '; + data[12] = ' '; + data[13] = ' '; + data[14] = ' '; + data[15] = ' '; + data[16] = ' '; + data[17] = ' '; + data[18] = ' '; + data[19] = ' '; + data[20] = ' '; // ID string (16 chars total) + data[21] = SP_TYPE_BYTE_FUJINET_MODEM; // Device type - 0x02 harddisk + data[22] = SP_SUBTYPE_BYTE_FUJINET_MODEM; // Device Subtype - 0x0a + data[23] = 0x00; // Firmware version 2 bytes + data[24] = 0x01; // + IWM.iwm_send_packet(id(), iwm_packet_type_t::status, SP_ERR_NOERROR, data, 25); +} + +void iwmModem::iwm_open(iwm_decoded_cmd_t cmd) +{ + Debug_printf("\nModem: Open\n"); + send_reply_packet(SP_ERR_NOERROR); +} + +void iwmModem::iwm_close(iwm_decoded_cmd_t cmd) +{ + Debug_printf("\nModem: Close\n"); + + if (tcpClient.connected() == true) + { + tcpClient.flush(); + tcpClient.stop(); + } + + send_reply_packet(SP_ERR_NOERROR); +} + +void iwmModem::iwm_read(iwm_decoded_cmd_t cmd) +{ + uint16_t numbytes = get_numbytes(cmd); // cmd.g7byte3 & 0x7f) | ((cmd.grp7msb << 3) & 0x80); + uint32_t addy = get_address(cmd); // (cmd.g7byte5 & 0x7f) | ((cmd.grp7msb << 5) & 0x80); + unsigned short mw = uxQueueMessagesWaiting(mrxq); + + Debug_printf("\r\nDevice %02x READ %04x bytes from address %06x\n", id(), numbytes, addy); + + memset(data_buffer, 0, sizeof(data_buffer)); + + if (mw) // check if we really have some bytes waiting + { + if (mw < numbytes) //if there are less than requested, just send what we have + { + numbytes = mw; + } + + data_len = 0; + for (int i = 0; i < numbytes; i++) + { + char b; + xQueueReceive(mrxq, &b, portMAX_DELAY); + data_buffer[i] = b; + data_len++; + } + } + else // no bytes waiting, just reply back with no data + { + data_len = 0; + } + + Debug_printf("\r\nsending Modem read data packet ..."); + IWM.iwm_send_packet(id(), iwm_packet_type_t::data, 0, data_buffer, data_len); + data_len = 0; + memset(data_buffer, 0, sizeof(data_buffer)); +} + +void iwmModem::iwm_write(iwm_decoded_cmd_t cmd) +{ + uint16_t num_bytes = get_numbytes(cmd); // (cmd.g7byte3 & 0x7f) | ((cmd.grp7msb << 3) & 0x80); + + Debug_printf("\nWRITE %u bytes\n", num_bytes); + + // get write data packet, keep trying until no timeout + // to do - this blows up - check handshaking + + data_len = num_bytes; + IWM.iwm_decode_data_packet(data_buffer, data_len); + // if (IWM.iwm_decode_data_packet(100, data_buffer, data_len)) // write data packet now read in ISR + // { + // Debug_printf("\r\nTIMEOUT in read packet!"); + // return; + // } + + { + // DO write + for (int i = 0; i < num_bytes; i++) + xQueueSend(mtxq, &data_buffer[i], portMAX_DELAY); + } + + send_reply_packet(SP_ERR_NOERROR); +} + +void iwmModem::iwm_ctrl(iwm_decoded_cmd_t cmd) +{ + uint8_t err_result = SP_ERR_NOERROR; + + uint8_t control_code = get_status_code(cmd); // (cmd.g7byte3 & 0x7f) | ((cmd.grp7msb << 3) & 0x80); // ctrl codes 00-FF + Debug_printf("\r\nModem Device %02x Control Code %02x", id(), control_code); + data_len = 512; + IWM.iwm_decode_data_packet(data_buffer, data_len); + print_packet(data_buffer,data_len); + + // if (data_len > 0) + // switch (control_code) + // { + // } + // else + // err_result = SP_ERR_IOERROR; + + Debug_printf("\nSending Control Reply"); + send_reply_packet(err_result); +} + +void iwmModem::iwm_modem_status() +{ + unsigned short mw = uxQueueMessagesWaiting(mrxq); + + //if (mw > 512) + // mw = 512; + + data_buffer[0] = mw & 0xFF; + data_buffer[1] = mw >> 8; + data_len = 2; + Debug_printf("--- %u bytes waiting\n", mw); +} + +void iwmModem::iwm_status(iwm_decoded_cmd_t cmd) +{ + // uint8_t source = cmd.dest; // we are the destination and will become the source // packet_buffer[6]; + uint8_t status_code = get_status_code(cmd); // (cmd.g7byte3 & 0x7f) | ((cmd.grp7msb << 3) & 0x80); // status codes 00-FF + Debug_printf("\r\nDevice %02x Status Code %02x\n", id(), status_code); + // Debug_printf("\r\nStatus List is at %02x %02x\n", cmd.g7byte1 & 0x7f, cmd.g7byte2 & 0x7f); + + switch (status_code) + { + case IWM_STATUS_STATUS: // 0x00 + send_status_reply_packet(); + return; + break; + case IWM_STATUS_DIB: // 0x03 + send_status_dib_reply_packet(); + return; + break; + case 'S': // Status + iwm_modem_status(); + break; + } + + Debug_printf("\r\nStatus code complete, sending response"); + IWM.iwm_send_packet(id(), iwm_packet_type_t::data, 0, data_buffer, data_len); +} + +void iwmModem::process(iwm_decoded_cmd_t cmd) +{ + switch (cmd.command) + { + case 0x00: // status + Debug_printf("\r\nhandling status command"); + iwm_status(cmd); + break; + case 0x04: // control + Debug_printf("\r\nhandling control command"); + iwm_ctrl(cmd); + Debug_printf("\r\ncontrol command done"); + break; + case 0x06: // open + Debug_printf("\r\nhandling open command"); + iwm_open(cmd); + break; + case 0x07: // close + Debug_printf("\r\nhandling close command"); + iwm_close(cmd); + break; + case 0x08: // read + Debug_printf("\r\nhandling read command"); + fnLedManager.set(LED_BUS, true); + iwm_read(cmd); + fnLedManager.set(LED_BUS, false); + break; + case 0x09: // write + Debug_printf("\r\nhandling write command"); + fnLedManager.set(LED_BUS, true); + iwm_write(cmd); + fnLedManager.set(LED_BUS, true); + break; + default: + iwm_return_badcmd(cmd); + break; + } // switch (cmd) + fnLedManager.set(LED_BUS, false); +} + +#endif /* BUILD_APPLE */ \ No newline at end of file diff --git a/lib/device/mac/modem.h b/lib/device/mac/modem.h new file mode 100644 index 000000000..c7e18b464 --- /dev/null +++ b/lib/device/mac/modem.h @@ -0,0 +1,291 @@ +#ifdef BUILD_MAC +#ifndef MAC_MODEM_H +#define MAC_MODEM_H + +#include "bus.h" +#include "fnSystem.h" + +#include "modem-sniffer.h" + +class macModem : public macDevice +{ +private: + FileSystem *activeFS; // Active Filesystem for ModemSniffer. + ModemSniffer *modemSniffer; // ptr to modem sniffer. +public: + macModem(FileSystem *_fs, bool snifferEnable); + virtual ~macModem(); + + ModemSniffer *get_modem_sniffer() { return modemSniffer; }; + time_t get_last_activity_time() { return fnSystem.millis(); }; + + void shutdown()override {}; + void process(mac_cmd_t cmd) override {}; +}; + +#endif // guard +#endif // BUILD_MAC + +#if 0 +#ifndef APPLE_MODEM_H +#define APPLE_MODEM_H + +#include + +#include +#include + +#include "bus.h" + +#include "fnTcpServer.h" +#include "fnTcpClient.h" +#include "modem-sniffer.h" +#include "../telnet/libtelnet.h" + +/* Keep strings under 40 characters, for the benefit of 40-column users! */ +#define HELPL01 " FujiNet Virtual apple Modem" +#define HELPL02 "=======================================" +#define HELPL03 "" +#define HELPL04 "ATWIFILIST | List avail networks" +#define HELPL05 "ATWIFICONNECT," +#define HELPL06 " | Connect to WiFi net" +#define HELPL07 "ATDT: | Connect by TCP" +#define HELPL08 "ATIP | See my IP address" +#define HELPL09 "ATNET<0|1> | Dis/enable TELNET" +#define HELPL10 " | command handling" +#define HELPL11 "ATPORT | Set listening port" +#define HELPL12 "ATS0=<0|1> | Auto-answer in-" +#define HELPL13 " | coming connections" +#define HELPL14 "ATGET | HTTP GET" +#define HELPL15 "AT+TERM=| Set telnet term" +#define HELPL16 " | type (DUMB, VT52," +#define HELPL17 " | VT100, ANSI)" +#define HELPL18 "AT[UN]SNIFF | Dis/enable sniffing" +#define HELPL19 "ATTERMVT52 | Set TERM to VT52" +#define HELPL20 "ATTERMVT100 | Set TERM to VT100" +#define HELPL21 "ATTERMDUMB | Set TERM to DUMB" +#define HELPL22 "ATTERMANSI | Set TERM to ANSI" +#define HELPL23 "ATCPM | Go into CP/M" +#define HELPL24 "ATPHONEBOOKLIST | List Phonebook" +#define HELPL25 "ATPHONEBOOKCLR | Clear Phonebook" +#define HELPL26 "ATPB= | Add to Phonebook" + +/* Not explicitly mentioned at this time, since they are commonly known: + * (these are sioModem class's _at_cmds enums) + * - AT + * - ATA (mentioned below) + * - AT? (the help command itself) + * - AT_H / AT_H1 / AT_OFFHOOK (hangup) + * - AT_E0 / AT_E1 (echo off/on) + * - AT_V0 / AT_V1 (verbose off/on) +*/ + +#define HELPPORT1 "Listening to connections on port " +#define HELPPORT2 "which result in RING that you can" +#define HELPPORT3 "answer with ATA." +#define HELPPORT4 "No incoming connections are enabled." + +#define HELPSCAN1 "Scanning..." +#define HELPSCAN2 "No networks found" +#define HELPSCAN3 " networks found:" +#define HELPSCAN4 " (open)" +#define HELPSCAN5 " (encrypted)" + +#define HELPNOWIFI "WiFi is not connected." +#define HELPWIFICONNECTING "Connecting to " + +#define RING_INTERVAL 3000 // How often to print RING when having a new incoming connection (ms) +#define MAX_CMD_LENGTH 256 // Maximum length for AT command +#define TX_BUF_SIZE 256 // Buffer where to read from serial before writing to TCP (that direction is very blocking by the ESP TCP stack, so we can't do one byte a time.) + +#define ANSWER_TIMER_MS 1000 // milliseconds to wait before issuing CONNECT command, to simulate carrier negotiation. + +class iwmModem : public iwmDevice +{ +private: + +#define RESULT_CODE_OK 0 +#define RESULT_CODE_CONNECT 1 +#define RESULT_CODE_RING 2 +#define RESULT_CODE_NO_CARRIER 3 +#define RESULT_CODE_ERROR 4 +#define RESULT_CODE_CONNECT_1200 5 +#define RESULT_CODE_BUSY 7 +#define RESULT_CODE_NO_ANSWER 8 +#define RESULT_CODE_CONNECT_2400 10 +#define RESULT_CODE_CONNECT_9600 13 +#define RESULT_CODE_CONNECT_4800 18 +#define RESULT_CODE_CONNECT_19200 85 + + /* The actual strings expected for these can be + * found in `modem.cpp`'s at_cmds[] array. */ + enum _at_cmds + { + AT_AT = 0, + AT_NET0, + AT_NET1, + AT_A, + AT_IP, + AT_HELP, + AT_H, + AT_H1, + AT_DT, + AT_DP, + AT_DI, + AT_WIFILIST, + AT_WIFICONNECT, + AT_GET, + AT_PORT, + AT_V0, + AT_V1, + AT_ANDF_ignored, + AT_S0E0, + AT_S0E1, + AT_S2E43_ignored, + AT_S5E8_ignored, + AT_S6E2_ignored, + AT_S7E30_ignored, + AT_S12E20_ignored, + AT_E0, + AT_E1, + AT_M0_ignored, + AT_M1_ignored, + AT_X1_ignored, + AT_AC1_ignored, + AT_AD2_ignored, + AT_AW_ignored, + AT_OFFHOOK, + AT_ZPPP_ignored, + AT_BBSX_ignored, + AT_SNIFF, + AT_UNSNIFF, + AT_TERMVT52, + AT_TERMVT100, + AT_TERMDUMB, + AT_TERMANSI, + AT_CPM, + AT_PHONEBOOKLIST, + AT_PHONEBOOKCLR, + AT_PHONEBOOK, + AT_O, + AT_ENUMCOUNT}; + + uint modemBaud = 300; // Holds modem baud rate, Default 300 + bool DTR = false; + bool RTS = false; + bool XMT = false; + bool baudLock = false; // lock modem baud rate from further changes. + + int count_PollType1 = 0; // Keep track of how many times we've seen command 0x3F + int count_PollType3 = 0; + + int count_ReqRelocator = 0; + int count_ReqHandler = 0; + bool firmware_sent = false; + + QueueHandle_t mrxq; + QueueHandle_t mtxq; + TaskHandle_t modemTask; + + /* Modem Active Variables */ + std::string cmd = ""; // Gather a new AT command to this string from serial + bool cmdMode = true; // Are we in AT command mode or connected mode + bool cmdAtascii = false; // last CMD contained an ATASCII EOL? + unsigned short listenPort = 0; // Listen to this if not connected. Set to zero to disable. + fnTcpClient tcpClient; // Modem client + fnTcpServer tcpServer; // Modem server + unsigned long lastRingMs = 0; // Time of last "RING" message (millis()) + char plusCount = 0; // Go to AT mode at "+++" sequence, that has to be counted + unsigned long plusTime = 0; // When did we last receive a "+++" sequence + uint8_t txBuf[TX_BUF_SIZE]; + bool cmdOutput=true; // toggle whether to emit command output + bool numericResultCode=false; // Use numeric result codes? (ATV0) + bool autoAnswer=false; // Auto answer? (ATS0?) + bool commandEcho=true; // Echo MODEM input. (ATEx) + bool CRX=false; // CRX flag. + uint8_t mdmStatus[2] = {0x00, 0x00}; // modem status value + bool answerHack=false; // ATA answer hack on SIO write. + FileSystem *activeFS; // Active Filesystem for ModemSniffer. + ModemSniffer* modemSniffer; // ptr to modem sniffer. + time_t _lasttime; // most recent timestamp of data activity. + telnet_t *telnet; // telnet FSM state. + bool use_telnet=false; // Use telnet mode? + bool do_echo; // telnet echo toggle. + std::string term_type; // telnet terminal type. + long answerTimer; + bool answered=false; + + void shutdown() override; + void process(iwm_decoded_cmd_t cmd) override; + + void iwm_ctrl(iwm_decoded_cmd_t cmd) override; + void iwm_open(iwm_decoded_cmd_t cmd) override; + void iwm_close(iwm_decoded_cmd_t cmd) override; + void iwm_read(iwm_decoded_cmd_t cmd) override; + void iwm_write(iwm_decoded_cmd_t cmd) override; + void iwm_status(iwm_decoded_cmd_t cmd) override; + + void iwm_modem_status(); + + void send_status_reply_packet() override; + void send_status_dib_reply_packet() override; + + void send_extended_status_reply_packet() override{}; + void send_extended_status_dib_reply_packet() override{}; + + void crx_toggle(bool toggle); // CRX active/inactive? + + void modemCommand(); // Execute modem AT command + + // CR/EOL aware println() functions for AT mode + void at_connect_resultCode(int modemBaud); + void at_cmd_resultCode(int resultCode); + void at_cmd_println(); + void at_cmd_println(const char *s, bool addEol = true); + void at_cmd_println(int i, bool addEol = true); + void at_cmd_println(std::string s, bool addEol = true); + + // Command handlers + void at_handle_answer(); + void at_handle_dial(); + void at_handle_wifilist(); + void at_handle_wificonnect(); + void at_handle_help(); + void at_handle_get(); + void at_handle_port(); + void at_handle_pblist(); + void at_handle_pb(); + void at_handle_pbclear(); + +protected: + +public: + + bool modemActive = false; // If we are in modem mode or not + void handle_modem(); // Handle incoming & outgoing data for modem + + iwmModem(FileSystem *_fs, bool snifferEnable); + virtual ~iwmModem(); + + time_t get_last_activity_time() { return _lasttime; } // timestamp of last input or output. + ModemSniffer *get_modem_sniffer() { return modemSniffer; } + fnTcpClient get_tcp_client() { return tcpClient; } // Return TCP client. + bool get_do_echo() { return do_echo; } + void set_do_echo(bool _do_echo) { do_echo = _do_echo; } + std::string get_term_type() {return term_type; } + void set_term_type(std::string _term_type) { term_type = _term_type; } + + // Low level routines to write to modem IWM queue + unsigned short modem_write(uint8_t* buf, unsigned short len); + unsigned short modem_write(char c); + unsigned short modem_print(const char *s); + unsigned short modem_print(std::string s); + unsigned short modem_print(int i); + + unsigned short modem_read(uint8_t *buf, unsigned short len); + +// virtual void startup_hack() override {}; +}; + +#endif /* apple_MODEM_H */ +#endif /* BUILD_APPLE */ \ No newline at end of file diff --git a/lib/device/mac/printer.cpp b/lib/device/mac/printer.cpp new file mode 100644 index 000000000..123de7632 --- /dev/null +++ b/lib/device/mac/printer.cpp @@ -0,0 +1,89 @@ +#ifdef BUILD_MAC +#include "printer.h" + +#include "file_printer.h" +#include "html_printer.h" +#include "epson_80.h" +#include "fnSystem.h" + +constexpr const char *const macPrinter::printer_model_str[PRINTER_INVALID]; + +macPrinter::macPrinter(FileSystem *filesystem, printer_type printer_type) +{ + _storage = filesystem; + set_printer_type(printer_type); +} + +macPrinter::~macPrinter() +{ + delete _pptr; +} + +/** + * Print from CP/M, which is one character...at...a...time... + */ +void macPrinter::print_from_cpm(uint8_t c) +{ + _pptr->provideBuffer()[_llen++]=c; + + if (c == 0x0D || c == 0x0a || _llen == 80) + { + _last_ms = fnSystem.millis(); + _pptr->process(_llen, 0, 0); + _llen = 0; + } +} + +void macPrinter::process(mac_cmd_t cmd) +{ +} + +void macPrinter::set_printer_type(macPrinter::printer_type printer_type) +{ + // Destroy any current printer emu object + delete _pptr; + + _ptype = printer_type; + switch (printer_type) + { + case PRINTER_FILE_RAW: + _pptr = new filePrinter(RAW); + break; + case PRINTER_FILE_TRIM: + _pptr = new filePrinter; + break; + case PRINTER_FILE_ASCII: + _pptr = new filePrinter(ASCII); + break; + case PRINTER_EPSON: + _pptr = new epson80; + break; + case PRINTER_HTML: + _pptr = new htmlPrinter; + break; + default: + _pptr = new filePrinter; + _ptype = PRINTER_FILE_RAW; + break; + } + + _pptr->initPrinter(_storage); +} + +macPrinter::printer_type macPrinter::match_modelname(std::string model_name) +{ + const char *models[PRINTER_INVALID] = + { + "file printer (RAW)", + "file printer (TRIM)", + "file printer (ASCII)", + "Epson 80", + "HTML printer"}; + int i; + for (i = 0; i < PRINTER_INVALID; i++) + if (model_name.compare(models[i]) == 0) + break; + + return (printer_type)i; +} +#endif /* BUILD_APPLE */ \ No newline at end of file diff --git a/lib/device/mac/printer.h b/lib/device/mac/printer.h new file mode 100644 index 000000000..3a2ae3c44 --- /dev/null +++ b/lib/device/mac/printer.h @@ -0,0 +1,81 @@ +#ifdef BUILD_MAC +#ifndef PRINTER_H +#define PRINTER_H + +#include + +#include "../printer-emulator/printer_emulator.h" +#include "fnFS.h" + +#include "../../bus/bus.h" + +#define PRINTER_UNSUPPORTED "Unsupported" + +class macPrinter : public macDevice +{ +protected: + + // // IWM Status methods + // void send_status_reply_packet() override; + // void send_extended_status_reply_packet() override; + // void send_status_dib_reply_packet() override; + // void send_extended_status_dib_reply_packet() override; + + // // IWM methods + // void iwm_status(iwm_decoded_cmd_t cmd) override; + // void iwm_open(iwm_decoded_cmd_t cmd) override; + // void iwm_close(iwm_decoded_cmd_t cmd) override; + // void iwm_write(iwm_decoded_cmd_t cmd) override; + + + printer_emu *_pptr = nullptr; + FileSystem *_storage = nullptr; + + time_t _last_ms; + // TODO following are copied over from sio/printer.h + // uint8_t _lastaux1; + // uint8_t _lastaux2; + +public: + enum printer_type + { + PRINTER_FILE_RAW = 0, + PRINTER_FILE_TRIM, + PRINTER_FILE_ASCII, + PRINTER_EPSON, + PRINTER_HTML, + PRINTER_INVALID + }; + + constexpr static const char * const printer_model_str[PRINTER_INVALID] + { + "file printer (RAW)", + "file printer (TRIM)", + "file printer (ASCII)", + "Epson 80", + "HTML printer", + }; + + macPrinter(FileSystem *filesystem, printer_type printer_type = PRINTER_FILE_TRIM); + ~macPrinter(); + + static printer_type match_modelname(std::string model_name); + void set_printer_type(printer_type printer_type); + void reset_printer() { set_printer_type(_ptype); }; + time_t lastPrintTime() { return _last_ms; }; + // void print_from_cpm(uint8_t c); // left over from ATARI although could use it on APPLE maybe? + + printer_emu *getPrinterPtr() { return _pptr; }; + void print_from_cpm(uint8_t c); + + void process(mac_cmd_t cmd) override; + void shutdown() override {} + +private: + printer_type _ptype; + int _llen = 0; +}; + + +#endif // guard +#endif // BUILD_APPLE diff --git a/lib/device/mac/printerlist.cpp b/lib/device/mac/printerlist.cpp new file mode 100644 index 000000000..276baf1d3 --- /dev/null +++ b/lib/device/mac/printerlist.cpp @@ -0,0 +1,54 @@ +#ifdef BUILD_MAC + +#include "printerlist.h" + +// Global object to hold our printers +printerlist fnPrinters; + +void printerlist::set_entry(int index, macPrinter *ptr, macPrinter::printer_type ptype, int pport) +{ + if(index < 0 || index >= PRINTERLIST_SIZE) + return; + + _printers[index].pPrinter = ptr; + _printers[index].type = ptype; + _printers[index].port = pport; +} +void printerlist::set_ptr(int index, macPrinter *ptr) +{ + if(index < 0 || index >= PRINTERLIST_SIZE) + return; + _printers[index].pPrinter = ptr; +} +void printerlist::set_type(int index, macPrinter::printer_type ptype) +{ + if(index < 0 || index >= PRINTERLIST_SIZE) + return; + _printers[index].type = ptype; +} +void printerlist::set_port(int index, int pport) +{ + if(index < 0 || index >= PRINTERLIST_SIZE) + return; + _printers[index].port = pport; +} +macPrinter * printerlist::get_ptr(int index) +{ + if(index < 0 || index >= PRINTERLIST_SIZE) + return nullptr; + return _printers[index].pPrinter; +} +macPrinter::printer_type printerlist::get_type(int index) +{ + if(index < 0 || index >= PRINTERLIST_SIZE) + return macPrinter::printer_type::PRINTER_INVALID; + return _printers[index].type; +} +int printerlist::get_port(int index) +{ + if(index < 0 || index >= PRINTERLIST_SIZE) + return -1; + return _printers[index].port; +} + +#endif /* BUILD_APPLE */ \ No newline at end of file diff --git a/lib/device/mac/printerlist.h b/lib/device/mac/printerlist.h new file mode 100644 index 000000000..58aeb1921 --- /dev/null +++ b/lib/device/mac/printerlist.h @@ -0,0 +1,32 @@ +#ifndef _PRINTERLIST_H +#define _PRINTERLIST_H + +#include "printer.h" + +#define PRINTERLIST_SIZE 4 +class printerlist +{ +private: + struct printerlist_entry + { + macPrinter::printer_type type = macPrinter::printer_type::PRINTER_INVALID; + macPrinter *pPrinter = nullptr; + int port = 0; + }; + printerlist_entry _printers[PRINTERLIST_SIZE]; + +public: + void set_entry(int index, macPrinter *ptr, macPrinter::printer_type ptype, int pport); + + void set_ptr(int index, macPrinter *ptr); + void set_type(int index, macPrinter::printer_type ptype); + void set_port(int index, int pport); + + macPrinter * get_ptr(int index); + macPrinter::printer_type get_type(int index); + int get_port(int index); +}; + +extern printerlist fnPrinters; + +#endif // _PRINTERLIST_H \ No newline at end of file diff --git a/lib/device/modem.h b/lib/device/modem.h index fe328467e..56c086fa7 100644 --- a/lib/device/modem.h +++ b/lib/device/modem.h @@ -41,13 +41,18 @@ extern adamModem *sioR; extern iwmModem *sioR; #endif +#ifdef BUILD_MAC +#include "mac/modem.h" + extern macModem *sioR; +#endif + #ifdef BUILD_CX16 #include "cx16_i2c/modem.h" -extern cx16Modem *sioR; + extern cx16Modem *sioR; #endif #ifdef BUILD_RC2014 -# include "rc2014/modem.h" +#include "rc2014/modem.h" extern rc2014Modem *sioR; #endif diff --git a/lib/device/printer.h b/lib/device/printer.h index 7be8cd4e6..b62d0e232 100644 --- a/lib/device/printer.h +++ b/lib/device/printer.h @@ -67,6 +67,12 @@ # define PRINTER_CLASS iwmPrinter #endif +#ifdef BUILD_MAC +#include "mac/printer.h" +#include "mac/printerlist.h" +#define PRINTER_CLASS macPrinter +#endif + #ifdef BUILD_CX16 #include "cx16_i2c/printer.h" #include "cx16_i2c/printerlist.h" diff --git a/lib/device/sio/network.cpp b/lib/device/sio/network.cpp index 8badb1545..61b3ee148 100755 --- a/lib/device/sio/network.cpp +++ b/lib/device/sio/network.cpp @@ -147,7 +147,7 @@ void sioNetwork::sio_open() timer_start(); // Go ahead and send an interrupt, so Atari knows to get status. - sio_assert_interrupt(); + protocol->forceStatus = true; // TODO: Finally, go ahead and let the parsers know json = new FNJSON(); @@ -445,6 +445,8 @@ void sioNetwork::sio_status_channel() sio_status_channel_json(&status); break; } + // clear forced flag (first status after open) + protocol->forceStatus = false; // Serialize status into status bytes serialized_status[0] = status.rxBytesWaiting & 0xFF; @@ -858,7 +860,7 @@ void sioNetwork::sio_process(uint32_t commanddata, uint8_t checksum) } /** - * Check to see if PROCEED needs to be asserted, and assert if needed. + * Check to see if PROCEED needs to be asserted, and assert if needed (continue toggling PROCEED). */ void sioNetwork::sio_poll_interrupt() { @@ -867,6 +869,13 @@ void sioNetwork::sio_poll_interrupt() if (protocol->interruptEnable == false) return; + /* assert interrupt if we need Status call from host to arrive */ + if (protocol->forceStatus == true) + { + sio_assert_interrupt(); + return; + } + protocol->fromInterrupt = true; protocol->status(&status); protocol->fromInterrupt = false; @@ -1033,6 +1042,7 @@ void sioNetwork::processCommaFromDevicespec() void sioNetwork::sio_assert_interrupt() { fnSystem.digital_write(PIN_PROC, interruptProceed == true ? DIGI_HIGH : DIGI_LOW); + // Debug_print(interruptProceed ? "+" : "-"); } void sioNetwork::sio_set_translation() diff --git a/lib/fn_esp_http_client/fn_esp_http_client.cpp b/lib/fn_esp_http_client/fn_esp_http_client.cpp index 2606b75f5..13b250ce3 100644 --- a/lib/fn_esp_http_client/fn_esp_http_client.cpp +++ b/lib/fn_esp_http_client/fn_esp_http_client.cpp @@ -77,7 +77,8 @@ typedef struct { esp_http_buffer_t *buffer; /*!< data buffer as linked list */ int status_code; /*!< status code (integer) */ int content_length; /*!< data length */ - int chunk_length; /*!< chunk length */ + int chunk_length; /*!< last chunk length */ + int total_chunk_length; /*!< total chunk length */ int data_offset; /*!< offset to http data (Skip header) */ int data_process; /*!< data processed */ int method; /*!< http method */ @@ -244,6 +245,7 @@ static int http_on_header_value(http_parser *parser, const char *at, size_t leng } else if (strcasecmp(client->current_header_key, "Transfer-Encoding") == 0 && memcmp(at, "chunked", length) == 0) { client->response->is_chunked = true; + client->response->total_chunk_length = 0; } else if (strcasecmp(client->current_header_key, "WWW-Authenticate") == 0) { http_utils_assign_string(&client->auth_header, at, length); } @@ -306,7 +308,9 @@ static int http_on_chunk_header(http_parser *parser) { esp_http_client_handle_t client = (esp_http_client_handle_t)parser->data; client->response->chunk_length = parser->content_length; - ESP_LOGD(TAG, "http_on_chunk_header, chunk_length"); + client->response->total_chunk_length += parser->content_length; + ESP_LOGD(TAG, "http_on_chunk_header, chunk_length: %d, total_chunk_length: %d", + client->response->chunk_length, client->response->total_chunk_length); return 0; } @@ -1304,6 +1308,17 @@ int esp_http_client_get_chunk_length(esp_http_client_handle_t client) return -1; } +int esp_http_client_get_total_chunk_length(esp_http_client_handle_t client) +{ + if (client == NULL) + return -1; + + if (esp_http_client_is_chunked_response(client)) + return client->response->total_chunk_length; + + return -1; +} + esp_http_client_transport_t esp_http_client_get_transport_type(esp_http_client_handle_t client) { if (!strcasecmp(client->connection_info.scheme, "https") ) { diff --git a/lib/fn_esp_http_client/fn_esp_http_client.h b/lib/fn_esp_http_client/fn_esp_http_client.h index cd68ef5ff..4a97406c7 100644 --- a/lib/fn_esp_http_client/fn_esp_http_client.h +++ b/lib/fn_esp_http_client/fn_esp_http_client.h @@ -386,7 +386,7 @@ int esp_http_client_fetch_headers(esp_http_client_handle_t client); bool esp_http_client_is_chunked_response(esp_http_client_handle_t client); /** - * @brief Get chunked content length + * @brief Get chunked content length, returns length from last processed chunk header * * @param[in] client The esp_http_client handle * @@ -394,6 +394,15 @@ bool esp_http_client_is_chunked_response(esp_http_client_handle_t client); */ int esp_http_client_get_chunk_length(esp_http_client_handle_t client); +/** + * @brief Get chunked content length, returns total length from all processed chunk headers + * + * @param[in] client The esp_http_client handle + * + * @return -1 on error, chunk length + */ +int esp_http_client_get_total_chunk_length(esp_http_client_handle_t client); + /** * @brief Read data from http stream * diff --git a/lib/http/fnHttpClient.cpp b/lib/http/fnHttpClient.cpp index 90bfae9fa..6b774525f 100755 --- a/lib/http/fnHttpClient.cpp +++ b/lib/http/fnHttpClient.cpp @@ -71,44 +71,7 @@ int fnHttpClient::available() int len = -1; if (esp_http_client_is_chunked_response(_handle)) - { - len = esp_http_client_get_chunk_length(_handle); -#if 1 - // if (len == 0) - // Debug_println("::available last chunk reached"); - if (len <= _buffer_total_read && len > 0) - { - // We need to collect next chunk header, i.e. chunk size - // ... part of following chunk data will be read too (and stored into _buffer) - - // Make sure store our current task handle to respond to - _taskh_consumer = xTaskGetCurrentTaskHandle(); - - // Our HTTP subtask is gone - say there's nothing left to read... - if (_taskh_subtask == nullptr) - { - Debug_println("::available subtask gone"); - return 0; - } - - // Let the HTTP process task know to fill the buffer - // Debug_println("::available notifyGive"); - xTaskNotifyGive(_taskh_subtask); - // Wait till the HTTP task lets us know it's filled the buffer - // Debug_println("::available notifyTake..."); - if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(HTTPCLIENT_WAIT_FOR_HTTP_TASK)) != 1) - { - // Abort if we timed-out receiving the data - Debug_println("::available time-out"); - return 0; - } - - _buffer_total_read = 0; // well, reset this with new chunk - len = esp_http_client_get_chunk_length(_handle); - Debug_printf("::available next chunk len: %d\r\n", len); - } -#endif - } + len = esp_http_client_get_total_chunk_length(_handle); else len = esp_http_client_get_content_length(_handle); @@ -119,6 +82,11 @@ int fnHttpClient::available() return result; } +bool fnHttpClient::is_transaction_done() +{ + return _transaction_done; +} + /* Reads HTTP response data Return value is bytes stored in buffer or -1 on error diff --git a/lib/http/fnHttpClient.h b/lib/http/fnHttpClient.h index ed7df825a..9f6dc53eb 100755 --- a/lib/http/fnHttpClient.h +++ b/lib/http/fnHttpClient.h @@ -75,6 +75,7 @@ class fnHttpClient int MOVE(const char *destination, bool overwrite); int available(); + bool is_transaction_done(); int read(uint8_t *dest_buffer, int dest_bufflen); diff --git a/lib/media/mac/mediaType.cpp b/lib/media/mac/mediaType.cpp new file mode 100644 index 000000000..4199fce45 --- /dev/null +++ b/lib/media/mac/mediaType.cpp @@ -0,0 +1,125 @@ +#ifdef BUILD_MAC +#include "mediaType.h" + +MediaType::~MediaType() +{ + unmount(); +} + +bool MediaType::format(uint16_t *respopnsesize) +{ + return true; +} + +#include +#include + +void MediaType::unmount() +{ + if (_media_fileh != nullptr) + { + fclose(_media_fileh); + _media_fileh = nullptr; + } +} + +mediatype_t MediaType::discover_mediatype(const char *filename) +{ + // should probably look inside the file to help figure it out + int l = strlen(filename); + if (l > 5 && filename[l - 5] == '.') + { + // Check the last 4 characters of the string + const char *ext = filename + l - 2; + if (strcasecmp(ext, "MOOF") == 0) + return MEDIATYPE_MOOF; + } + else if (l > 4 && filename[l - 4] == '.') + { + // Check the last 3 characters of the string + const char *ext = filename + l - 3; + // if (strcasecmp(ext, "HDV") == 0) + // return MEDIATYPE_PO; + // else if (strcasecmp(ext,"2MG") == 0) + // return MEDIATYPE_PO; + // else if (strcasecmp(ext, "WOZ") == 0) + // return MEDIATYPE_WOZ; + // else if (strcasecmp(ext, "DSK") == 0) + // return MEDIATYPE_DSK; + } + else if (l > 3 && filename[l - 3] == '.') + { + // Check the last 3 characters of the string + const char *ext = filename + l - 2; + // if (strcasecmp(ext, "PO") == 0) + // return MEDIATYPE_PO; + } + return MEDIATYPE_UNKNOWN; +} + +#endif // BUILD_MAC + +#if 0 + +#include "mediaType.h" + +#include +#include + +MediaType::~MediaType() +{ + unmount(); +} + +bool MediaType::format(uint16_t *respopnsesize) +{ + return true; +} + +// bool MediaType::read(uint32_t blockNum, uint16_t *readcount) +// { +// return true; +// } + +// bool MediaType::write(uint32_t blockNum, bool verify) +// { +// return true; +// } + +void MediaType::unmount() +{ + if (_media_fileh != nullptr) + { + fclose(_media_fileh); + _media_fileh = nullptr; + } +} + +mediatype_t MediaType::discover_mediatype(const char *filename) +{ + // should probably look inside the file to help figure it out + int l = strlen(filename); + if (l > 4 && filename[l - 4] == '.') + { + // Check the last 3 characters of the string + const char *ext = filename + l - 3; + if (strcasecmp(ext, "HDV") == 0) + return MEDIATYPE_PO; + else if (strcasecmp(ext, "2MG") == 0) + return MEDIATYPE_PO; + else if (strcasecmp(ext, "WOZ") == 0) + return MEDIATYPE_WOZ; + else if (strcasecmp(ext, "DSK") == 0) + return MEDIATYPE_DSK; + } + else if (l > 3 && filename[l - 3] == '.') + { + // Check the last 3 characters of the string + const char *ext = filename + l - 2; + if (strcasecmp(ext, "PO") == 0) + return MEDIATYPE_PO; + } + return MEDIATYPE_UNKNOWN; +} + +#endif // NEW_TARGET \ No newline at end of file diff --git a/lib/media/mac/mediaType.h b/lib/media/mac/mediaType.h new file mode 100644 index 000000000..4a80cdbb7 --- /dev/null +++ b/lib/media/mac/mediaType.h @@ -0,0 +1,84 @@ +#ifndef _MEDIA_TYPE_ +#define _MEDIA_TYPE_ + +#include + +#define INVALID_SECTOR_VALUE 65536 + +#define DISK_SECTORBUF_SIZE 512 + +#define DISK_BYTES_PER_SECTOR_SINGLE 128 +#define DISK_BYTES_PER_SECTOR_DOUBLE 256 +#define DISK_BYTES_PER_SECTOR_DOUBLE_DOUBLE 512 + +#define DISK_CTRL_STATUS_CLEAR 0x00 + +enum mediatype_t +{ + MEDIATYPE_UNKNOWN = 0, + MEDIATYPE_MOOF, + MEDIATYPE_COUNT +}; + +class MediaType +{ +protected: + FILE *_media_fileh = nullptr; + uint32_t _media_image_size = 0; + uint32_t _media_num_sectors = 0; + uint16_t _media_sector_size = DISK_BYTES_PER_SECTOR_SINGLE; + int32_t _media_last_sector = INVALID_SECTOR_VALUE; + uint8_t _media_controller_status = DISK_CTRL_STATUS_CLEAR; + +public: + // struct + // { + // uint8_t num_tracks; + // uint8_t step_rate; + // uint8_t sectors_per_trackH; + // uint8_t sectors_per_trackL; + // uint8_t num_sides; + // uint8_t density; + // uint8_t sector_sizeH; + // uint8_t sector_sizeL; + // uint8_t drive_present; + // uint8_t reserved1; + // uint8_t reserved2; + // uint8_t reserved3; + // } _percomBlock; + + uint32_t num_blocks; + uint8_t num_sides; + + // FILE* fileptr() {return _media_fileh;} + + // uint8_t _media_sectorbuff[DISK_SECTORBUF_SIZE]; + + mediatype_t _mediatype = MEDIATYPE_UNKNOWN; + // bool _allow_hsio = true; + bool floppy_emulation; + + virtual mediatype_t mount(FILE *f, uint32_t disksize) = 0; + virtual void unmount(); + + // Returns TRUE if an error condition occurred + virtual bool format(uint16_t *respopnsesize); + + // Returns TRUE if an error condition occurred + virtual bool read(uint32_t blockNum, uint16_t *count, uint8_t *buffer) = 0; + // Returns TRUE if an error condition occurred + virtual bool write(uint32_t blockNum, uint16_t *count, uint8_t *buffer) = 0; + + // virtual uint16_t sector_size(uint16_t sectornum); + + virtual bool status() = 0; + + static mediatype_t discover_mediatype(const char *filename); + + // void dump_percom_block(); + // void derive_percom_block(uint16_t numSectors); + + virtual ~MediaType(); +}; + +#endif // _MEDIA_TYPE_ diff --git a/lib/media/mac/mediaTypeMOOF.cpp b/lib/media/mac/mediaTypeMOOF.cpp new file mode 100644 index 000000000..3f99bd39d --- /dev/null +++ b/lib/media/mac/mediaTypeMOOF.cpp @@ -0,0 +1,179 @@ +#ifdef BUILD_MAC +// https://applesaucefdc.com/moof-reference/ + +#include "mediaTypeMOOF.h" +#include "../../include/debug.h" +// #include + +mediatype_t MediaTypeMOOF::mount(FILE *f, uint32_t disksize) +{ + _media_fileh = f; + floppy_emulation = true; + // check MOOF header + if (moof_check_header()) + return MEDIATYPE_UNKNOWN; + + // work through INFO chunk + if (moof_read_info()) + return MEDIATYPE_UNKNOWN; + + if (moof_read_tmap()) + return MEDIATYPE_UNKNOWN; + if (moof_read_tracks()) + return MEDIATYPE_UNKNOWN; + + return MEDIATYPE_MOOF; +} + +void MediaTypeMOOF::unmount() +{ + MediaType::unmount(); + for (int i = 0; i < MAX_TRACKS; i++) + { + if (trk_ptrs[i] != nullptr) + free(trk_ptrs[i]); + } +} + +bool MediaTypeMOOF::moof_check_header() +{ + char hdr[12]; + fread(&hdr, sizeof(char), 12, _media_fileh); + if (hdr[0] == 'M' && hdr[1] == 'O' && hdr[2] == 'O' && hdr[2] == 'F') + { + Debug_printf("\nMOOF-%c file confirmed!"); + } + else + { + Debug_printf("\nNot a MOOF file!"); + return true; + } + // check for file integrity + if (hdr[4] == 0xFF && hdr[5] == 0x0A && hdr[6] == 0x0D && hdr[7] == 0x0A) + Debug_printf("\n8-bit binary file verified"); + else + return true; + + // could check CRC if one wanted + + return false; +} + +bool MediaTypeMOOF::moof_read_info() +{ + if (fseek(_media_fileh, 12, SEEK_SET)) + { + Debug_printf("\nError seeking INFO chunk"); + return true; + } + uint32_t chunk_id, chunk_size; + fread(&chunk_id, sizeof(chunk_id), 1, _media_fileh); + Debug_printf("\nINFO Chunk ID: %08x", chunk_id); + fread(&chunk_size, sizeof(chunk_size), 1, _media_fileh); + Debug_printf("\nINFO Chunk size: %d", chunk_size); + + uint8_t info_version, disk_type; + fread(&info_version, sizeof(info_version), 1, _media_fileh); + Debug_printf("\nINFO Version: %d", info_version); + fread(&disk_type, sizeof(disk_type), 1, _media_fileh); + Debug_printf("\nDisk type: %d", disk_type); + // 1 = SSDD GCR (400K) + // 2 = DSDD GCR (800K) + // 3 = DSHD MFM (1.44M) + switch (disk_type) + { + case 1: + num_sides = 1; + moof_disktype = moof_disk_type_t::SSDD_GCR; + break; + case 2: + num_sides = 2; + moof_disktype = moof_disk_type_t::DSDD_GCR; + break; + case 3: + num_sides = 2; + moof_disktype = moof_disk_type_t::DSDD_MFM; + break; + default: + moof_disktype = moof_disk_type_t::UNKNOWN; + Debug_printf("\nUnknown Disk Type: %d", disk_type); + return true; + } + + Debug_printf("\nNow at byte %d", ftell(_media_fileh)); + // blow the next two bytes + getc(_media_fileh); + getc(_media_fileh); + // get the bit timing: + uint8_t bit_timing; + fread(&bit_timing, sizeof(bit_timing), 1, _media_fileh); + optimal_bit_timing = bit_timing; + Debug_printf("\nMOOF Optimal Bit Timing = 125 ns X %d = %d ns", optimal_bit_timing, (int)optimal_bit_timing * 125); + // and jump to get the track size + fseek(_media_fileh, 58, SEEK_SET); + uint16_t largest_track; + fread(&largest_track, sizeof(largest_track), 1, _media_fileh); + num_blocks = largest_track; + + return false; +} + +bool MediaTypeMOOF::moof_read_tmap() +{ // read TMAP + if (fseek(_media_fileh, 80, SEEK_SET)) + { + Debug_printf("\nError seeking TMAP chunk"); + return true; + } + + uint32_t chunk_id, chunk_size; + fread(&chunk_id, sizeof(chunk_id), 1, _media_fileh); + Debug_printf("\nTMAP Chunk ID: %08x", chunk_id); + fread(&chunk_size, sizeof(chunk_size), 1, _media_fileh); + Debug_printf("\nTMAP Chunk size: %d", chunk_size); + Debug_printf("\nNow at byte %d", ftell(_media_fileh)); + + fread(&tmap, sizeof(tmap[0]), MAX_TRACKS, _media_fileh); +#ifdef DEBUG + Debug_printf("\nTrack, Index"); + for (int i = 0; i < MAX_TRACKS; i++) + Debug_printf("\n%d/4, %d", i, tmap[i]); +#endif + + return false; +} + +bool MediaTypeMOOF::moof_read_tracks() +{ // depend upon little endian-ness + fseek(_media_fileh, 256, SEEK_SET); + fread(&trks, sizeof(TRK_t), MAX_TRACKS, _media_fileh); +#ifdef DEBUG + Debug_printf("\nStart Block, Block Count, Bit Count"); + for (int i = 0; i < MAX_TRACKS; i++) + Debug_printf("\n%d, %d, %lu", trks[i].start_block, trks[i].block_count, trks[i].bit_count); +#endif + // read MOOF tracks into RAM + for (int i = 0; i < MAX_TRACKS; i++) + { + size_t s = trks[i].block_count * 512; + if (s != 0) + { + trk_ptrs[i] = (uint8_t *)heap_caps_malloc(s, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + if (trk_ptrs[i] != nullptr) + { + Debug_printf("\nReading %d bytes of track %d into location %lu", s, i, trk_ptrs[i]); + fseek(_media_fileh, trks[i].start_block * 512, SEEK_SET); + fread(trk_ptrs[i], 1, s, _media_fileh); + Debug_printf("\n%d, %d, %lu", trks[i].start_block, trks[i].block_count, trks[i].bit_count); + } + else + { + Debug_printf("\nNo RAM allocated!"); + return true; + } + } + } + return false; +} + +#endif // BUILD_MAC diff --git a/lib/media/mac/mediaTypeMOOF.h b/lib/media/mac/mediaTypeMOOF.h new file mode 100644 index 000000000..d8e976d60 --- /dev/null +++ b/lib/media/mac/mediaTypeMOOF.h @@ -0,0 +1,64 @@ +#ifndef _MEDIATYPE_MOOF_ +#define _MEDIATYPE_MOOF_ +// https://applesaucefdc.com/moof-reference/ + +#include "mediaType.h" +#include + +#define MAX_CYLINDERS 80 +#define MAX_SIDES 2 +#define MAX_TRACKS (MAX_SIDES * MAX_CYLINDERS) + +struct TRK_t +{ + uint16_t start_block; + uint16_t block_count; + uint32_t bit_count; +}; + +enum class moof_disk_type_t +{ + UNKNOWN, + SSDD_GCR, + DSDD_GCR, + DSDD_MFM +}; + +class MediaTypeMOOF : public MediaType +{ +private: + moof_disk_type_t moof_disktype; + + bool moof_check_header(); + bool moof_read_info(); + bool moof_read_tmap(); + bool moof_read_tracks(); + +protected: + uint8_t tmap[MAX_TRACKS]; + TRK_t trks[MAX_TRACKS]; + uint8_t *trk_ptrs[MAX_TRACKS] = {}; + +public: + MediaTypeMOOF() {}; + + virtual bool read(uint32_t blockNum, uint16_t *count, uint8_t *buffer) override { return false; }; + virtual bool write(uint32_t blockNum, uint16_t *count, uint8_t *buffer) override { return false; }; + + virtual bool format(uint16_t *respopnsesize) override { return false; }; + + virtual mediatype_t mount(FILE *f, uint32_t disksize) override; + mediatype_t mount(FILE *f) { return mount(f, 0); }; + virtual void unmount() override; + + virtual bool status() override { return (_media_fileh != nullptr); } + + uint8_t trackmap(uint8_t t) { return tmap[t]; }; + uint8_t *get_track(int t) { return trk_ptrs[tmap[t]]; }; + int track_len(int t) { return trks[tmap[t]].block_count * 512; }; + int num_bits(int t) { return trks[tmap[t]].bit_count; }; + uint8_t optimal_bit_timing; + // static bool create(FILE *f, uint32_t numBlock); +}; + +#endif // guard diff --git a/lib/media/media.h b/lib/media/media.h index 97008ba1f..5dce9fe76 100644 --- a/lib/media/media.h +++ b/lib/media/media.h @@ -34,7 +34,12 @@ # include "apple/mediaTypePO.h" # include "apple/mediaTypeWOZ.h" # include "apple/mediaTypeDSK.h" -#endif +#endif + +#ifdef BUILD_MAC +#include "mac/mediaType.h" +#include "mac/mediaTypeMOOF.h" +#endif #ifdef BUILD_S100 # include "adam/mediaType.h" diff --git a/lib/network-protocol/HTTP.cpp b/lib/network-protocol/HTTP.cpp index 0b74e940a..0ea3057e1 100755 --- a/lib/network-protocol/HTTP.cpp +++ b/lib/network-protocol/HTTP.cpp @@ -328,8 +328,8 @@ bool NetworkProtocolHTTP::status_file(NetworkStatus *status) if (fromInterrupt == false && resultCode == 0) http_transaction(); status->rxBytesWaiting = client->available() > 65535 ? 65535 : client->available(); - status->connected = client->available()>0; - status->error = client->available() == 0 && error == NETWORK_ERROR_SUCCESS ? NETWORK_ERROR_END_OF_FILE : error; + status->connected = client->is_transaction_done() ? 0 : 1; + status->error = client->available() == 0 && client->is_transaction_done() && error == NETWORK_ERROR_SUCCESS ? NETWORK_ERROR_END_OF_FILE : error; return false; case SET_HEADERS: case COLLECT_HEADERS: diff --git a/lib/network-protocol/Protocol.h b/lib/network-protocol/Protocol.h index aa13dcbd6..6e400c144 100755 --- a/lib/network-protocol/Protocol.h +++ b/lib/network-protocol/Protocol.h @@ -73,6 +73,12 @@ class NetworkProtocol */ bool interruptEnable = true; + /** + * Do we need to force Status call? + * (e.g. to start http_transaction in http protocol adapter after open) + */ + bool forceStatus = false; + /** * @brief Open connection to the protocol using URL * @param urlParser The URL object passed in to open. diff --git a/platformio-sample.ini b/platformio-sample.ini index 946d3058b..c314a28d7 100644 --- a/platformio-sample.ini +++ b/platformio-sample.ini @@ -44,7 +44,7 @@ flash_filesystem = FLASH_SPIFFS ;build_platform = BUILD_MAC ;build_bus = MAC -;build_board = macfuji-rev0 ; Use FujiApple Rev 0 Prototype for Mac 68000 +;build_board = fujimac-rev0 ; Use FujiApple Rev 0 Prototype for Mac 68000 ;build_platform = BUILD_IEC ;build_bus = IEC @@ -162,8 +162,8 @@ build_flags = ;-D NO3STATE ; no external tri-state on FN data out to A2, done internally ;-D REV1DETECT ; Only 3 people on earth need this enabled. You know who you are :P -; MacFuji using FujiApple Rev 0 Prototype (ESP32-DEVKITC-VE 8MB Flash, 8MB PSRAM) -[env:macfuji-rev0] +; Fuji for Mac 68000 using FujiApple Rev 0 Prototype (ESP32-DEVKITC-VE 8MB Flash, 8MB PSRAM) +[env:fujimac-rev0] platform = espressif32@${fujinet.esp32_platform_version} platform_packages = ${fujinet.esp32_platform_packages} board = fujinet-v1-8mb diff --git a/src/main.cpp b/src/main.cpp index e4fab64e0..721f8febd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -146,11 +146,6 @@ void main_setup() #endif // BUILD_IEC -#ifdef BUILD_MAC - FileSystem *ptrfs = fnSDFAT.running() ? (FileSystem *)&fnSDFAT : (FileSystem *)&fsFlash; - -#endif // BUILD_MAC - #ifdef BUILD_LYNX theFuji.setup(&ComLynx); ComLynx.setup(); @@ -257,6 +252,15 @@ void main_setup() #endif /* BUILD_APPLE */ +#ifdef BUILD_MAC + FileSystem *ptrfs = fnSDFAT.running() ? (FileSystem *)&fnSDFAT : (FileSystem *)&fsFlash; + + sioR = new macModem(ptrfs, Config.get_modem_sniffer_enabled()); + + theFuji.setup(&MAC); + MAC.setup(); +#endif // BUILD_MAC + #ifdef BUILD_CX16 theFuji.setup(&CX16); CX16.addDevice(&theFuji, CX16_DEVICEID_FUJINET); // the FUJINET! @@ -348,7 +352,7 @@ extern "C" // This is assigned to CPU1; the WiFi task ends up on CPU0 #ifdef BUILD_ATARI #define MAIN_STACKSIZE 32768 -#define MAIN_PRIORITY 10 +#define MAIN_PRIORITY 17 #else #define MAIN_STACKSIZE 32768 #define MAIN_PRIORITY 10