This commit is contained in:
Oliver Schmidt 2023-04-20 16:12:55 -04:00 committed by GitHub
commit 39788e4167
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 788 additions and 116 deletions

View File

@ -1,63 +1,34 @@
apple2pi
========
# Apple II Pi
What is the Apple II Pi?
------------------------
Basically, the Apple II Pi is the integration of an Apple II with a Raspberry Pi (http://www.raspberrypi.org) to create a hybrid computer combining the input devices and storage mediums of the Apple with the CPU, GPU (graphical processing unit), USB, and network capabilities of the Broadcom BCM2835 SoC (think smartphone processor). The concept is to create an updated version of the Apple II using some imagination, low-level drivers, off-the-shelf hardware, and a closely coupled communications channel; thus bringing semi-modern hardware and 32 bit *nix software to the Apple II platform. The Apple II is running as a dedicated I/O processor for the Raspberry Pi under ProDOS. The Raspberry Pi is running a version of Debian Linux: Raspbian. Much like the PC Transporter card brought MS-DOS and the Z-80 card brought CP/M, the Apple II Pi brings Linux to the Apple II using the Apples input devices and the Raspberry Pis video output. As such, knowledge and familiarity with Linux is required to get the most out of its environment. Under Linux, the Apple II Pi can read and write the Apples storage devices (floppies/harddrives/CFFA) and also run the GSport Apple IIgs emulator (http://gsport.sourceforge.net). Together, GSport and Apple II Pi provide an immersive environment providing access to most of the Apple II hardware you own plus an accelerated ~20MHz 65816 with up to 8 MB RAM, and all the disk images you can fit on the SD card.
See the [upstream repo](https://github.com/dschmenk/apple2pi) for the original, full info.
Apple II client/server for Raspberry Pi
---------------------------------------
## Installing
Apple II Pi works by connecting an Apple II to a Raspberry Pi using a RS232 serial connection. In order to get the Raspberry Pi to talk RS232 from it's 3.3V GPIO serial port, you will need to build or buy a converter. They are very cheap on eBay, so I would recommend going that route. Alternatively, you can use a USB serial port converter, but you will need to know its tty device name. To ensure you've hooked the converter up correctly, try loggin into the Raspberry Pi from another modern-ish computer. Raspbian, the default Debian based Linux for the Raspberry Pi, opens up a login (getty) session on the serial port at 115.2K baud. You will probably need a null modem or cross-over cable to login from another computer. Once it all checks out, time to connect your Apple II. All the 3.3V converters and USB serial ports I see have a DB-9 connector and many of the Apple II era connectors are DB-25 so you may need a DB-9 to DB-25 converter.
1. Flash [Apple-II-Pi.uf2](https://github.com/oliverschmidt/apple2pi/releases/latest/download/Apple-II-Pi.uf2) to the Raspberry Pi Pico.
Installing and configuring the Apple II: You will need an Apple //c or Apple ][, //e, IIgs w/ SuperSerial Card. An Apple ][ requires the SHIFT key mod. An Apple II Mouse is recommended for that full-on retro feel, but not required. Download and install the A2PI.PO disk image onto a 5 1/4 floppy. ADTPro would be the recommended tool for that operation although once you have the latest apple2pi version running, you can use the included dskwrite and dskread utilities for writing and reading ProDOS floppies.
2. Install [Raspberry Pi OS](https://www.raspberrypi.org/software/).
Installing and configuring the Raspberry Pi: Download the apple2pi project to your Raspberry Pi. Enter the apple2pi/src directory. Compile the daemon and tools with 'make' and copy the results to /usr/local/bin with 'sudo make install'. To build the FUSE driver needed to mount ProDOS devices under Linux, you will need the libfuse-dev package installed. Get this from apt-get, aptitude, or whichever package manager you like. Build with 'make fusea2pi' and install with 'sudo make fuse-install'.
3. Execute
```
sudo apt install git libfuse-dev -y
git clone https://github.com/oliverschmidt/apple2pi.git
cd apple2pi
make
sudo make install
```
The following is no longer neessary as the install script carefully makes all the following adjustments automatically. I left this here so you know what the script is doing.
4. Execute `sudo systemctl start a2pi.service` to run the Apple II Pi Daemon right away.
## Using
>You will need to disable the Raspbain serial login by editing /etc/inittab and commenting out the line (probably at the very bottom) to look like:<br>
<code>
\#T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
</code>
<br>
You will also want to disable the console messages that go out to the serial port in the /boot/cmdline.txt file. Remove the "console=" clause and the "kgdboc=" clause from the /boot/cmdline.txt file. Mine looks like:<br>
<code>
dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
</code>
1. Start Apple II Pi either via cold boot or `PR#n`.
>If you are using an HDMI port to display video, skip the overscan settings. This was for my monitor and your values may vary (a lot). I adjust the NTSC output so it fits nicely on my //c monitor, I edited the setting in /boot/config.txt such that:
2. Make sure to check out `a2term` in a Raspberry Pi shell.
><code>
overscan_left=26<br>
overscan_right=-8<br>
overscan_top=-8<br>
\#overscan_bottom=16<br>
</code>
## Building
>To run the a2pid daemon automatically at boot time, edit /etc/rc.local and add:
1. Execute `make` in `/pidrive` (requires [cc65](https://cc65.github.io/)).
><code>
/usr/local/bin/a2pid --daemon
</code>
2. Build the project in `/pipico` (requires the [Raspberry Pico C/C++ SDK](https://www.raspberrypi.com/documentation/microcontrollers/c_sdk.html)).
>right before the line:
><code>
exit 0
</code>
followed by rebooting the Raspberry Pi.
NOTE - For USB serial port users and non-Raspberry Pi owners: This isn't actually tied to the Raspberry Pi in any way, except for the default serial port used to connect the Pi to the Apple II. On most up-to-date Linux distributions, you should be able to build all the files. To run the daemon on a specific serial port, just add it as a command line option i.e. a2pid --daemon /dev/ttyUSB0
Reboot the Apple II with the newly created floppy in the start-up drive. If everything is configured correctly, you should be able to login to the Raspberry Pi with your Apple II keyboard. If you have an Apple II Mouse, that should control the cursor in X, or in the console if you have gdm installed.
Using a2pi: The Apple //c and //e keyboards are pretty minimal compared to modern keyboards, and the Apple II Mouse only has one button. In order to provide most of the funcitonality required of modern OSes, the Open-Apple and Closed-Apple keys are used as modifiers to enhance the keyboard and mouse. On the keyboard, Open-Apple acts just like the Alt key. The Closed-Apple key acts like a Fn key, changing the actual key codes. Currently, the Closed-Apple key will modify the number keys 1-0 as funciton keys F1-F10 and the arrow keys as Left-Arrow=Home, Right-Arrow=End, Up-Arrow=PgUp, Down-Arrow=PgDn. For the mouse, when you click the mouse button by itself, that is the left(default)-click. Open-Apple along with the mouse button will return the right-click, and Closed-Apple along with the mouse button will return the middle-click. a2pid can be run directly (not as a daemon) by leaving off the '--daemon' option. Enabling printf's in the code allows one to watch the packets arrive and get processed when run from a network ssh session.
Theory of operation: Apple II Pi works by running code on the Apple II and the Raspberry Pi, talking to each other with a simple protocol. The Apple II basically appears to the Raspberry Pi as an external peripheral, not unlike a USB keyboard and mouse. The Apple II floppy boots into ProDOS and runs a simple machine language program that scans the keyboard, and mouse if present, sending the events out the serial port to the Raspberry Pi. It is a very simple protocol and the serial port is running at 115.2K baud, so it is fast and low overhead. On the Raspberry Pi, a little daemon runs, waiting for packets on the serial port, converts the Apple II events into Linux compatible events, and inserts them into the input subsystem. This daemon also has a socket interface (port 6551) that can be used to access the Apple II memory and execute arbitrary code. Look at a2lib.c for implementation.
Enjoy,
Dave Schmenk
3. Execute `make` in `/src` (requires [libfuse-dev](https://packages.debian.org/en/sid/libfuse-dev)).

33
pipico/CMakeLists.txt Normal file
View File

@ -0,0 +1,33 @@
set(PROJECT_NAME Apple-II-Pi)
cmake_minimum_required(VERSION 3.12)
include(pico_sdk_import.cmake)
pico_sdk_init()
project(${PROJECT_NAME} C CXX ASM)
add_executable(${PROJECT_NAME})
pico_add_extra_outputs(${PROJECT_NAME})
pico_enable_stdio_uart(${PROJECT_NAME} 0)
pico_enable_stdio_usb(${PROJECT_NAME} 1)
pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/bus.pio)
target_sources(${PROJECT_NAME} PRIVATE
main.c
board.c
incbin.S
)
target_link_libraries(${PROJECT_NAME} PRIVATE
pico_stdlib
pico_multicore
hardware_pio
)
if(${PICO_BOARD} STREQUAL "pico_w")
target_link_libraries(${PROJECT_NAME} PRIVATE
pico_cyw43_arch_none
)
endif()

21
pipico/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Oliver Schmidt (https://a2retro.de/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

121
pipico/board.c Normal file
View File

@ -0,0 +1,121 @@
/*
MIT License
Copyright (c) 2022 Oliver Schmidt (https://a2retro.de/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "bus.pio.h"
#include "board.h"
extern const __attribute__((aligned(4))) uint8_t firmware[];
volatile bool active;
static uint32_t command;
static uint32_t control;
void __time_critical_func(board)(void) {
for (uint gpio = gpio_addr; gpio < gpio_addr + size_addr; gpio++) {
gpio_init(gpio);
gpio_set_pulls(gpio, false, false); // floating
}
for (uint gpio = gpio_data; gpio < gpio_data + size_data; gpio++) {
pio_gpio_init(pio0, gpio);
gpio_set_pulls(gpio, false, false); // floating
}
gpio_init(gpio_enbl);
gpio_set_pulls(gpio_enbl, false, false); // floating
uint offset;
offset = pio_add_program(pio0, &enbl_program);
enbl_program_init(offset);
offset = pio_add_program(pio0, &write_program);
write_program_init(offset);
offset = pio_add_program(pio0, &read_program);
read_program_init(offset);
active = false;
command = 0;
control = 0;
while (true) {
uint32_t enbl = pio_sm_get_blocking(pio0, sm_enbl);
uint32_t addr = enbl & 0x0FFF;
uint32_t io = enbl & 0x0F00; // IOSTRB or IOSEL
uint32_t strb = enbl & 0x0800; // IOSTRB
uint32_t read = enbl & 0x1000; // R/W
if (read) {
if (!io) { // DEVSEL
switch (addr & 0xF) {
case 0x8:
pio_sm_put(pio0, sm_read, sio_hw->fifo_rd);
break;
case 0x9:
// SIO_FIFO_ST_VLD_BITS _u(0x00000001)
// SIO_FIFO_ST_RDY_BITS _u(0x00000002)
pio_sm_put(pio0, sm_read, (sio_hw->fifo_st & 3) << 3);
break;
case 0xA:
pio_sm_put(pio0, sm_read, command);
break;
case 0xB:
pio_sm_put(pio0, sm_read, control);
break;
}
} else {
if (!strb || active) {
pio_sm_put(pio0, sm_read, firmware[addr]);
}
}
} else {
uint32_t data = pio_sm_get_blocking(pio0, sm_write);
if (!io) { // DEVSEL
switch (addr & 0xF) {
case 0x8:
sio_hw->fifo_wr = data;
break;
case 0xA:
command = data;
break;
case 0xB:
control = data;
break;
}
}
}
if (io && !strb) {
active = true;
} else if (addr == 0x0FFF) {
active = false;
}
}
}

34
pipico/board.h Normal file
View File

@ -0,0 +1,34 @@
/*
MIT License
Copyright (c) 2022 Oliver Schmidt (https://a2retro.de/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef _BOARD_H
#define _BOARD_H
extern volatile bool active;
void board(void);
#endif

205
pipico/bus.pio Normal file
View File

@ -0,0 +1,205 @@
///////////////////////////////////////////////////////////////////////////////
/*
MIT License
Copyright (c) 2022 Oliver Schmidt (https://a2retro.de/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
///////////////////////////////////////////////////////////////////////////////
.define public gpio_addr 2 // 12 pins
.define public gpio_rw 14
.define public gpio_data 15 // 8 pins
.define public gpio_enbl 26 // DEVSEL | IOSEL | IOSTRB
.define public gpio_irq 27
.define public gpio_res 28
.define public size_addr 13 // incl. R/W
.define public size_data 8
.define public sm_enbl 0
.define public sm_read 1 // from Apple II perspective
.define public sm_write 2 // from Apple II perspective
.define irq_write 4
/*
Implementation of the Apple II peripheral bus protocol:
- /DEVSEL, /IOSEL and /IOSTRB are supposed to combined to ENBL via an AND gate.
- On the falling edge of ENBL, the lines A0-A11 and R/W are sampled and pushed
into the 'enable' state machine RX FIFO.
- In case of an Apple II write cycle, the lines D0-D7 are sampled ~300ns later
and pushed into the 'write' state machine RX FIFO.
- If a byte is pushed into the 'read' state machine TX FIFO, it is driven out
to the lines D0-D7 until the rising edge of ENBL.
*/
///////////////////////////////////////////////////////////////////////////////
.program enbl
.wrap_target
idle:
wait 1 gpio gpio_enbl // wait for ENBL to rise
wait 0 gpio gpio_enbl // wait for ENBL to fall
in pins, size_addr // shift A0-A11 + R/W into ISR
jmp pin idle // jump to 'idle' if R/W is high
irq irq_write // set 'write' IRQ
.wrap
% c-sdk {
static inline void enbl_program_init(uint offset) {
pio_sm_config c = enbl_program_get_default_config(offset);
// in_base: gpio_addr
sm_config_set_in_pins(&c, gpio_addr);
// shift_right: false
// autopush: true
// push_threshold: size_addr
sm_config_set_in_shift(&c, false, true, size_addr);
// pin: gpio_rw
sm_config_set_jmp_pin(&c, gpio_rw);
// state_machine: sm_enbl
// initial_pc: offset
pio_sm_init(pio0, sm_enbl, offset, &c);
// state_machine: sm_enbl
pio_sm_set_enabled(pio0, sm_enbl, true);
}
%}
///////////////////////////////////////////////////////////////////////////////
.program write
.wrap_target
wait 1 irq irq_write [31] // wait for 'write' IRQ to be set and clear it
// [31 cycles to allow 6502 to set up D0-D7]
in pins, size_data // shift D0-D7 into ISR
.wrap
% c-sdk {
static inline void write_program_init(uint offset) {
pio_sm_config c = write_program_get_default_config(offset);
// in_base: gpio_data
sm_config_set_in_pins(&c, gpio_data);
// shift_right: false
// autopush: true
// push_threshold: size_data
sm_config_set_in_shift(&c, false, true, size_data);
// state_machine: sm_write
// initial_pc: offset
pio_sm_init(pio0, sm_write, offset, &c);
// state_machine: sm_write
pio_sm_set_enabled(pio0, sm_write, true);
}
%}
///////////////////////////////////////////////////////////////////////////////
.program read
/*
Both set and set-side are limited to 5 pins each. So both set and side-set are
configured to set (the direction of) 4 pins. This approach allows to set the
direction of D0-D7 in one operation.
Note: The naive approach would have been
mov osr, ~x // move 0xFFFFFFFF to OSR
out pindirs, size_data // enable output of D0-D7
[...]
mov osr, x // move 0x00000000 to OSR
out pindirs, size_data // disable output of D0-D7
but this would have required two operations and destroyed OSR.
*/
.side_set (size_data / 2) opt pindirs
.wrap_target
pull block // pull data into OSR, block on empty FIFO
out pins, size_data // shift OSR out to D0-D7
set pindirs, 15 side 15 // enable output of D0-D7
wait 1 gpio gpio_enbl // wait for ENBL to rise
set pindirs, 0 side 0 // disable output of D0-D7
.wrap
% c-sdk {
static inline void read_program_init(uint offset) {
pio_sm_config c = read_program_get_default_config(offset);
// out_base: gpio_data
// out_count: size_data
sm_config_set_out_pins(&c, gpio_data, size_data);
// shift_right: true
// autopull: false
// pull_threshold: size_data
sm_config_set_out_shift(&c, true, false, size_data);
// set_base: gpio_data
// set_count: size_data / 2
sm_config_set_set_pins(&c, gpio_data, size_data / 2);
// sideset_base: gpio_data + size_data / 2
sm_config_set_sideset_pins(&c, gpio_data + size_data / 2);
// state_machine: sm_read
// initial_pc: offset
pio_sm_init(pio0, sm_read, offset, &c);
// state_machine: sm_read
pio_sm_set_enabled(pio0, sm_read, true);
}
%}
///////////////////////////////////////////////////////////////////////////////

6
pipico/incbin.S Normal file
View File

@ -0,0 +1,6 @@
.section .time_critical.firmware
.global firmware
.type firmware, %object
.balign 4
firmware:
.incbin "../../pidrive/PIROM#062000"

110
pipico/main.c Normal file
View File

@ -0,0 +1,110 @@
/*
MIT License
Copyright (c) 2022 Oliver Schmidt (https://a2retro.de/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include "pico/printf.h"
#include "pico/stdlib.h"
#include "pico/multicore.h"
#ifdef RASPBERRYPI_PICO_W
#include "pico/cyw43_arch.h"
#endif
#include "bus.pio.h"
#include "board.h"
#ifdef TRACE
void uart_printf(uart_inst_t *uart, const char *format, ...) {
static char buffer[0x100];
va_list va;
va_start(va, format);
vsnprintf(buffer, sizeof(buffer), format, va);
va_end(va);
buffer[0xFF] = '\0';
uart_puts(uart, buffer);
}
#endif
void res_callback(uint gpio, uint32_t events) {
}
void main(void) {
multicore_launch_core1(board);
#ifdef RASPBERRYPI_PICO_W
cyw43_arch_init();
#elif defined(PICO_DEFAULT_LED_PIN)
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
#endif
gpio_init(gpio_irq);
gpio_pull_up(gpio_irq);
gpio_init(gpio_res);
gpio_set_irq_enabled_with_callback(gpio_res, GPIO_IRQ_EDGE_RISE, true, &res_callback);
stdio_init_all();
stdio_set_translate_crlf(&stdio_usb, false);
#ifdef TRACE
uart_init(uart0, 115200);
uart_set_translate_crlf(uart0, true);
gpio_set_function(0, GPIO_FUNC_UART);
gpio_set_function(1, GPIO_FUNC_UART);
#endif
while (true) {
if (multicore_fifo_rvalid()) {
uint32_t data = multicore_fifo_pop_blocking();
putchar(data);
#ifdef TRACE
uart_printf(uart0, "> %02X\n", data);
#endif
}
if (multicore_fifo_wready()) {
int data = getchar_timeout_us(0);
if (data != PICO_ERROR_TIMEOUT) {
multicore_fifo_push_blocking(data);
#ifdef TRACE
uart_printf(uart0, "< %02X\n", data);
#endif
}
}
#ifdef RASPBERRYPI_PICO_W
static bool last_active;
if (active != last_active) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, active);
last_active = active;
}
#elif defined(PICO_DEFAULT_LED_PIN)
gpio_put(PICO_DEFAULT_LED_PIN, active);
#endif
}
}

View File

@ -0,0 +1,73 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
endif ()
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

View File

@ -6,7 +6,7 @@ After=
Type=idle
Environment="HOME=/root"
EnvironmentFile=-/etc/default/a2pi
ExecStart=/sbin/a2pid $A2PID_OPTS
ExecStart=/usr/local/sbin/a2pid /dev/ttyACM*
Restart=on-failure
RestartSec=60

View File

@ -1,12 +1,7 @@
DESTDIR=/usr/local
SBINDIR=$(DESTDIR)/sbin
ifneq "$(findstring environment,$(origin DESTDIR))" ""
BINDIR=$(DESTDIR)/bin
SHAREDIR=$(DESTDIR)/share/a2pi
else
BINDIR=$(DESTDIR)/usr/bin
SHAREDIR=$(DESTDIR)/usr/share/a2pi
endif
BIN=a2joy a2joymou a2joypad a2mon a2term fusea2pi a2pidcmd dskread dskwrite bload brun nibread dskformat eddread
SBIN=a2pid
# A2PIDEFS=-DSETSERCLK
@ -24,12 +19,14 @@ clean:
install:
-mkdir -p $(BINDIR)
cp $(BIN) a2mount $(BINDIR)
cp $(BIN) a2setvd $(BINDIR)
cp $(BIN) a2mount a2setvd $(BINDIR)
-mkdir -p $(SBINDIR)
cp $(SBIN) $(SBINDIR)
-mkdir -p $(SHAREDIR)
cp -R ../share/* $(SHAREDIR)
mv $(SHAREDIR)/A2PI-1.7.PO $(SHAREDIR)/A2VD1.PO
mv $(SHAREDIR)/UTILS.PO $(SHAREDIR)/A2VD2.PO
systemctl enable --system $(SHAREDIR)/a2pi.service
uninstall:
cd $(BINDIR); rm $(BIN) a2mount

View File

@ -5,6 +5,7 @@
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <glob.h>
#include <termios.h>
#include <string.h>
#include <netinet/in.h>
@ -35,7 +36,7 @@ char deftty[] = "/dev/serial0"; /* Default for Raspberry Pi */
* Apple II request entry
*/
#define MAX_XFER 64
#define AWAIT_COMPLETE 0x100
#define AWAIT_COMPLETE 0x100
struct a2request {
int idx;
int type;
@ -48,10 +49,10 @@ struct a2request {
/*
* Client info
*/
#define CLIENT_OPEN 1
#define CLIENT_CLOSING 2
#define CLIENT_COUT 4
#define MAX_CLIENT 8
#define CLIENT_OPEN 1
#define CLIENT_CLOSING 2
#define CLIENT_COUT 4
#define MAX_CLIENT 8
struct {
int fd;
@ -76,14 +77,14 @@ int vdrvfd[2];
/*
* Daemon states.
*/
#define RUN 0
#define STOP 1
#define RESET 2
#define RUN 0
#define STOP 1
#define RESET 2
volatile int state = RUN, isdaemon = FALSE;
void prlog(char *str)
{
if (!isdaemon)
puts(str);
puts(str);
}
static void sig_bye(int signo)
{
@ -102,12 +103,16 @@ int vdrvopen(char *path)
char filename[256];
strcpy(filename, path);
strcat(filename, "A2VD1.PO");
//printf("vdrv: open %s\n", filename);
#ifdef TRACE
printf("vdrv: open %s\n", filename);
#endif
if ((vdrvfd[0] = open(filename, O_RDWR, 0)) < 0)
vdrvfd[0] = 0;
strcpy(filename, path);
strcat(filename, "A2VD2.PO");
//printf("vdrv: open %s\n", filename);
#ifdef TRACE
printf("vdrv: open %s\n", filename);
#endif
if ((vdrvfd[1] = open(filename, O_RDWR, 0)) < 0)
vdrvfd[1] = 0;
return vdrvfd[0] + vdrvfd[1];
@ -315,7 +320,7 @@ void sendrelxy(int fd, int x, int y)
write(fd, &evsync, sizeof(evsync));
}
/*****************************************************************\
* *
* *
* Request queue management *
* *
\*****************************************************************/
@ -456,6 +461,27 @@ void flushreqs(int a2fd, int clidx, int status, int result)
}
/*****************************************************************\
* *
* Path matching *
* *
\*****************************************************************/
int pathmatch(char **path, char *pattern)
{
glob_t result;
int found;
if (*path)
{
free(*path);
*path = NULL;
}
found = !glob(pattern, 0, NULL, &result);
if (found)
*path = strdup(*result.gl_pathv);
globfree(&result);
return found;
}
/*****************************************************************\
* *
* Main entrypoint *
* *
\*****************************************************************/
@ -468,8 +494,9 @@ void main(int argc, char **argv)
int a2fd, kbdfd, moufd, srvfd, maxfd;
struct sockaddr_in servaddr;
fd_set readset, openset;
char *devtty = deftty;
char *vdrvdir = "/usr/share/a2pi/"; /* default vdrv image directory */
char *ttypattern = deftty;
char *devtty = NULL;
char *vdrvdir = "/usr/local/share/a2pi/"; /* default vdrv image directory */
/*
* Parse arguments
@ -485,16 +512,16 @@ void main(int argc, char **argv)
die("a2pid: daemon() failure");
isdaemon = TRUE;
/*
* Another argument must be tty device
* Another argument must be tty pattern
*/
if (argc > 2)
devtty = argv[2];
ttypattern = argv[2];
}
else
/*
* Must be tty device
* Must be tty pattern
*/
devtty = argv[1];
ttypattern = argv[1];
}
/*
* Add signal handlers.
@ -596,6 +623,21 @@ void main(int argc, char **argv)
* Get vdrv images.
*/
vdrvactive = vdrvopen(vdrvdir);
/*
* Open socket.
*/
prlog("a2pid: Open server socket\n");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6551);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
srvfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (srvfd < 0)
die("error: socket create");
if (bind(srvfd,(struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
die("error: bind socket");
if (listen(srvfd, MAX_CLIENT - 1) < 0)
die("error: listen socket");
#if defined(SETSERCLK) && defined(__ARMEL__)
/*
* Initialize ACIA clock for Apple II Pi card
@ -603,13 +645,29 @@ void main(int argc, char **argv)
if (devtty == deftty)
gpclk(271); /* divisor for ~1.8 MHz => (500/271) MHz */
#endif
/*
* Come back here on serial read error.
*/
openserial:
/*
* Open serial port.
*/
while (!pathmatch(&devtty, ttypattern))
{
usleep(1000);
if (state == STOP)
die("interrupted");
}
prlog("a2pid: Open serial port\n");
a2fd = open(devtty, O_RDWR | O_NOCTTY);
if (a2fd < 0)
die("error: serial port open");
#ifdef TRACE
printf("a2pid: open %s\n", devtty);
#endif
while ((a2fd = open(devtty, O_RDWR | O_NOCTTY)) < 0)
{
usleep(1000);
if (state == STOP)
die("interrupted");
}
tcflush(a2fd, TCIFLUSH);
tcgetattr(a2fd, &oldtio); /* save current port settings */
bzero(&newtio, sizeof(newtio));
@ -644,25 +702,28 @@ void main(int argc, char **argv)
state = RESET;
}
}
/* drain superfluous sync requests from potential fifo */
usleep(1000);
c = read(a2fd, iopkt, sizeof(iopkt));
for (i = 0; i < c; i++)
{
#ifdef TRACE
printf("a2pid: drain 0x%02X\n", iopkt[i]);
#endif
/* there's already some other request */
if (iopkt[i] != 0x80)
{
#ifdef TRACE
printf("a2pid: push back 0x%02X\n", iopkt[i]);
#endif
/* 'simulate terminal input' for a push back */
ioctl(a2fd, TIOCSTI, &iopkt[i]);
}
}
newtio.c_cc[VMIN] = 3; /* blocking read until 3 chars received */
tcsetattr(a2fd, TCSANOW, &newtio);
/*
* Open socket.
*/
prlog("a2pid: Open server socket\n");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6551);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
srvfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (srvfd < 0)
die("error: socket create");
if (bind(srvfd,(struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
die("error: bind socket");
if (listen(srvfd, MAX_CLIENT - 1) < 0)
die("error: listen socket");
/*
* Come basck here on RESET.
* Come back here on RESET.
*/
reset:
state = RUN;
@ -698,7 +759,9 @@ reset:
rdycnt--;
if (read(a2fd, iopkt, 3) == 3)
{
// printf("a2pi: A2 Event [0x%02X] [0x%02X] [0x%02X]\n", iopkt[0], iopkt[1], iopkt[2]);
#ifdef TRACE
printf("a2pi: A2 Event [0x%02X] [0x%02X] [0x%02X]\n", iopkt[0], iopkt[1], iopkt[2]);
#endif
switch (iopkt[0])
{
case 0x80: /* sync */
@ -709,23 +772,31 @@ reset:
flushreqs(a2fd, 0, -1, -1);
break;
case 0x82: /* keyboard event */
// printf("Keyboard Event: 0x%02X:%c\n", iopkt[1], iopkt[2] & 0x7F);
#ifdef TRACE
printf("Keyboard Event: 0x%02X:%c\n", iopkt[1], iopkt[2] & 0x7F);
#endif
sendkey(kbdfd, iopkt[1], iopkt[2]);
//if (iopkt[2] == 0x9B && iopkt[1] == 0xC0)
//state = STOP;
break;
case 0x84: /* mouse move event */
//printf("Mouse XY Event: %d,%d\n", (signed char)iopkt[1], (signed char)iopkt[2]);
#ifdef TRACE
printf("Mouse XY Event: %d,%d\n", (signed char)iopkt[1], (signed char)iopkt[2]);
#endif
sendrelxy(moufd, (signed char)iopkt[1], (signed char)iopkt[2]);
break;
case 0x86: /* mouse button event */
// printf("Mouse Button %s Event 0x%02X\n", iopkt[2] ? "[PRESS]" : "[RELEASE]", iopkt[1]);
sendbttn(moufd, iopkt[1], iopkt[2]);
#ifdef TRACE
printf("Mouse Button %s Event 0x%02X\n", iopkt[2] ? "[PRESS]" : "[RELEASE]", iopkt[1]);
#endif
sendbttn(moufd, iopkt[1], iopkt[2]);
break;
case 0x90: /* acknowledge read bytes request*/
if (a2reqlist) /* better have an outstanding request */
{
//printf("a2pid: read %d of %d bytes from 0x%04X\n", a2reqlist->xfer, a2reqlist->count, a2reqlist->addr);
#ifdef TRACE
printf("a2pid: read %d of %d bytes from 0x%04X\n", a2reqlist->xfer, a2reqlist->count, a2reqlist->addr);
#endif
newtio.c_cc[VMIN] = 1; /* blocking read until 1 char received */
tcsetattr(a2fd, TCSANOW, &newtio);
c = a2reqlist->count - a2reqlist->xfer > MAX_XFER
@ -755,7 +826,9 @@ reset:
case 0x92: /* acknowledge write bytes */
if (a2reqlist) /* better have an outstanding request */
{
//printf("a2pid: wrote %d of %d bytes to 0x%04X\n", a2reqlist->xfer, a2reqlist->count, a2reqlist->addr);
#ifdef TRACE
printf("a2pid: wrote %d of %d bytes to 0x%04X\n", a2reqlist->xfer, a2reqlist->count, a2reqlist->addr);
#endif
newtio.c_cc[VMIN] = 1; /* blocking read until 1 char received */
tcsetattr(a2fd, TCSANOW, &newtio);
c = a2reqlist->count - a2reqlist->xfer > MAX_XFER
@ -785,7 +858,9 @@ reset:
case 0x94: /* acknowledge call */
if (a2reqlist) /* better have an outstanding request */
{
//printf("a2pid: call address 0x%04X\n", a2reqlist->addr);
#ifdef TRACE
printf("a2pid: call address 0x%04X\n", a2reqlist->addr);
#endif
newtio.c_cc[VMIN] = 1; /* blocking read until 1 char received */
tcsetattr(a2fd, TCSANOW, &newtio);
if (!writeword(a2fd, a2reqlist->addr, iopkt[0] + 1))
@ -800,7 +875,9 @@ reset:
case 0x96: /* send input char to Apple II */
if (a2reqlist) /* better have an outstanding request */
{
//printf("a2pid: call address 0x%04X\n", a2reqlist->addr);
#ifdef TRACE
printf("a2pid: call address 0x%04X\n", a2reqlist->addr);
#endif
newtio.c_cc[VMIN] = 1; /* blocking read until 1 char received */
tcsetattr(a2fd, TCSANOW, &newtio);
if (!writeword(a2fd, a2reqlist->addr, 0x97))
@ -822,7 +899,9 @@ reset:
case 0x9F: /* request complete error */
if (a2reqlist) /* better have an outstanding request */
{
//printf("a2pid: complete request 0x%02X:0x%02X\n", (unsigned char)iopkt[0], (unsigned char)iopkt[1]);
#ifdef TRACE
printf("a2pid: complete request 0x%02X:0x%02X\n", (unsigned char)iopkt[0], (unsigned char)iopkt[1]);
#endif
if ((a2reqlist->type == 0x90 || a2reqlist->type == 0x92)
&& (a2reqlist->count > a2reqlist->xfer))
{
@ -831,7 +910,9 @@ reset:
}
else
{
//printf("a2pid: finish request 0x%02X:0x%02X\n", (unsigned char)iopkt[0], (unsigned char)iopkt[1]);
#ifdef TRACE
printf("a2pid: finish request 0x%02X:0x%02X\n", (unsigned char)iopkt[0], (unsigned char)iopkt[1]);
#endif
finreq(a2fd, (unsigned char)iopkt[0], (unsigned char)iopkt[1]);
}
}
@ -840,7 +921,9 @@ reset:
break;
case 0xA0: /* virtual drive 1 STATUS call */
case 0xA2: /* virtual drive 2 STATUS call */
//printf("vdrive: STATUS unit:%d\n", (iopkt[0] >> 1) & 0x01);
#ifdef TRACE
printf("vdrive: STATUS unit:%d\n", (iopkt[0] >> 1) & 0x01);
#endif
iopkt[3] = iopkt[0] + 1; /* ack */
write(a2fd, &iopkt[3], 1);
iopkt[0] = vdrvstat(a2fd, (iopkt[0] >> 1) & 0x01);
@ -849,12 +932,16 @@ reset:
{
iopkt[0] = a2reqlist->type;
write(a2fd, iopkt, 1);
//printf("vdrive: status resend request %04X\n", a2reqlist->type);
#ifdef TRACE
printf("vdrive: status resend request %04X\n", a2reqlist->type);
#endif
}
break;
case 0xA4: /* virtual drive 1 READ call */
case 0xA6: /* virtual drive 2 READ call */
//printf("vdrive: READ unit:%d block:%d\n", (iopkt[0] >> 1) & 0x01, iopkt[1] | (iopkt[2] << 8));
#ifdef TRACE
printf("vdrive: READ unit:%d block:%d\n", (iopkt[0] >> 1) & 0x01, iopkt[1] | (iopkt[2] << 8));
#endif
iopkt[3] = iopkt[0] + 1; /* ack */
write(a2fd, &iopkt[3], 1);
iopkt[0] = vdrvread(a2fd, (iopkt[0] >> 1) & 0x01, iopkt[1] | (iopkt[2] << 8));
@ -863,12 +950,16 @@ reset:
{
iopkt[0] = a2reqlist->type;
write(a2fd, iopkt, 1);
//printf("vdrive: read resend request %04X\n", a2reqlist->type);
#ifdef TRACE
printf("vdrive: read resend request %04X\n", a2reqlist->type);
#endif
}
break;
case 0xA8: /* virtual drive 1 WRITE call */
case 0xAA: /* virtual drive 2 WRITE call */
//printf("vdrive: WRITE unit:%d block:%d\n", (iopkt[0] >> 1) & 0x01, iopkt[1] | (iopkt[2] << 8));
#ifdef TRACE
printf("vdrive: WRITE unit:%d block:%d\n", (iopkt[0] >> 1) & 0x01, iopkt[1] | (iopkt[2] << 8));
#endif
iopkt[3] = iopkt[0] + 1; /* ack */
write(a2fd, &iopkt[3], 1);
newtio.c_cc[VMIN] = 1; /* blocking read until command packet received */
@ -881,11 +972,15 @@ reset:
{
iopkt[0] = a2reqlist->type;
write(a2fd, iopkt, 1);
//printf("vdrive: write resend request %04X\n", a2reqlist->type);
#ifdef TRACE
printf("vdrive: write resend request %04X\n", a2reqlist->type);
#endif
}
break;
case 0xAC: /* virtual clock TIME call */
//printf("vclock: TIME\n");
#ifdef TRACE
printf("vclock: TIME\n");
#endif
iopkt[0] = 0xAD; /* ack */
write(a2fd, iopkt, 1);
write(a2fd, prodos_time(), 4);
@ -893,7 +988,9 @@ reset:
{
iopkt[0] = a2reqlist->type;
write(a2fd, iopkt, 1);
//printf("vclock: resend request %04X\n", a2reqlist->type);
#ifdef TRACE
printf("vclock: resend request %04X\n", a2reqlist->type);
#endif
}
break;
default:
@ -905,7 +1002,8 @@ reset:
else
{
prlog("a2pid: error read serial port ????\n");
state = STOP;
close(a2fd);
goto openserial;
}
}
/*
@ -950,7 +1048,9 @@ reset:
rdycnt--;
if (read(a2client[i].fd, iopkt, 1) == 1)
{
// printf("a2pi: Client Request [0x%02X]\n", iopkt[0]);
#ifdef TRACE
printf("a2pi: Client Request [0x%02X]\n", iopkt[0]);
#endif
switch (iopkt[0])
{
case 0x90: /* read bytes */

View File

@ -1,3 +1,4 @@
#include <ctype.h>
#include "a2lib.c"
char online[] = {