Initial Commit

Cleaned up for release at last!

Signed-off-by: Michel Pollet <buserror@gmail.com>
This commit is contained in:
Michel Pollet 2023-10-25 08:50:14 +01:00
parent 936f37728f
commit f7a56ebc01
116 changed files with 51755 additions and 1 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.bash_history
build-*
.vscode
compile_commands.json

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023 Michel Pollet
Copyright (c) 2023 Michel Pollet <buserror+git@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

105
Makefile Normal file
View File

@ -0,0 +1,105 @@
# GCC is default -- simply because it's faster!
CC = gcc
SHELL = /bin/bash
# This is where (g)make looks for the source files for implicit rules
VPATH := src src/format src/drivers nuklear contrib
CPPFLAGS += -Isrc -Isrc/format -Isrc/roms -Isrc/drivers
CPPFLAGS += -Icontrib -Inuklear
CPPFLAGS += -Ilibmish/src
CFLAGS += --std=gnu99 -Wall -Wextra -O2 -g
CFLAGS += -Wno-unused-parameter -Wno-unused-function
LDLIBS += -lX11 -lm -lGL -lGLU
HAS_ALSA := $(shell pkg-config --exists alsa && echo 1)
ifeq ($(HAS_ALSA),1)
LDLIBS += $(shell pkg-config --libs alsa)
CPPFLAGS += $(shell pkg-config --cflags alsa) -DHAS_ALSA
else
${warning ALSA not found, no sound support}
endif
O := build-$(shell $(CC) -dumpmachine)
BIN := $(O)/bin
LIB := $(O)/lib
OBJ := $(O)/obj
all : $(BIN)/mii_emu
MII_SRC := $(wildcard src/*.c src/format/*.c \
src/drivers/*.c contrib/*.c)
UI_SRC := $(wildcard nuklear/*.c)
SRC := $(MII_SRC) $(UI_SRC)
ALL_OBJ := ${patsubst %, ${OBJ}/%, ${notdir ${SRC:.c=.o}}}
$(BIN)/mii_emu : $(ALL_OBJ)
$(BIN)/mii_emu : $(LIB)/libmish.a
libmish : $(LIB)/libmish.a
LDLIBS += $(LIB)/libmish.a
$(LIB)/libmish.a : | $(LIB) $(OBJ) $(BIN)
make -j -C libmish O=$(PWD)
# Smartport firmware needs the assembler first
test/asm/%.bin : test/asm/%.asm | $(BIN)/mii_asm
$(BIN)/mii_asm -v -o $@ $<
# And it also INCBIN the firmware driver
$(OBJ)/mii_smarport.o : test/asm/mii_smartport_driver.bin
$(OBJ)/libsofd.o : CPPFLAGS += -DHAVE_X11
clean :
rm -rf $(O)
# This is for development purpose. This will recompile the project
# everytime a file is modified.
watch :
while true; do \
clear; $(MAKE) -j all tests; \
inotifywait -qre close_write src src/format nuklear test; \
done
tests : $(BIN)/mii_test $(BIN)/mii_cpu_test $(BIN)/mii_asm
# Just the library for mii, not any of the UI stuff
TEST_OBJ := ${patsubst %, ${OBJ}/%, ${notdir ${MII_SRC:.c=.o}}}
VPATH += test
# Base test without the UI, for performance testing
$(BIN)/mii_test : $(TEST_OBJ)
$(BIN)/mii_test : $(OBJ)/mii_test.o $(OBJ)/mii_mish.o
$(OBJ)/mii_test.o : CFLAGS += -O0 -Og
$(OBJ)/mii_cpu_test.o : CFLAGS += -O0 -Og
$(BIN)/mii_cpu_test : $(OBJ)/mii_cpu_test.o $(TEST_OBJ)
$(BIN)/mii_asm : $(OBJ)/mii_asm.o $(TEST_OBJ)
ifeq ($(V),1)
Q :=
else
Q := @
endif
$(OBJ)/%.o : %.c | $(OBJ)
@echo " CC $<"
$(Q)$(CC) -MMD $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
$(BIN)/% : | $(BIN)
@echo " LD $@"
$(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
$(OBJ) $(BIN) $(LIB) :
@mkdir -p $@
# Generates the necessary file to help clangd index the files properly.
# This currently has to be done manually, but helps a lot if you use 'kate'
# editor or anthing else that is compatible with the LSP protocol
compile_commands.json: lsp
lsp:
{ $$(which gmake) CC=gcc V=1 --always-make --dry-run all tests; \
$$(which gmake) CC=gcc V=1 --always-make --dry-run -C libmish ; } | \
sh utils/clangd_gen.sh >compile_commands.json
-include $(O)/*.d
-include $(O)/obj/*.d

152
README.md Normal file
View File

@ -0,0 +1,152 @@
# MII Apple //e Emulator
I know there are many out there, but none of them were ticking my fancy, so I decide to write my own. To start with it was "How hard can it be really?" then it snowballed as more and more things were fixed & added. It's been shelved for a while because well, it lacked documentation, headers, licence and stuff, so I spent some time cleaning it up for release.
One primary reason for this project was that linapple (or -pie) codebase is really horrible. It dates back from 2000's or before, with loads of Windows crud leftover, some SDL crud added, the audio just doesn't really work, and overall if you want to hack around the codebase, it's pretty dreadful.
![Monochrome Double-Hi res](docs/screen_main.png)
*Double hires in monochrome*
I wanted something:
* Modern, Clean code, modular -- in the spirit of simavr, usable as a library.
* Made for linux. In C. With a Makefile. None of the trendy bloatware.
* Small, Fast.
* Minimal dependencies.
* No gigantic config file.
* I didn't need II+ or unenhanced IIe, just 65c02 //e.
![Glorious NTSC colors](docs/screen_color.png)
*Double hires in color*
## What can it do?
* 65c02 //e with 128K of ram.
* Support all known graphic modes:
* Double-hires in mono and color, with automatic switch
* All the other modes in color/mono
* Color, Green, Amber rendering
* Simulated 'scanlines' to make it look vintage
* Speaker audio. ALSA For playback.
* Adds a small 'attack' filter when playing back to soften the often annoying 'click' of typical audio effects from the apple II.
* Mouse Card -- mouse isn't captured like in some other emulators.
* No Slot Clock
* Smartport DMA 'hard drive' card
* "Titan Accelerator //e" simulation, to turn on/off fast mode.
* Terence's J Boldt [1MB ROM card](https://github.com/tjboldt/ProDOS-ROM-Drive), also because I own a couple!
* Floppy Drive [more on that later]
* No dependencies (X11) OpenGL rendering, using Nuklear backend for UI
![Phosphorescent Green](docs/screen_green.png)
*Good old green monitor style. Theres Amber too.*
## How to I compile it and run it?
* You need a C compiler, make, and a few libraries:
* libasound2-dev [ optional, for audio ]
* libgl-dev
* libglu-dev
* libx11-dev
* Many of them will probably be installed already.
* For more details on development, see [Compiling](docs/Compiling.md)
* Then just type `make` and it should compile.
* To run it, just type `build-x86_64-linux-gnu/bin/bin/mii_emu` and it should start.
* `mii_emu --help` will display:
Usage: ./build-x86_64-linux-gnu/bin/mii_emu [options]
Options:
-h, --help This help
-v, --verbose Verbose output
-m, --mute Mute the speaker
-vol, --volume <volume> Set speaker volume (0.0 to 10.0)
-speed, --speed <speed> Set the CPU speed in MHz
-s, --slot <slot>:<driver> Specify a slot and driver
Slot id is 1..7
-d, --drive <slot>:<drive>:<filename> Specify a drive
Slot id is 1..7, drive is 1..2
Alternate syntax: <slot>:<drive> <filename>
-L, --list-drivers List available drivers
-def, --default Set default drives:
Slot 4: mouse
Slot 6: disk2
Slot 7: smartport
-nsc[=0|1] Enable/Disable No Slot Clock:
0: disable
1: enable [Enabled by default]
-titan[=0|1] Enable/Disable Titan Accelerator IIe:
0: disable [default]
1: enable [Start at 3.58MHz]
And the available drivers:
$ ./build-x86_64-linux-gnu/bin/mii_emu -L
mii: available drivers:
titan - Titan Accelerator IIe
smartport - SmartPort card
nsc - No Slot Clock
mouse - Mouse card
eecard - EEPROM 1MB card
disk2 - Apple Disk ][
## Key Bindings
There are just a few keys that are mapped for anything useful.
* **Control-F12** is Control-Reset on the IIe. (**Shift-Control-F12** is **Open Apple-Reset**)
* **'Super'** left and **'Super'** right are **Open** and **Close Apple** keys.
These keys are mapped to the left and right 'Windows' keys on a PC keyboard, and they might want to open the start menu (I know it's the case with Cinnamon), so you might want to disable that.
* **F5** sets the CPU speed to 1MHz
* **F6** to 4MHz.
* These keys control the built-in debugger:
* **Control-F11** Stops the emulator; see the command prompt/telnet for how to proceed, dump state, disassembly etc.
* **Control-F10** 'steps' the emulator, ie one instruction at a time.
* **Control-F9** is 'continue' -- resumes the emulator.
![Telnet into mii_emu](docs/screen_mish.png)
*The built-in shell, telnet into the emulator!*
## Anything else?
* Well it has it's own command line shell, using my own [libmish](https://github.com/buserror/libmish) so there's loads you can do by... *telnet into* the emulator!
* Yes, you can telnet into the running emulator!
+ The telnet port is displayed in the window title bar.
+ The port is 'semi random' -- it tries to find one that is derivative of the program name, but it will try several until it finds one that is free.
* It has it's own debugger shell. You can:
+ Add break/watch points.
+ Step, Next (jump over JSR's), Continue, Halt
+ Has a 16 instruction trace history when hitting a breakpoint.
+ Dump memory map
+ Trigger soft switches
+ Disassemble
* Also MII comes with it's own mini-assembler, used to compile some driver and the CPU unit tests (inspired by apple2ts, see bellow)
* It embeds all the files it needs, ROMs, fonts etc so you don't need to install it, just run it, it'll work.
* MII "Hard Disk" uses 'overlay' files:
+ This writes blocks on a /separate/ (sparse) file from the main file.
+ This "Overlay" is created automatically and allows you to keep your image files clean, unless you want to 'commit' your overlay back in.
+ This allows you to make sure your disk images aren't corrupted when 'hard rebooting' the emulator, if you are in the process of testing/developing a program for example.
## What it cannot do
* MouseCalc crashes (VBL IRQ, or a mouse mode I don't support yet)
* A2Desktop PT3 player doesn't see keypresses.
* Sometimes the emulator goes in 'slow mode', ie 0.2MHz. Likely the frame scheduler playing up.
* Thats' about it really, all the other things I tried work
## What it could do with
* Not sure about keeping Nuklear, it does a lot bit it's hard work customizing anything
* Add a memory extension 'card' -- not sure why, but hey, why not.
* Joystick support. As soon as I find a USB joystick that vaguely looks retro, I'll get one.
* The main window is 1280x720 on purpose, so it could do Full Screen.
* Port it to Raspbery Pi. I don't expect compiling issues, just video issues with GLes
* Make a tool to 'flatten' overlay files back into the primary image.
* Some sort of UI to select/eject disks.
![Total Replay](docs/screen_total.png)
*Obligatory View of [Total Replay](https://github.com/a2-4am/4cade), from legend [4am](https://github.com/a2-4am)*
## Inspiration, Licence etc
* MIT Licence, I think this is the most permissive, and this work is a derivative and has a lot of inpsiration from too many projects to claim anything more restrictive.
* The CPU Emulation was inspired by a few other implementations:
* [Chips](https://github.com/floooh/chips) -- which I used for a little while, but it was a pain to add the 65c02 instructions, and it had stuff I didn't want/need like 6510 emulation.
* [Apple2ts](https://github.com/ct6502/apple2ts/) -- In fact I converted a few bits from there; and I got the idea for the built-in assember from there, and some of the unit tests, AND the Smartport driver emulation idea!
* Other bits were inspired by:
* [bobbin](https://github.com/micahcowan/bobbin) which is newish as well, and is great for text mode, but I didn't like the fact its' all globals etc. I still borrowed the Floppy emulation from there, until I get around to do one.
* [isapple2](https://github.com/ivanizag/izapple2/) for other bits and pieces.
* And of course, countless books, articles and posts read over the last 40 years!

379
contrib/incbin.h Normal file
View File

@ -0,0 +1,379 @@
/**
* @file incbin.h
* @author Dale Weiler
* @brief Utility for including binary files
*
* Facilities for including binary files into the current translation unit and
* making use from them externally in other translation units.
*/
#ifndef INCBIN_HDR
#define INCBIN_HDR
#include <limits.h>
// Michel addition:
// Allow the included file to have an extra zero, to include text files
// as plain zero terminated strings
#ifdef INCBIN_TRAILING_ZERO
#define INCBIN_TRAIL INCBIN_BYTE "0\n"
#else
#define INCBIN_TRAIL
#endif
#if defined(__AVX512BW__) || \
defined(__AVX512CD__) || \
defined(__AVX512DQ__) || \
defined(__AVX512ER__) || \
defined(__AVX512PF__) || \
defined(__AVX512VL__) || \
defined(__AVX512F__)
# define INCBIN_ALIGNMENT_INDEX 6
#elif defined(__AVX__) || \
defined(__AVX2__)
# define INCBIN_ALIGNMENT_INDEX 5
#elif defined(__SSE__) || \
defined(__SSE2__) || \
defined(__SSE3__) || \
defined(__SSSE3__) || \
defined(__SSE4_1__) || \
defined(__SSE4_2__) || \
defined(__neon__)
# define INCBIN_ALIGNMENT_INDEX 4
#elif ULONG_MAX != 0xffffffffu
# define INCBIN_ALIGNMENT_INDEX 3
# else
# define INCBIN_ALIGNMENT_INDEX 2
#endif
/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */
#define INCBIN_ALIGN_SHIFT_0 1
#define INCBIN_ALIGN_SHIFT_1 2
#define INCBIN_ALIGN_SHIFT_2 4
#define INCBIN_ALIGN_SHIFT_3 8
#define INCBIN_ALIGN_SHIFT_4 16
#define INCBIN_ALIGN_SHIFT_5 32
#define INCBIN_ALIGN_SHIFT_6 64
/* Actual alignment value */
#define INCBIN_ALIGNMENT \
INCBIN_CONCATENATE( \
INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \
INCBIN_ALIGNMENT_INDEX)
/* Stringize */
#define INCBIN_STR(X) \
#X
#define INCBIN_STRINGIZE(X) \
INCBIN_STR(X)
/* Concatenate */
#define INCBIN_CAT(X, Y) \
X ## Y
#define INCBIN_CONCATENATE(X, Y) \
INCBIN_CAT(X, Y)
/* Deferred macro expansion */
#define INCBIN_EVAL(X) \
X
#define INCBIN_INVOKE(N, ...) \
INCBIN_EVAL(N(__VA_ARGS__))
/* Green Hills uses a different directive for including binary data */
#if defined(__ghs__)
# if (__ghs_asm == 2)
# define INCBIN_MACRO ".file"
/* Or consider the ".myrawdata" entry in the ld file */
# else
# define INCBIN_MACRO "\tINCBIN"
# endif
#else
# define INCBIN_MACRO ".incbin"
#endif
#ifndef _MSC_VER
# define INCBIN_ALIGN \
__attribute__((aligned(INCBIN_ALIGNMENT)))
#else
# define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT))
#endif
#if defined(__arm__) || /* GNU C and RealView */ \
defined(__arm) || /* Diab */ \
defined(_ARM) /* ImageCraft */
# define INCBIN_ARM
#endif
#ifdef __GNUC__
/* Utilize .balign where supported */
# define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
# define INCBIN_ALIGN_BYTE ".balign 1\n"
#elif defined(INCBIN_ARM)
/*
* On arm assemblers, the alignment value is calculated as (1 << n) where `n' is
* the shift count. This is the value passed to `.align'
*/
# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n"
# define INCBIN_ALIGN_BYTE ".align 0\n"
#else
/* We assume other inline assembler's treat `.align' as `.balign' */
# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
# define INCBIN_ALIGN_BYTE ".align 1\n"
#endif
/* INCBIN_CONST is used by incbin.c generated files */
#if defined(__cplusplus)
# define INCBIN_EXTERNAL extern "C"
# define INCBIN_CONST extern const
#else
# define INCBIN_EXTERNAL extern
# define INCBIN_CONST const
#endif
/**
* @brief Optionally override the linker section into which data is emitted.
*
* @warning If you use this facility, you'll have to deal with platform-specific linker output
* section naming on your own
*
* Overriding the default linker output section, e.g for esp8266/Arduino:
* @code
* #define INCBIN_OUTPUT_SECTION ".irom.text"
* #include "incbin.h"
* INCBIN(Foo, "foo.txt");
* // Data is emitted into program memory that never gets copied to RAM
* @endcode
*/
#if !defined(INCBIN_OUTPUT_SECTION)
# if defined(__APPLE__)
# define INCBIN_OUTPUT_SECTION ".const_data"
# else
# define INCBIN_OUTPUT_SECTION ".rodata"
# endif
#endif
#if defined(__APPLE__)
/* The directives are different for Apple branded compilers */
# define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n"
# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
# define INCBIN_INT ".long "
# define INCBIN_MANGLE "_"
# define INCBIN_BYTE ".byte "
# define INCBIN_TYPE(...)
#else
# define INCBIN_SECTION ".section " INCBIN_OUTPUT_SECTION "\n"
# define INCBIN_GLOBAL(NAME) ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
# if defined(__ghs__)
# define INCBIN_INT ".word "
# else
# define INCBIN_INT ".int "
# endif
# if defined(__USER_LABEL_PREFIX__)
# define INCBIN_MANGLE INCBIN_STRINGIZE(__USER_LABEL_PREFIX__)
# else
# define INCBIN_MANGLE ""
# endif
# if defined(INCBIN_ARM)
/* On arm assemblers, `@' is used as a line comment token */
# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n"
# elif defined(__MINGW32__) || defined(__MINGW64__)
/* Mingw doesn't support this directive either */
# define INCBIN_TYPE(NAME)
# else
/* It's safe to use `@' on other architectures */
# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n"
# endif
# define INCBIN_BYTE ".byte "
#endif
/* List of style types used for symbol names */
#define INCBIN_STYLE_CAMEL 0
#define INCBIN_STYLE_SNAKE 1
/**
* @brief Specify the prefix to use for symbol names.
*
* By default this is `g', producing symbols of the form:
* @code
* #include "incbin.h"
* INCBIN(Foo, "foo.txt");
*
* // Now you have the following symbols:
* // const unsigned char gFooData[];
* // const unsigned char *const gFooEnd;
* // const unsigned int gFooSize;
* @endcode
*
* If however you specify a prefix before including: e.g:
* @code
* #define INCBIN_PREFIX incbin
* #include "incbin.h"
* INCBIN(Foo, "foo.txt");
*
* // Now you have the following symbols instead:
* // const unsigned char incbinFooData[];
* // const unsigned char *const incbinFooEnd;
* // const unsigned int incbinFooSize;
* @endcode
*/
#if !defined(INCBIN_PREFIX)
# define INCBIN_PREFIX g
#endif
/**
* @brief Specify the style used for symbol names.
*
* Possible options are
* - INCBIN_STYLE_CAMEL "CamelCase"
* - INCBIN_STYLE_SNAKE "snake_case"
*
* Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form:
* @code
* #include "incbin.h"
* INCBIN(Foo, "foo.txt");
*
* // Now you have the following symbols:
* // const unsigned char <prefix>FooData[];
* // const unsigned char *const <prefix>FooEnd;
* // const unsigned int <prefix>FooSize;
* @endcode
*
* If however you specify a style before including: e.g:
* @code
* #define INCBIN_STYLE INCBIN_STYLE_SNAKE
* #include "incbin.h"
* INCBIN(foo, "foo.txt");
*
* // Now you have the following symbols:
* // const unsigned char <prefix>foo_data[];
* // const unsigned char *const <prefix>foo_end;
* // const unsigned int <prefix>foo_size;
* @endcode
*/
#if !defined(INCBIN_STYLE)
# define INCBIN_STYLE INCBIN_STYLE_CAMEL
#endif
/* Style lookup tables */
#define INCBIN_STYLE_0_DATA Data
#define INCBIN_STYLE_0_END End
#define INCBIN_STYLE_0_SIZE Size
#define INCBIN_STYLE_1_DATA _data
#define INCBIN_STYLE_1_END _end
#define INCBIN_STYLE_1_SIZE _size
/* Style lookup: returning identifier */
#define INCBIN_STYLE_IDENT(TYPE) \
INCBIN_CONCATENATE( \
INCBIN_STYLE_, \
INCBIN_CONCATENATE( \
INCBIN_EVAL(INCBIN_STYLE), \
INCBIN_CONCATENATE(_, TYPE)))
/* Style lookup: returning string literal */
#define INCBIN_STYLE_STRING(TYPE) \
INCBIN_STRINGIZE( \
INCBIN_STYLE_IDENT(TYPE)) \
/* Generate the global labels by indirectly invoking the macro with our style
* type and concatenating the name against them. */
#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \
INCBIN_INVOKE( \
INCBIN_GLOBAL, \
INCBIN_CONCATENATE( \
NAME, \
INCBIN_INVOKE( \
INCBIN_STYLE_IDENT, \
TYPE))) \
INCBIN_INVOKE( \
INCBIN_TYPE, \
INCBIN_CONCATENATE( \
NAME, \
INCBIN_INVOKE( \
INCBIN_STYLE_IDENT, \
TYPE)))
/**
* @brief Externally reference binary data included in another translation unit.
*
* Produces three external symbols that reference the binary data included in
* another translation unit.
*
* The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
* "Data", as well as "End" and "Size" after. An example is provided below.
*
* @param NAME The name given for the binary data
*
* @code
* INCBIN_EXTERN(Foo);
*
* // Now you have the following symbols:
* // extern const unsigned char <prefix>FooData[];
* // extern const unsigned char *const <prefix>FooEnd;
* // extern const unsigned int <prefix>FooSize;
* @endcode
*/
#define INCBIN_EXTERN(NAME) \
INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char \
INCBIN_CONCATENATE( \
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
INCBIN_STYLE_IDENT(DATA))[]; \
INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char *const \
INCBIN_CONCATENATE( \
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
INCBIN_STYLE_IDENT(END)); \
INCBIN_EXTERNAL const unsigned int \
INCBIN_CONCATENATE( \
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
INCBIN_STYLE_IDENT(SIZE))
/**
* @brief Include a binary file into the current translation unit.
*
* Includes a binary file into the current translation unit, producing three symbols
* for objects that encode the data and size respectively.
*
* The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
* "Data", as well as "End" and "Size" after. An example is provided below.
*
* @param NAME The name to associate with this binary data (as an identifier.)
* @param FILENAME The file to include (as a string literal.)
*
* @code
* INCBIN(Icon, "icon.png");
*
* // Now you have the following symbols:
* // const unsigned char <prefix>IconData[];
* // const unsigned char *const <prefix>IconEnd;
* // const unsigned int <prefix>IconSize;
* @endcode
*
* @warning This must be used in global scope
* @warning The identifiers may be different if INCBIN_STYLE is not default
*
* To externally reference the data included by this in another translation unit
* please @see INCBIN_EXTERN.
*/
#ifdef _MSC_VER
#define INCBIN(NAME, FILENAME) \
INCBIN_EXTERN(NAME)
#else
#define INCBIN(NAME, FILENAME) \
__asm__(INCBIN_SECTION \
INCBIN_GLOBAL_LABELS(NAME, DATA) \
INCBIN_ALIGN_HOST \
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \
INCBIN_MACRO " \"" FILENAME "\"\n" \
INCBIN_TRAIL \
INCBIN_GLOBAL_LABELS(NAME, END) \
INCBIN_ALIGN_BYTE \
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \
INCBIN_BYTE "1\n" \
INCBIN_GLOBAL_LABELS(NAME, SIZE) \
INCBIN_ALIGN_HOST \
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \
INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \
INCBIN_ALIGN_HOST \
".text\n" \
); \
INCBIN_EXTERN(NAME)
#endif
#endif

2413
contrib/libsofd.c Normal file

File diff suppressed because it is too large Load Diff

194
contrib/libsofd.h Normal file
View File

@ -0,0 +1,194 @@
/* libSOFD - Simple Open File Dialog [for X11 without toolkit]
*
* Copyright (C) 2014 Robin Gareus <robin@gareus.org>
*
* 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 LIBSOFD_H
#define LIBSOFD_H 1
#ifdef HAVE_X11
#include <X11/Xlib.h>
#ifdef __cplusplus
extern "C" {
#endif
///////////////////////////////////////////////////////////////////////////////
/* public API */
/** open a file select dialog
* @param dpy X Display connection
* @param parent (optional) if not NULL, become transient for given window
* @param x if >0 set explict initial width of the window
* @param y if >0 set explict initial height of the window
* @return 0 on success
*/
int x_fib_show (Display *dpy, Window parent, int x, int y);
/** force close the dialog.
* This is normally not needed, the dialog closes itself
* when a file is selected or the user cancels selection.
* @param dpy X Display connection
*/
void x_fib_close (Display *dpy);
/** non-blocking X11 event handler.
* It is safe to run this function even if the dialog is
* closed or was not initialized.
*
* @param dpy X Display connection
* @param event the XEvent to process
* @return status
* 0: the event was not for this window, or file-dialog still
* active, or the dialog window is not displayed.
* >0: file was selected, dialog closed
* <0: file selection was cancelled.
*/
int x_fib_handle_events (Display *dpy, XEvent *event);
/** last status of the dialog
* @return >0: file was selected, <0: canceled or inactive. 0: active
*/
int x_fib_status ();
/** query the selected filename
* @return NULL if none set, or allocated string to be free()ed by the called
*/
char *x_fib_filename ();
/** customize/configure the dialog before calling \ref x_fib_show
* changes only have any effect if the dialog is not visible.
* @param k key to change
* 0: set current dir to display (must end with slash)
* 1: set title of dialog window
* 2: specify a custom X11 font to use
* 3: specify a custom 'places' file to include
* (following gtk-bookmark convention)
* @param v value
* @return 0 on success.
*/
int x_fib_configure (int k, const char *v);
/** customize/configure the dialog before calling \ref x_fib_show
* changes only have any effect if the dialog is not visible.
*
* @param k button to change:
* 1: show hidden files
* 2: show places
* 3: show filter/list all (automatically hidden if there is no
* filter function)
* @param v <0 to hide the button >=0 show button,
* 0: set button-state to not-checked
* 1: set button-state to checked
* >1: retain current state
* @return 0 on success.
*/
int x_fib_cfg_buttons (int k, int v);
/** set custom callback to filter file-names.
* NULL will disable the filter and hide the 'show all' button.
* changes only have any effect if the dialog is not visible.
*
* @param cb callback function to check file
* the callback function is called with the file name (basename only)
* and is expected to return 1 if the file passes the filter
* and 0 if the file should not be listed by default.
* @return 0 on success.
*/
int x_fib_cfg_filter_callback (int (*cb)(const char*));
#ifdef __cplusplus
}
#endif
#endif /* END X11 specific functions */
#ifdef __cplusplus
extern "C" {
#endif
/* 'recently used' API. x-platform
* NOTE: all functions use a static cache and are not reentrant.
* It is expected that none of these functions are called in
* parallel from different threads.
*/
/** release static resources of 'recently used files'
*/
void x_fib_free_recent ();
/** add an entry to the recently used list
*
* The dialog does not add files automatically on open,
* if the application succeeds to open a selected file,
* this function should be called.
*
* @param path complete path to file
* @param atime time of last use, 0: NOW
* @return -1 on error, number of current entries otherwise
*/
int x_fib_add_recent (const char *path, time_t atime);
/** get a platform specific path to a good location for
* saving the recently used file list.
* (follows XDG_DATA_HOME on Unix, and CSIDL_LOCAL_APPDATA spec)
*
* @param application-name to use to include in file
* @return pointer to static path or NULL
*/
const char *x_fib_recent_file(const char *appname);
/** save the current list of recently used files to the given filename
* (the format is one file per line, filename URL encoded and space separated
* with last-used timestamp)
*
* This function tries to creates the containing directory if it does
* not exist.
*
* @param fn file to save the list to
* @return 0: on success
*/
int x_fib_save_recent (const char *fn);
/** load a recently used file list.
*
* @param fn file to load the list from
* @return 0: on success
*/
int x_fib_load_recent (const char *fn);
/** get number of entries in the current list
* @return number of entries in the recently used list
*/
unsigned int x_fib_recent_count ();
/** get recently used entry at given position
*
* @param i entry to query
* @return pointer to static string
*/
const char *x_fib_recent_at (unsigned int i);
#ifdef __cplusplus
}
#endif
#endif // header guard

289
contrib/md5.c Normal file
View File

@ -0,0 +1,289 @@
/*
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
* MD5 Message-Digest Algorithm (RFC 1321).
*
* Homepage:
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
*
* Author:
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
*
* This software was written by Alexander Peslyak in 2001. No copyright is
* claimed, and the software is hereby placed in the public domain.
* In case this attempt to disclaim copyright and place the software in the
* public domain is deemed null and void, then the software is
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
* general public under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* There's ABSOLUTELY NO WARRANTY, express or implied.
*
* (This is a heavily cut-down "BSD license".)
*
* This differs from Colin Plumb's older public domain implementation in that
* no exactly 32-bit integer data type is required (any 32-bit or wider
* unsigned integer data type will do), there's no compile-time endianness
* configuration, and the function prototypes match OpenSSL's. No code from
* Colin Plumb's implementation has been reused; this comment merely compares
* the properties of the two independent implementations.
*
* The primary goals of this implementation are portability and ease of use.
* It is meant to be fast, but not as fast as possible. Some known
* optimizations are not included to reduce source code size and avoid
* compile-time configuration.
*/
#ifndef HAVE_OPENSSL
#include <string.h>
#include "md5.h"
/*
* The basic MD5 functions.
*
* F and G are optimized compared to their RFC 1321 definitions for
* architectures that lack an AND-NOT instruction, just like in Colin Plumb's
* implementation.
*/
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
#define H(x, y, z) (((x) ^ (y)) ^ (z))
#define H2(x, y, z) ((x) ^ ((y) ^ (z)))
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
/*
* The MD5 transformation for all four rounds.
*/
#define STEP(f, a, b, c, d, x, t, s) \
(a) += f((b), (c), (d)) + (x) + (t); \
(a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \
(a) += (b);
/*
* SET reads 4 input bytes in little-endian byte order and stores them in a
* properly aligned word in host byte order.
*
* The check for little-endian architectures that tolerate unaligned memory
* accesses is just an optimization. Nothing will break if it fails to detect
* a suitable architecture.
*
* Unfortunately, this optimization may be a C strict aliasing rules violation
* if the caller's data buffer has effective type that cannot be aliased by
* MD5_u32plus. In practice, this problem may occur if these MD5 routines are
* inlined into a calling function, or with future and dangerously advanced
* link-time optimizations. For the time being, keeping these MD5 routines in
* their own translation unit avoids the problem.
*/
#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
#define SET(n) \
(*(MD5_u32plus *)&ptr[(n) * 4])
#define GET(n) \
SET(n)
#else
#define SET(n) \
(ctx->block[(n)] = \
(MD5_u32plus)ptr[(n) * 4] | \
((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \
((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \
((MD5_u32plus)ptr[(n) * 4 + 3] << 24))
#define GET(n) \
(ctx->block[(n)])
#endif
/*
* This processes one or more 64-byte data blocks, but does NOT update the bit
* counters. There are no alignment requirements.
*/
static const void *body(MD5_CTX *ctx, const void *data, unsigned long size)
{
const unsigned char *ptr;
MD5_u32plus a, b, c, d;
MD5_u32plus saved_a, saved_b, saved_c, saved_d;
ptr = (const unsigned char *)data;
a = ctx->a;
b = ctx->b;
c = ctx->c;
d = ctx->d;
do {
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
/* Round 1 */
STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)
STEP(F, c, d, a, b, SET(2), 0x242070db, 17)
STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)
STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)
STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)
STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)
STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)
STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)
STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)
STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)
/* Round 2 */
STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)
/* Round 3 */
STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11)
STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23)
STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11)
STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23)
STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11)
STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23)
STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11)
STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23)
/* Round 4 */
STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
ptr += 64;
} while (size -= 64);
ctx->a = a;
ctx->b = b;
ctx->c = c;
ctx->d = d;
return ptr;
}
void MD5_Init(MD5_CTX *ctx)
{
ctx->a = 0x67452301;
ctx->b = 0xefcdab89;
ctx->c = 0x98badcfe;
ctx->d = 0x10325476;
ctx->lo = 0;
ctx->hi = 0;
}
void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size)
{
MD5_u32plus saved_lo;
unsigned long used, available;
saved_lo = ctx->lo;
if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
ctx->hi++;
ctx->hi += size >> 29;
used = saved_lo & 0x3f;
if (used) {
available = 64 - used;
if (size < available) {
memcpy(&ctx->buffer[used], data, size);
return;
}
memcpy(&ctx->buffer[used], data, available);
data = (const unsigned char *)data + available;
size -= available;
body(ctx, ctx->buffer, 64);
}
if (size >= 64) {
data = body(ctx, data, size & ~(unsigned long)0x3f);
size &= 0x3f;
}
memcpy(ctx->buffer, data, size);
}
#define OUT(dst, src) \
(dst)[0] = (unsigned char)(src); \
(dst)[1] = (unsigned char)((src) >> 8); \
(dst)[2] = (unsigned char)((src) >> 16); \
(dst)[3] = (unsigned char)((src) >> 24);
void MD5_Final(unsigned char *result, MD5_CTX *ctx)
{
unsigned long used, available;
used = ctx->lo & 0x3f;
ctx->buffer[used++] = 0x80;
available = 64 - used;
if (available < 8) {
memset(&ctx->buffer[used], 0, available);
body(ctx, ctx->buffer, 64);
used = 0;
available = 64;
}
memset(&ctx->buffer[used], 0, available - 8);
ctx->lo <<= 3;
OUT(&ctx->buffer[56], ctx->lo)
OUT(&ctx->buffer[60], ctx->hi)
body(ctx, ctx->buffer, 64);
OUT(&result[0], ctx->a)
OUT(&result[4], ctx->b)
OUT(&result[8], ctx->c)
OUT(&result[12], ctx->d)
memset(ctx, 0, sizeof(*ctx));
}
#endif

43
contrib/md5.h Normal file
View File

@ -0,0 +1,43 @@
/*
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
* MD5 Message-Digest Algorithm (RFC 1321).
*
* Homepage:
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
*
* Author:
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
*
* This software was written by Alexander Peslyak in 2001. No copyright is
* claimed, and the software is hereby placed in the public domain.
* In case this attempt to disclaim copyright and place the software in the
* public domain is deemed null and void, then the software is
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
* general public under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* There's ABSOLUTELY NO WARRANTY, express or implied.
*
* See md5.c for more information.
*/
#ifndef _MD5_H
#define _MD5_H
/* Any 32-bit or wider unsigned integer data type will do */
typedef unsigned int MD5_u32plus;
typedef struct {
MD5_u32plus lo, hi;
MD5_u32plus a, b, c, d;
unsigned char buffer[64];
MD5_u32plus block[16];
} MD5_CTX;
void MD5_Init(MD5_CTX *ctx);
void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size);
void MD5_Final(unsigned char *result, MD5_CTX *ctx);
#endif /* _MD5_H */

1724
contrib/stb_image_write.h Normal file

File diff suppressed because it is too large Load Diff

BIN
disks/Games1MB.po Normal file

Binary file not shown.

1
disks/dos33master.nib generated Normal file

File diff suppressed because one or more lines are too long

BIN
disks/prodos242.dsk Normal file

Binary file not shown.

33
docs/Compiling.md Normal file
View File

@ -0,0 +1,33 @@
## How to I compile it and run it?
* You need a C compiler, make, and a few libraries:
* libasound2-dev [ optional, for audio ]
* libgl-dev
* libglu-dev
* libx11-dev
* Many of them will probably be installed already if you regulargly compile stuff.
* Then just type `make` and it should compile.
* To run it, just type `build-x86_64-linux-gnu/bin/bin/mii_emu` and it should start.
## Development
* This is the first project I made using vscode as my main IDE; I used Eclipse for many years as the indexer was pretty much unbeatable (still is).
* To support vscode I needed some tooling to create the '*compile_commands.json*' file, which is used by the C/C++ extension to provide intellisense. So to create that file, you use `make lsp` and it will create the file in the project directory.
* Another thing vscode sucks at is Makefiles (despite the extension, building is painful and next to useless). So there is a target called `make watch` that you can run in a separate terminal to auto-build as soon as you same any files in vscode. That is when you realize for example that vscode re-save the file anytime you press control-S, regardless of wether the file has changed or not. So you end up with a lot of unnecessary builds. I'm sure there is a way to fix that, but I haven't found it yet.
## Code Style
I have pretty consistent code style across my projects.
* tabs=4. Just because.
* 80 columns lines or so. I don't like to scroll horizontally and I like splitting my screen vertically. (also see, tabs=4!).
* K&R style exclusively. None of that Allman/GNU horribleness.
* No Yoda, we are no longer in 1975. `if (0 == foo)` is just stupid.
* I use !! to convert to 1 or zero from an expression. I call it the 'normalisation operator'. it is not commonly seen, apart from places like the Linux kernel.
* I use *minipt.h* for protothreads. I like protothreads, and when you are aware of their limitations, they are a great tool. They are used for the video rendering, the no-slot-clock etc.
## What needs doing?
* I'm sure there are bugs. I haven't tested it on a lot of hardware, and apart from quite a few games and a few known productivity app, it had had very little extensive testing. So testing!
* In the code there are a few bits which needs fixing
* The mouse card is not working properly. It works for some (most importantly A2 Desktop), but it's not working properly. I suspect the VBL Interrupt handling.
* In mii.c, the *mii_page_table_update()* does work, but it sub-optimal, it was handy to debug the countless combination of soft-switches, but it is not the ideal way to handle that problem; it needs a proper switch-case statement.
* The floppy drive emulation was borrowed from bobbin, and it works, and it
got it all working, but it's definitely not matching the style of the rest of the codebase. It needs to be replaced.
* Plenty of the most complicated piece of code (video, memory mapping) load a dozen of soft-switches, it probably should use a separately maintained bit field that is maintained by the on/off switches, and would make it a lot easier to test for bit combinations.
* The static array of memory 'banks' works, but it prevents me easily making a memory extension card. It should be refactored at some point.

BIN
docs/screen_color.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 KiB

BIN
docs/screen_green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

BIN
docs/screen_main.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

BIN
docs/screen_mish.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
docs/screen_total.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
fonts/DroidSans.ttf Normal file

Binary file not shown.

20
fonts/FreeLicense.txt Normal file
View File

@ -0,0 +1,20 @@
KREATIVE SOFTWARE RELAY FONTS FREE USE LICENSE
version 1.2f
Permission is hereby granted, free of charge, to any person or entity (the "User") obtaining a copy of the included font files (the "Software") produced by Kreative Software, to utilize, display, embed, or redistribute the Software, subject to the following conditions:
1. The User may not sell copies of the Software for a fee.
1a. The User may give away copies of the Software free of charge provided this license and any documentation is included verbatim and credit is given to Kreative Korporation or Kreative Software.
2. The User may not modify, reverse-engineer, or create any derivative works of the Software.
3. Any Software carrying the following font names or variations thereof is not covered by this license and may not be used under the terms of this license: Jewel Hill, Miss Diode n Friends, This is Beckie's font!
3a. Any Software carrying a font name ending with the string "Pro CE" is not covered by this license and may not be used under the terms of this license.
4. This license becomes null and void if any of the above conditions are not met.
5. Kreative Software reserves the right to change this license at any time without notice.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE SOFTWARE OR FROM OTHER DEALINGS IN THE SOFTWARE.

BIN
fonts/PRNumber3.ttf Normal file

Binary file not shown.

BIN
fonts/PrintChar21.ttf Normal file

Binary file not shown.

BIN
fonts/ProggyClean.ttf Normal file

Binary file not shown.

2
libmish/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build-*
doc/.tags*

202
libmish/LICENSE-2.0.txt Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

98
libmish/Makefile Normal file
View File

@ -0,0 +1,98 @@
# Makefile
#
# Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
#
# SPDX-License-Identifier: Apache-2.0
SHELL = /bin/bash
SOV = 1
VERSION = 0.10
PKG = 1
TARGET = libmish
DESC = Program Shell Access
ifneq ($(CC), emcc)
BASE_LDFLAGS += -lutil
endif
BASE_LDFLAGS += -lpthread
EXTRA_LDFLAGS = $(BASE_LDFLAGS)
# Auto load all the .c files dependencies, and object files
LIBSRC := ${notdir ${wildcard src/*.c}}
# Tell make/gcc to find the files in VPATH
VPATH = src
VPATH += tests
IPATH = src
include ./Makefile.common
TOOLS =
TESTS = ${BIN}/mish_test \
${BIN}/mish_vt_test ${BIN}/mish_cmd_test \
${BIN}/mish_input_test \
${BIN}/mish_argv_make_test
all : tools tests
debug: all ${BIN}/mish_debug_test
@echo "*** mish_debug_test is dangerous, do not install anywhere"
.PHONY: static shared tools tests
static: $(LIB)/$(TARGET).a
shared: ${LIB}/$(TARGET).so.$(SOV)
tools: $(TOOLS)
tests: $(TESTS)
LIBOBJ := ${patsubst %, ${OBJ}/%, ${notdir ${LIBSRC:.c=.o}}}
$(LIB)/$(TARGET).a : $(LIBOBJ) | $(LIB)
$(LIB)/$(TARGET).so.$(SOV) : $(LIBOBJ) | $(LIB)/$(TARGET).a
# This rule OUGHT to be enough, but somehow it fails to take into account
# the 'shared' dependency, if its written explicitely as it is now, it
# works. I see no reason why it should not work, as the one a line down
# has no problem!!
#$(BIN)/%: | shared
$(TOOLS) $(TESTS): | shared
ifeq ($(CC), emcc)
$(BIN)/%: LDFLAGS_TARGET+=-L${LIB}
endif
$(BIN)/%: LDFLAGS_TARGET += -lmish -lrt
$(BIN)/mish_input_test: LDFLAGS_TARGET =
clean::
rm -f $(LIB)/$(TARGET).* $(TOOLS) $(TESTS)
install:
mkdir -p $(DESTDIR)/bin/ $(DESTDIR)/lib/ $(DESTDIR)/include/
for bin in $(TOOLS); do $(INSTALL) -m 0755 $$bin $(DESTDIR)/bin/$$(basename $$bin) ; done
$(INSTALL) -m 644 $(LIB)/$(TARGET).a $(DESTDIR)/lib/
$(INSTALL) -m 644 src/mish.h $(DESTDIR)/include/
rm -rf $(DESTDIR)/lib/$(TARGET).so*
$(INSTALL) -m 644 $(LIB)/$(TARGET).so.$(VERSION).$(SOV) $(DESTDIR)/lib/
cp -f -d $(LIB)/{$(TARGET).so.$(SOV),$(TARGET).so} $(DESTDIR)/lib/
mkdir -p $(DESTDIR)/lib/pkgconfig
sed -e "s|PREFIX|$(PREFIX)|" -e "s|VERSION|$(VERSION)|" \
mish.pc >$(DESTDIR)/lib/pkgconfig/mish.pc
deb :
rm -rf /tmp/$(TARGET) *.deb
make clean && make all && make install DESTDIR=/tmp/$(TARGET)/usr
mkdir -p $(BUILD)/debian; (cd $(BUILD)/debian; \
fpm -s dir -t deb -C /tmp/$(TARGET) -n $(TARGET) -v $(VERSION) \
--iteration $(PKG) \
--description "$(DESC)" \
usr/lib/{$(TARGET).so.$(SOV),$(TARGET).so.$(VERSION).$(SOV),$(TARGET).so} && \
fpm -s dir -t deb -C /tmp/$(TARGET) -n $(TARGET)-dev -v $(VERSION) \
--iteration $(PKG) \
--description "$(DESC) - development files" \
-d "$(TARGET) (= $(VERSION))" \
usr/lib/$(TARGET).a \
usr/lib/pkgconfig \
usr/include ; \
)

97
libmish/Makefile.common Normal file
View File

@ -0,0 +1,97 @@
# Makefile.common
#
# Copyright (C) 2012 Michel Pollet <buserror@gmail.com>
#
SHELL = /bin/bash
INSTALL ?= install
CC ?= gcc
CFLAGS ?= -O2 -Wall
O ?= .
PREFIX ?= /usr/local
ARCH := $(shell $(CC) -dumpmachine)
BUILDNAME := build-$(ARCH)
BUILD := $(O)/$(BUILDNAME)
OBJ = $(BUILD)/obj/$(TARGET)
BIN = $(BUILD)/bin
LIB = $(BUILD)/lib
DESTDIR = $(O)/build-$(ARCH)
EXTRA_CFLAGS += -g -fPIC --std=gnu99
EXTRA_CFLAGS += -ffunction-sections -fdata-sections
EXTRA_CFLAGS += ${patsubst %,-I%,${subst :, ,${IPATH}}}
ifeq (${shell uname},Linux)
EXTRA_LDFLAGS += -Wl,--relax,--gc-sections
READLINK = readlink -f
endif
ifeq (${shell uname},Darwin)
READLINK = echo
endif
ifeq ($(V),1)
Q :=
else
Q := @
endif
EXTRA_LDFLAGS += -L$(LIB) -Wl,-rpath,${shell $(READLINK) ${LIB}}
all: ${OBJ}
#
# Generic rules
#
$(OBJ) $(BIN) $(LIB):
+$(Q)echo " MKDIR" $@ && mkdir -p $@
$(LIB)/%.a : | $(LIB)
$(Q)echo " LIB ${@}" && \
$(AR) cr $@ $^ \
|| echo Error: $(AR) cru $@ $^
.SECONDARY:
$(OBJ)/%.o: %.c | $(OBJ)
$(Q)echo " CC" ${filter -O%, ${EXTRA_CFLAGS} $(CFLAGS) ${CFLAGS_TARGET}} "${<}" && \
${CC} -MMD ${EXTRA_CFLAGS} ${CPPFLAGS} ${CFLAGS} ${CFLAGS_TARGET} -c -o ${@} ${<} \
|| echo Error: ${CC} -MD ${EXTRA_CFLAGS} ${CPPFLAGS} ${CFLAGS} ${CFLAGS_TARGET} -c -o ${@} ${<}
$(BIN)/%: $(OBJ)/%.o | $(BIN)
$(Q)echo " LD ${*}" && \
${CC} -o ${@} $(filter %.o %.a,$^) \
${EXTRA_LDFLAGS} ${BASE_LDFLAGS} ${LDFLAGS_TARGET} ${LDFLAGS} ${LIBS_${*}} \
|| echo Error: ${CC} -o $(filter %.o %.a,$^) \
${EXTRA_LDFLAGS} ${BASE_LDFLAGS} ${LDFLAGS_TARGET} ${LDFLAGS} ${LIBS_${*}}
ifeq (${shell uname},Linux)
define soname
"-Wl,-soname,$(1)"
endef
else
define soname
endef
endif
$(LIB)/%.so.$(SOV) $(LIB)/%.so.$(VERSION).$(SOV) $(LIB)/%.so: | $(LIB)
$(Q)echo " LDSO $@" && \
{ \
naked=${shell basename ${basename $@}}; \
${CC} -shared \
${call soname,$$naked} \
-o $(LIB)/$$naked.$(VERSION).$(SOV) \
$(filter %.o %.a,$^) \
${EXTRA_LDFLAGS} ${BASE_LDFLAGS} ${LDFLAGS} && \
ln -sf $$naked.$(VERSION).$(SOV) ${LIB}/$$naked.$(SOV) && \
ln -sf $$naked.$(SOV) ${LIB}/$$naked ; \
} || echo ERROR: $@ -- relaunch with V=1
clean::
# rm -rf $(OBJ) $(BIN)/$(TARGET)
rm -rf $(BUILD)
# Include autogenerated dependencies
-include ${wildcard $(OBJ)/*.d}

111
libmish/README.md Normal file
View File

@ -0,0 +1,111 @@
## libmish TL;DR :
*libmish* is a library that allows you to add a command prompt to any of your own program. It captures the standard output and standard error file descriptor, create a log with this, and displays it back to you, with a command prompt.
You can then add your own commands to interract with your (running) program. Oh, *libmish* can also serve this to incoming telnet sessions (on a custom port), so you can connect to detached processes too.
## Demo
Consider this program. The only "new" things are the <u>#include</u>, and the <u>mish_prepare()</u> call. That gives you telnet access and will log all output, but not much else.
```C
#include "mish.h"
int cnt = 0;
int main()
{
mish_prepare(0);
while (1) {
sleep(1);
printf("Count %d\n", cnt++);
}
}
```
Now add this at the bottom of the file:
```C
/* And here is a command line action that can reset the 'cnt' variable */
static void _test_set_cnt(void * param, int argc, const char * argv[])
{
if (argc > 1) {
cnt = atoi(argv[1]);
} else {
fprintf(stderr,
"%s: syntax 'set XXX' to set the variable\n", argv[0]);
}
}
MISH_CMD_NAMES(set, "set");
MISH_CMD_HELP(set,
"set 'cnt' variable",
"test command for libmish!");
MISH_CMD_REGISTER(set, _test_set_cnt);
```
And here you go:
![Demo of libmish](doc/demo.gif)
As you can see, the stderr output is colorized. The program also told you the telnet port to call into (but you can also see it via the 'env' command later on).
Calling the 'set' command will change the main variable. Of course it doesn't been to be thread safe in this instance, if you want your command to run in a thread safe way, you have to use <u>mish_cmd_poll()</u> from your thread, this will run pending commands in your own context.
## Ok what's going on here, why do I need this?
Let's say, you have that program that runs for days. Or months, or years, and it has it's log in a log file and all is very well, but sometime, you'd like to just *interract* with it, say, check statistics, internal state, or just change a parameter or so. Or just pet it for the good job it's doing.
Traditionally, you'd just restart the program with new parameters, or use a *host* of other complicated methods to send commands to the running program, like signals, UNIX sockets and countless stuff like that.
*libmish* allows you to that poking around, without having to bother that much about it. Just add *libmish*, register some of your own commands, and you'll be able to 'connect' to your running program, check it's output history (without having to deal with log files) and use your own commands on your own running program.
## What does work?
Well, its' already quite useful:
* You can browse the history of the log of your program with Beg/Page Up/Down/End
* You can telnet in, and check the log too.
* You can display command history and use control-P/N to navigate it
* You can easily "register" your commands, with their own 'help'.
* It only requires one single "mish_prepare(...)" call at the start of yout main() to work.
## Security? What security?
Obviously, using mish in your program is an obvious security risk, you're
allowing anyone on that machine to temper with your program via telnet, there's no
safegards in place. *libmish* is really made for developement purpose on
machine that you trust.
Also, as much as I don't think I code like a dork, I didn't go mega full paranoia on security in the internals, so there's bound checking, but I can almost guarantee there must be ways of fooling the various parsers into crashing. Again this was all written in the space of 3 days, so I went for the goalpost as a hare, not as a tank!
If you want to make sure *libmish* is disabled on machine that you *don't* trust (ie, production), you can set an environment variable MISH_OFF=1 before launching the programs and it will prevent the library starting. But again, buyers beware.
As to why I use a TCP port (bound to 127.0.0.1), well it's because:
* I want to be able to use 'telnet' to connect...
* 'telnet' on linux doesn't handle UNIX sockets
* I need 'telnet' because it gives me the terminal window size.
... otherwise, I wouldn't have!
## Issues & todos & caveats
This is a brand new library, a whole ton of things works, but quite a few things don't, just yet.
Some bits I know I want but didn't *need* and some I've tried to fix, but haven't spent enough time on it to nail them down.
**TODO:**
* Currently the main thread uses select(), because it's portable. Need to add an epool() alternative one for linux.
* Display a timestamp for lines in the backlog.
* Display some sort of progressy-bar thing at the bottom when navigating the log.
* Add a UNIX stream socket access, but we'll have to use socat/netcat, stty raw and some sort of helper command line.
* Make the backlog expire after X hours/days. A stamp is already collected, but not used.
**BUGS:**
* Find a way to cleany quit without screwing up the terminal settings (telnet).
- If you have that problem, use 'resize' or 'reset' to make your terminal work again.
- Or better, use 'screen telnet xxx yyy' as screen is awesome at cleaning up.
- or __betterer__, use the "dis" disconnect command, that works.
- Backlog navigation doesn't take into account line wrapping.
## Why is it called *libmish* anyway, sounds silly.
Well since you think you're clever, "lib" is for library, "sh" is for *shell* and "mi" is for **me** aka, **michel** pronunced *me* *shell*. Geddit?

21
libmish/doc/Makefile Normal file
View File

@ -0,0 +1,21 @@
#
# Copyright 2008, 2009 Michel Pollet <buserror@gmail.com>
#
# This file is part of libmish.
#
# SPDX-License-Identifier: Apache-2.0
# For the callgraph, you need Graphviz, ruby and exuberant ctags.
# This is not generated in the normal code build
all: libmish_callgraph.pdf
libmish_callgraph.pdf:
ctags -f .tags ../src/*.[ch] 2>/dev/null && \
ruby ./tags_to_dot.rb .tags \
../src/*.c ../src/mish.h >.tags.dot && \
dot -Tpdf .tags.dot -o $@
clean:
rm -f .tags*

BIN
libmish/doc/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

99
libmish/doc/tags_to_dot.rb Executable file
View File

@ -0,0 +1,99 @@
#!/usr/bin/ruby
#
# Copyright 2008, 2009 Michel Pollet <buserror@gmail.com>
#
# This file is part of libmish.
#
# SPDX-License-Identifier: Apache-2.0
$files = Hash.new
$syms = Hash.new
tags = File.new(ARGV[0])
key = Array.new
while !tags.eof?
next unless tags.readline.chomp.match(/([^\t]+)\t([^\t]+)\t(.*)\$\/;"\t([^\t]+)/);
key[0] = $1;
key[1] = $2;
key[3] = $3;
key[4] = $4;
next if key[4] == 'm' or key[4] == 't' or key[4] == 's' or key[4] == 'e' or key[4] == 'v';
next if key[0].match(/[us]?int[0-9]+_t/);
next if key[0] == "ROM_BASED";
key[1].gsub!(/.*\/|\.[ch]$/,"");
unless $files.key? key[1]
$files[key[1]] = Hash.new
end
unless $files[key[1]].key? key[0]
$files[key[1]][key[0]] = Hash.new
$syms[key[0]] = key[1]
end
#puts key[0] + " = '#{key[4]}'"
end
puts "digraph dump { node [shape=rect]; compound=true; nodesep=.1; ranksep=2; rankdir=LR; concentrate=true; "
modules = Hash.new;
links = Array.new;
1.upto(ARGV.length-1) { |i|
use = File.new(ARGV[i])
# puts "<<<<<<<<FILE " + ARGV[i]
fil = ARGV[i].gsub(/.*\/|\.[ch]$/,"");
while !use.eof?
line = use.readline;
next if line.match(/[ \t]*\/\//);
line.gsub!(/[^a-zA-Z0-9_]/, " ");
line.gsub!(/[ \t]+/, " ");
# puts ">>>" + line
words = line.split(/[ \t]+/);
words.each { |w|
if $syms.key? w and $syms[w] != fil
unless $files[$syms[w]][w].key? fil
# puts w + " is in " + $syms[w]
$files[$syms[w]][w][fil] = 1
sym=w
unless modules.key? fil
modules[fil] = Array.new
end
modules[fil] += [ "\"cc_#{fil}_#{sym}\" [label=\"#{sym}\",color=\"gray\",height=\".08\",style=dotted];" ]
links += ["\"cc_#{fil}_#{sym}\" -> \"dd_#{$syms[w]}_#{sym}\";"]
end
end
}
end
}
$files.keys.each { |fil|
# puts "File #{fil} ?"
$files[fil].keys.each { |sym|
# puts "\tSym #{sym} : #{$files[fil][sym].length} ?"
if $files[fil][sym].length > 0
unless modules.key? fil
modules[fil] = Array.new
end
modules[fil] += [ "\"dd_#{fil}_#{sym}\" [label=\"#{sym}\"];" ]
end
}
}
modules.keys.each {|fil|
puts "\tsubgraph cluster_#{fil} {\n\t\tlabel=\"#{fil}\"; fontsize=\"18\"; "
modules[fil].each { |lin|
puts "\t\t#{lin}"
}
puts "\t}"
}
links.each { |lin|
puts "\t#{lin}"
}
puts "}"

13
libmish/mish.pc Normal file
View File

@ -0,0 +1,13 @@
# SPDX-License-Identifier: Apache-2.0
mish.pc:
prefix=PREFIX
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib
Name: libmish
Description: Adds command prompt for your program(s)
Version: VERSION
Cflags: -I${includedir}
Libs: -L${libdir} -lmish -lrt

860
libmish/src/bsd_queue.h Normal file
View File

@ -0,0 +1,860 @@
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)queue.h 8.5 (Berkeley) 8/20/94
* $FreeBSD$
*/
#ifndef _SYS_QUEUE_H_
#define _SYS_QUEUE_H_
//#include <sys/cdefs.h>
/*
* This file defines four types of data structures: singly-linked lists,
* singly-linked tail queues, lists and tail queues.
*
* A singly-linked list is headed by a single forward pointer. The elements
* are singly linked for minimum space and pointer manipulation overhead at
* the expense of O(n) removal for arbitrary elements. New elements can be
* added to the list after an existing element or at the head of the list.
* Elements being removed from the head of the list should use the explicit
* macro for this purpose for optimum efficiency. A singly-linked list may
* only be traversed in the forward direction. Singly-linked lists are ideal
* for applications with large datasets and few or no removals or for
* implementing a LIFO queue.
*
* A singly-linked tail queue is headed by a pair of pointers, one to the
* head of the list and the other to the tail of the list. The elements are
* singly linked for minimum space and pointer manipulation overhead at the
* expense of O(n) removal for arbitrary elements. New elements can be added
* to the list after an existing element, at the head of the list, or at the
* end of the list. Elements being removed from the head of the tail queue
* should use the explicit macro for this purpose for optimum efficiency.
* A singly-linked tail queue may only be traversed in the forward direction.
* Singly-linked tail queues are ideal for applications with large datasets
* and few or no removals or for implementing a FIFO queue.
*
* A list is headed by a single forward pointer (or an array of forward
* pointers for a hash table header). The elements are doubly linked
* so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before
* or after an existing element or at the head of the list. A list
* may be traversed in either direction.
*
* A tail queue is headed by a pair of pointers, one to the head of the
* list and the other to the tail of the list. The elements are doubly
* linked so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before or
* after an existing element, at the head of the list, or at the end of
* the list. A tail queue may be traversed in either direction.
*
* For details on the use of these macros, see the queue(3) manual page.
*
* Below is a summary of implemented functions where:
* + means the macro is available
* - means the macro is not available
* s means the macro is available but is slow (runs in O(n) time)
*
* SLIST LIST STAILQ TAILQ
* _HEAD + + + +
* _CLASS_HEAD + + + +
* _HEAD_INITIALIZER + + + +
* _ENTRY + + + +
* _CLASS_ENTRY + + + +
* _INIT + + + +
* _EMPTY + + + +
* _FIRST + + + +
* _NEXT + + + +
* _PREV - + - +
* _LAST - - + +
* _FOREACH + + + +
* _FOREACH_FROM + + + +
* _FOREACH_SAFE + + + +
* _FOREACH_FROM_SAFE + + + +
* _FOREACH_REVERSE - - - +
* _FOREACH_REVERSE_FROM - - - +
* _FOREACH_REVERSE_SAFE - - - +
* _FOREACH_REVERSE_FROM_SAFE - - - +
* _INSERT_HEAD + + + +
* _INSERT_BEFORE - + - +
* _INSERT_AFTER + + + +
* _INSERT_TAIL - - + +
* _CONCAT s s + +
* _REMOVE_AFTER + - + -
* _REMOVE_HEAD + - + -
* _REMOVE s + s +
* _SWAP + + + +
*
*/
#ifdef QUEUE_MACRO_DEBUG
#warn Use QUEUE_MACRO_DEBUG_TRACE and/or QUEUE_MACRO_DEBUG_TRASH
#define QUEUE_MACRO_DEBUG_TRACE
#define QUEUE_MACRO_DEBUG_TRASH
#endif
#ifdef QUEUE_MACRO_DEBUG_TRACE
/* Store the last 2 places the queue element or head was altered */
struct qm_trace {
unsigned long lastline;
unsigned long prevline;
const char *lastfile;
const char *prevfile;
};
#define TRACEBUF struct qm_trace trace;
#define TRACEBUF_INITIALIZER { __LINE__, 0, __FILE__, NULL } ,
#define QMD_TRACE_HEAD(head) do { \
(head)->trace.prevline = (head)->trace.lastline; \
(head)->trace.prevfile = (head)->trace.lastfile; \
(head)->trace.lastline = __LINE__; \
(head)->trace.lastfile = __FILE__; \
} while (0)
#define QMD_TRACE_ELEM(elem) do { \
(elem)->trace.prevline = (elem)->trace.lastline; \
(elem)->trace.prevfile = (elem)->trace.lastfile; \
(elem)->trace.lastline = __LINE__; \
(elem)->trace.lastfile = __FILE__; \
} while (0)
#else /* !QUEUE_MACRO_DEBUG_TRACE */
#define QMD_TRACE_ELEM(elem)
#define QMD_TRACE_HEAD(head)
#define TRACEBUF
#define TRACEBUF_INITIALIZER
#endif /* QUEUE_MACRO_DEBUG_TRACE */
#ifdef QUEUE_MACRO_DEBUG_TRASH
#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
#define QMD_IS_TRASHED(x) ((x) == (void *)(intptr_t)-1)
#else /* !QUEUE_MACRO_DEBUG_TRASH */
#define TRASHIT(x)
#define QMD_IS_TRASHED(x) 0
#endif /* QUEUE_MACRO_DEBUG_TRASH */
#if defined(QUEUE_MACRO_DEBUG_TRACE) || defined(QUEUE_MACRO_DEBUG_TRASH)
#define QMD_SAVELINK(name, link) void **name = (void *)&(link)
#else /* !QUEUE_MACRO_DEBUG_TRACE && !QUEUE_MACRO_DEBUG_TRASH */
#define QMD_SAVELINK(name, link)
#endif /* QUEUE_MACRO_DEBUG_TRACE || QUEUE_MACRO_DEBUG_TRASH */
#ifdef __cplusplus
/*
* In C++ there can be structure lists and class lists:
*/
#define QUEUE_TYPEOF(type) type
#else
#define QUEUE_TYPEOF(type) struct type
#endif
/*
* Singly-linked List declarations.
*/
#define SLIST_HEAD(name, type) \
struct name { \
struct type *slh_first; /* first element */ \
}
#define SLIST_CLASS_HEAD(name, type) \
struct name { \
class type *slh_first; /* first element */ \
}
#define SLIST_HEAD_INITIALIZER(head) \
{ NULL }
#define SLIST_ENTRY(type) \
struct { \
struct type *sle_next; /* next element */ \
}
#define SLIST_CLASS_ENTRY(type) \
struct { \
class type *sle_next; /* next element */ \
}
/*
* Singly-linked List functions.
*/
#if (defined(_KERNEL) && defined(INVARIANTS))
#define QMD_SLIST_CHECK_PREVPTR(prevp, elm) do { \
if (*(prevp) != (elm)) \
panic("Bad prevptr *(%p) == %p != %p", \
(prevp), *(prevp), (elm)); \
} while (0)
#else
#define QMD_SLIST_CHECK_PREVPTR(prevp, elm)
#endif
#define SLIST_CONCAT(head1, head2, type, field) do { \
QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head1); \
if (curelm == NULL) { \
if ((SLIST_FIRST(head1) = SLIST_FIRST(head2)) != NULL) \
SLIST_INIT(head2); \
} else if (SLIST_FIRST(head2) != NULL) { \
while (SLIST_NEXT(curelm, field) != NULL) \
curelm = SLIST_NEXT(curelm, field); \
SLIST_NEXT(curelm, field) = SLIST_FIRST(head2); \
SLIST_INIT(head2); \
} \
} while (0)
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_FOREACH(var, head, field) \
for ((var) = SLIST_FIRST((head)); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = SLIST_FIRST((head)); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
for ((varp) = &SLIST_FIRST((head)); \
((var) = *(varp)) != NULL; \
(varp) = &SLIST_NEXT((var), field))
#define SLIST_INIT(head) do { \
SLIST_FIRST((head)) = NULL; \
} while (0)
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
SLIST_NEXT((slistelm), field) = (elm); \
} while (0)
#define SLIST_INSERT_HEAD(head, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
SLIST_FIRST((head)) = (elm); \
} while (0)
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
#define SLIST_REMOVE(head, elm, type, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.sle_next); \
if (SLIST_FIRST((head)) == (elm)) { \
SLIST_REMOVE_HEAD((head), field); \
} \
else { \
QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head); \
while (SLIST_NEXT(curelm, field) != (elm)) \
curelm = SLIST_NEXT(curelm, field); \
SLIST_REMOVE_AFTER(curelm, field); \
} \
TRASHIT(*oldnext); \
} while (0)
#define SLIST_REMOVE_AFTER(elm, field) do { \
SLIST_NEXT(elm, field) = \
SLIST_NEXT(SLIST_NEXT(elm, field), field); \
} while (0)
#define SLIST_REMOVE_HEAD(head, field) do { \
SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
} while (0)
#define SLIST_REMOVE_PREVPTR(prevp, elm, field) do { \
QMD_SLIST_CHECK_PREVPTR(prevp, elm); \
*(prevp) = SLIST_NEXT(elm, field); \
TRASHIT((elm)->field.sle_next); \
} while (0)
#define SLIST_SWAP(head1, head2, type) do { \
QUEUE_TYPEOF(type) *swap_first = SLIST_FIRST(head1); \
SLIST_FIRST(head1) = SLIST_FIRST(head2); \
SLIST_FIRST(head2) = swap_first; \
} while (0)
/*
* Singly-linked Tail queue declarations.
*/
#define STAILQ_HEAD(name, type) \
struct name { \
struct type *stqh_first;/* first element */ \
struct type **stqh_last;/* addr of last next element */ \
}
#define STAILQ_CLASS_HEAD(name, type) \
struct name { \
class type *stqh_first; /* first element */ \
class type **stqh_last; /* addr of last next element */ \
}
#define STAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).stqh_first }
#define STAILQ_ENTRY(type) \
struct { \
struct type *stqe_next; /* next element */ \
}
#define STAILQ_CLASS_ENTRY(type) \
struct { \
class type *stqe_next; /* next element */ \
}
/*
* Singly-linked Tail queue functions.
*/
#define STAILQ_CONCAT(head1, head2) do { \
if (!STAILQ_EMPTY((head2))) { \
*(head1)->stqh_last = (head2)->stqh_first; \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_INIT((head2)); \
} \
} while (0)
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
#define STAILQ_FIRST(head) ((head)->stqh_first)
#define STAILQ_FOREACH(var, head, field) \
for((var) = STAILQ_FIRST((head)); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = STAILQ_FIRST((head)); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_INIT(head) do { \
STAILQ_FIRST((head)) = NULL; \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_NEXT((tqelm), field) = (elm); \
} while (0)
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_FIRST((head)) = (elm); \
} while (0)
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
STAILQ_NEXT((elm), field) = NULL; \
*(head)->stqh_last = (elm); \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_LAST(head, type, field) \
(STAILQ_EMPTY((head)) ? NULL : \
__containerof((head)->stqh_last, \
QUEUE_TYPEOF(type), field.stqe_next))
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
#define STAILQ_REMOVE(head, elm, type, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \
if (STAILQ_FIRST((head)) == (elm)) { \
STAILQ_REMOVE_HEAD((head), field); \
} \
else { \
QUEUE_TYPEOF(type) *curelm = STAILQ_FIRST(head); \
while (STAILQ_NEXT(curelm, field) != (elm)) \
curelm = STAILQ_NEXT(curelm, field); \
STAILQ_REMOVE_AFTER(head, curelm, field); \
} \
TRASHIT(*oldnext); \
} while (0)
#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
if ((STAILQ_NEXT(elm, field) = \
STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_REMOVE_HEAD(head, field) do { \
if ((STAILQ_FIRST((head)) = \
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_SWAP(head1, head2, type) do { \
QUEUE_TYPEOF(type) *swap_first = STAILQ_FIRST(head1); \
QUEUE_TYPEOF(type) **swap_last = (head1)->stqh_last; \
STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_FIRST(head2) = swap_first; \
(head2)->stqh_last = swap_last; \
if (STAILQ_EMPTY(head1)) \
(head1)->stqh_last = &STAILQ_FIRST(head1); \
if (STAILQ_EMPTY(head2)) \
(head2)->stqh_last = &STAILQ_FIRST(head2); \
} while (0)
/*
* List declarations.
*/
#define LIST_HEAD(name, type) \
struct name { \
struct type *lh_first; /* first element */ \
}
#define LIST_CLASS_HEAD(name, type) \
struct name { \
class type *lh_first; /* first element */ \
}
#define LIST_HEAD_INITIALIZER(head) \
{ NULL }
#define LIST_ENTRY(type) \
struct { \
struct type *le_next; /* next element */ \
struct type **le_prev; /* address of previous next element */ \
}
#define LIST_CLASS_ENTRY(type) \
struct { \
class type *le_next; /* next element */ \
class type **le_prev; /* address of previous next element */ \
}
/*
* List functions.
*/
#if (defined(_KERNEL) && defined(INVARIANTS))
/*
* QMD_LIST_CHECK_HEAD(LIST_HEAD *head, LIST_ENTRY NAME)
*
* If the list is non-empty, validates that the first element of the list
* points back at 'head.'
*/
#define QMD_LIST_CHECK_HEAD(head, field) do { \
if (LIST_FIRST((head)) != NULL && \
LIST_FIRST((head))->field.le_prev != \
&LIST_FIRST((head))) \
panic("Bad list head %p first->prev != head", (head)); \
} while (0)
/*
* QMD_LIST_CHECK_NEXT(TYPE *elm, LIST_ENTRY NAME)
*
* If an element follows 'elm' in the list, validates that the next element
* points back at 'elm.'
*/
#define QMD_LIST_CHECK_NEXT(elm, field) do { \
if (LIST_NEXT((elm), field) != NULL && \
LIST_NEXT((elm), field)->field.le_prev != \
&((elm)->field.le_next)) \
panic("Bad link elm %p next->prev != elm", (elm)); \
} while (0)
/*
* QMD_LIST_CHECK_PREV(TYPE *elm, LIST_ENTRY NAME)
*
* Validates that the previous element (or head of the list) points to 'elm.'
*/
#define QMD_LIST_CHECK_PREV(elm, field) do { \
if (*(elm)->field.le_prev != (elm)) \
panic("Bad link elm %p prev->next != elm", (elm)); \
} while (0)
#else
#define QMD_LIST_CHECK_HEAD(head, field)
#define QMD_LIST_CHECK_NEXT(elm, field)
#define QMD_LIST_CHECK_PREV(elm, field)
#endif /* (_KERNEL && INVARIANTS) */
#define LIST_CONCAT(head1, head2, type, field) do { \
QUEUE_TYPEOF(type) *curelm = LIST_FIRST(head1); \
if (curelm == NULL) { \
if ((LIST_FIRST(head1) = LIST_FIRST(head2)) != NULL) { \
LIST_FIRST(head2)->field.le_prev = \
&LIST_FIRST((head1)); \
LIST_INIT(head2); \
} \
} else if (LIST_FIRST(head2) != NULL) { \
while (LIST_NEXT(curelm, field) != NULL) \
curelm = LIST_NEXT(curelm, field); \
LIST_NEXT(curelm, field) = LIST_FIRST(head2); \
LIST_FIRST(head2)->field.le_prev = &LIST_NEXT(curelm, field); \
LIST_INIT(head2); \
} \
} while (0)
#define LIST_EMPTY(head) ((head)->lh_first == NULL)
#define LIST_FIRST(head) ((head)->lh_first)
#define LIST_FOREACH(var, head, field) \
for ((var) = LIST_FIRST((head)); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = LIST_FIRST((head)); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_INIT(head) do { \
LIST_FIRST((head)) = NULL; \
} while (0)
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
QMD_LIST_CHECK_NEXT(listelm, field); \
if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
LIST_NEXT((listelm), field)->field.le_prev = \
&LIST_NEXT((elm), field); \
LIST_NEXT((listelm), field) = (elm); \
(elm)->field.le_prev = &LIST_NEXT((listelm), field); \
} while (0)
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
QMD_LIST_CHECK_PREV(listelm, field); \
(elm)->field.le_prev = (listelm)->field.le_prev; \
LIST_NEXT((elm), field) = (listelm); \
*(listelm)->field.le_prev = (elm); \
(listelm)->field.le_prev = &LIST_NEXT((elm), field); \
} while (0)
#define LIST_INSERT_HEAD(head, elm, field) do { \
QMD_LIST_CHECK_HEAD((head), field); \
if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
LIST_FIRST((head)) = (elm); \
(elm)->field.le_prev = &LIST_FIRST((head)); \
} while (0)
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
#define LIST_PREV(elm, head, type, field) \
((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \
__containerof((elm)->field.le_prev, \
QUEUE_TYPEOF(type), field.le_next))
#define LIST_REMOVE(elm, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.le_next); \
QMD_SAVELINK(oldprev, (elm)->field.le_prev); \
QMD_LIST_CHECK_NEXT(elm, field); \
QMD_LIST_CHECK_PREV(elm, field); \
if (LIST_NEXT((elm), field) != NULL) \
LIST_NEXT((elm), field)->field.le_prev = \
(elm)->field.le_prev; \
*(elm)->field.le_prev = LIST_NEXT((elm), field); \
TRASHIT(*oldnext); \
TRASHIT(*oldprev); \
} while (0)
#define LIST_SWAP(head1, head2, type, field) do { \
QUEUE_TYPEOF(type) *swap_tmp = LIST_FIRST(head1); \
LIST_FIRST((head1)) = LIST_FIRST((head2)); \
LIST_FIRST((head2)) = swap_tmp; \
if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
} while (0)
/*
* Tail queue declarations.
*/
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
TRACEBUF \
}
#define TAILQ_CLASS_HEAD(name, type) \
struct name { \
class type *tqh_first; /* first element */ \
class type **tqh_last; /* addr of last next element */ \
TRACEBUF \
}
#define TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first, TRACEBUF_INITIALIZER }
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
TRACEBUF \
}
#define TAILQ_CLASS_ENTRY(type) \
struct { \
class type *tqe_next; /* next element */ \
class type **tqe_prev; /* address of previous next element */ \
TRACEBUF \
}
/*
* Tail queue functions.
*/
#if (defined(_KERNEL) && defined(INVARIANTS))
/*
* QMD_TAILQ_CHECK_HEAD(TAILQ_HEAD *head, TAILQ_ENTRY NAME)
*
* If the tailq is non-empty, validates that the first element of the tailq
* points back at 'head.'
*/
#define QMD_TAILQ_CHECK_HEAD(head, field) do { \
if (!TAILQ_EMPTY(head) && \
TAILQ_FIRST((head))->field.tqe_prev != \
&TAILQ_FIRST((head))) \
panic("Bad tailq head %p first->prev != head", (head)); \
} while (0)
/*
* QMD_TAILQ_CHECK_TAIL(TAILQ_HEAD *head, TAILQ_ENTRY NAME)
*
* Validates that the tail of the tailq is a pointer to pointer to NULL.
*/
#define QMD_TAILQ_CHECK_TAIL(head, field) do { \
if (*(head)->tqh_last != NULL) \
panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \
} while (0)
/*
* QMD_TAILQ_CHECK_NEXT(TYPE *elm, TAILQ_ENTRY NAME)
*
* If an element follows 'elm' in the tailq, validates that the next element
* points back at 'elm.'
*/
#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \
if (TAILQ_NEXT((elm), field) != NULL && \
TAILQ_NEXT((elm), field)->field.tqe_prev != \
&((elm)->field.tqe_next)) \
panic("Bad link elm %p next->prev != elm", (elm)); \
} while (0)
/*
* QMD_TAILQ_CHECK_PREV(TYPE *elm, TAILQ_ENTRY NAME)
*
* Validates that the previous element (or head of the tailq) points to 'elm.'
*/
#define QMD_TAILQ_CHECK_PREV(elm, field) do { \
if (*(elm)->field.tqe_prev != (elm)) \
panic("Bad link elm %p prev->next != elm", (elm)); \
} while (0)
#else
#define QMD_TAILQ_CHECK_HEAD(head, field)
#define QMD_TAILQ_CHECK_TAIL(head, headname)
#define QMD_TAILQ_CHECK_NEXT(elm, field)
#define QMD_TAILQ_CHECK_PREV(elm, field)
#endif /* (_KERNEL && INVARIANTS) */
#define TAILQ_CONCAT(head1, head2, field) do { \
if (!TAILQ_EMPTY(head2)) { \
*(head1)->tqh_last = (head2)->tqh_first; \
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
(head1)->tqh_last = (head2)->tqh_last; \
TAILQ_INIT((head2)); \
QMD_TRACE_HEAD(head1); \
QMD_TRACE_HEAD(head2); \
} \
} while (0)
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_FOREACH(var, head, field) \
for ((var) = TAILQ_FIRST((head)); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = TAILQ_FIRST((head)); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for ((var) = TAILQ_LAST((head), headname); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
for ((var) = TAILQ_LAST((head), headname); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_INIT(head) do { \
TAILQ_FIRST((head)) = NULL; \
(head)->tqh_last = &TAILQ_FIRST((head)); \
QMD_TRACE_HEAD(head); \
} while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
QMD_TAILQ_CHECK_NEXT(listelm, field); \
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
TAILQ_NEXT((elm), field)->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else { \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
QMD_TRACE_HEAD(head); \
} \
TAILQ_NEXT((listelm), field) = (elm); \
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
QMD_TRACE_ELEM(&(elm)->field); \
QMD_TRACE_ELEM(&(listelm)->field); \
} while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
QMD_TAILQ_CHECK_PREV(listelm, field); \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
TAILQ_NEXT((elm), field) = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
QMD_TRACE_ELEM(&(elm)->field); \
QMD_TRACE_ELEM(&(listelm)->field); \
} while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
QMD_TAILQ_CHECK_HEAD(head, field); \
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
TAILQ_FIRST((head))->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_FIRST((head)) = (elm); \
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
QMD_TRACE_HEAD(head); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
QMD_TAILQ_CHECK_TAIL(head, field); \
TAILQ_NEXT((elm), field) = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
QMD_TRACE_HEAD(head); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_REMOVE(head, elm, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \
QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \
QMD_TAILQ_CHECK_NEXT(elm, field); \
QMD_TAILQ_CHECK_PREV(elm, field); \
if ((TAILQ_NEXT((elm), field)) != NULL) \
TAILQ_NEXT((elm), field)->field.tqe_prev = \
(elm)->field.tqe_prev; \
else { \
(head)->tqh_last = (elm)->field.tqe_prev; \
QMD_TRACE_HEAD(head); \
} \
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
TRASHIT(*oldnext); \
TRASHIT(*oldprev); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_SWAP(head1, head2, type, field) do { \
QUEUE_TYPEOF(type) *swap_first = (head1)->tqh_first; \
QUEUE_TYPEOF(type) **swap_last = (head1)->tqh_last; \
(head1)->tqh_first = (head2)->tqh_first; \
(head1)->tqh_last = (head2)->tqh_last; \
(head2)->tqh_first = swap_first; \
(head2)->tqh_last = swap_last; \
if ((swap_first = (head1)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head1)->tqh_first; \
else \
(head1)->tqh_last = &(head1)->tqh_first; \
if ((swap_first = (head2)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head2)->tqh_first; \
else \
(head2)->tqh_last = &(head2)->tqh_first; \
} while (0)
#endif /* !_SYS_QUEUE_H_ */

211
libmish/src/fifo_declare.h Normal file
View File

@ -0,0 +1,211 @@
/*
fido_declare.h
Copyright (C) 2003-2012 Michel Pollet <buserror@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* FIFO helpers, aka circular buffers
*
* these macros define accessories for FIFOs of any name, type and
* any (power of two) size
*/
#ifndef __FIFO_DECLARE__
#define __FIFO_DECLARE__
#ifdef __cplusplus
extern "C" {
#endif
/*
doing a :
DECLARE_FIFO(uint8_t, myfifo, 128);
will declare :
enum : myfifo_overflow_f
type : myfifo_t
functions:
// write a byte into the fifo, return 1 if there was room, 0 if there wasn't
int myfifo_write(myfifo_t *c, uint8_t b);
// reads a byte from the fifo, return 0 if empty. Use myfifo_isempty() to check beforehand
uint8_t myfifo_read(myfifo_t *c);
int myfifo_isfull(myfifo_t *c);
int myfifo_isempty(myfifo_t *c);
// returns number of items to read now
uint16_t myfifo_get_read_size(myfifo_t *c);
// read item at offset o from read cursor, no cursor advance
uint8_t myfifo_read_at(myfifo_t *c, uint16_t o);
// write b at offset o compared to current write cursor, no cursor advance
void myfifo_write_at(myfifo_t *c, uint16_t o, uint8_t b);
In your .c you need to 'implement' the fifo:
DEFINE_FIFO(uint8_t, myfifo)
To use the fifo, you must declare at least one :
myfifo_t fifo = FIFO_NULL;
while (!myfifo_isfull(&fifo))
myfifo_write(&fifo, 0xaa);
....
while (!myfifo_isempty(&fifo))
b = myfifo_read(&fifo);
*/
#include <stdint.h>
#if __AVR__
#define FIFO_CURSOR_TYPE uint8_t
#define FIFO_BOOL_TYPE char
#define FIFO_INLINE
#define FIFO_SYNC
#endif
#ifndef FIFO_CURSOR_TYPE
#define FIFO_CURSOR_TYPE uint16_t
#endif
#ifndef FIFO_BOOL_TYPE
#define FIFO_BOOL_TYPE int
#endif
#ifndef FIFO_INLINE
#define FIFO_INLINE inline
#endif
/* We should not need volatile */
#ifndef FIFO_VOLATILE
#define FIFO_VOLATILE
#endif
#ifndef FIFO_SYNC
#define FIFO_SYNC __sync_synchronize()
#endif
#ifndef FIFO_ZERO_INIT
#define FIFO_ZERO_INIT {0}
#endif
#define FIFO_NULL { FIFO_ZERO_INIT, 0, 0, 0 }
/* New compilers don't like unused static functions. However,
* we do like 'static inlines' for these small accessors,
* so we mark them as 'unused'. It stops it complaining */
#ifdef __GNUC__
#define FIFO_DECL static __attribute__ ((unused))
#else
#define FIFO_DECL static
#endif
#define DECLARE_FIFO(__type, __name, __size) \
enum { __name##_overflow_f = (1 << 0) }; \
enum { __name##_fifo_size = (__size) }; \
typedef struct __name##_t { \
__type buffer[__name##_fifo_size]; \
FIFO_VOLATILE FIFO_CURSOR_TYPE read; \
FIFO_VOLATILE FIFO_CURSOR_TYPE write; \
FIFO_VOLATILE uint8_t flags; \
} __name##_t
/*
* This allow declaring a FIFO without the functions that use
* pass-by-value arguments. The compiler isn't too happy using
* some of the variable length array these days and outputs a warning.
* Also, it is not efficient, so if your FIFO member is a big struct,
* you should use the pass by pointer instead.
*/
#define DEFINE_PTR_FIFO(__type, __name) \
FIFO_DECL FIFO_INLINE FIFO_BOOL_TYPE __name##_isfull(__name##_t *c)\
{\
FIFO_CURSOR_TYPE next = (c->write + 1) & (__name##_fifo_size-1);\
return c->read == next;\
}\
FIFO_DECL FIFO_INLINE FIFO_BOOL_TYPE __name##_isempty(__name##_t * c)\
{\
return c->read == c->write;\
}\
FIFO_DECL FIFO_INLINE FIFO_CURSOR_TYPE __name##_get_read_size(__name##_t *c)\
{\
return ((c->write + __name##_fifo_size) - c->read) & (__name##_fifo_size-1);\
}\
FIFO_DECL FIFO_INLINE FIFO_CURSOR_TYPE __name##_get_write_size(__name##_t *c)\
{\
return (__name##_fifo_size-1) - __name##_get_read_size(c);\
}\
FIFO_DECL FIFO_INLINE void __name##_read_offset(__name##_t *c, FIFO_CURSOR_TYPE o)\
{\
FIFO_SYNC; \
c->read = (c->read + o) & (__name##_fifo_size-1);\
}\
FIFO_DECL FIFO_INLINE void __name##_write_offset(__name##_t *c, FIFO_CURSOR_TYPE o)\
{\
FIFO_SYNC; \
c->write = (c->write + o) & (__name##_fifo_size-1);\
}\
FIFO_DECL FIFO_INLINE void __name##_reset(__name##_t *c)\
{\
FIFO_SYNC; \
c->read = c->write = c->flags = 0;\
}\
FIFO_DECL FIFO_INLINE __type * __name##_write_ptr(__name##_t * c) \
{\
return c->buffer + c->write;\
}\
FIFO_DECL FIFO_INLINE __type * __name##_read_ptr(__name##_t * c) \
{\
return c->buffer + c->read;\
}\
struct __name##_t
/*
* And this declares the whole FIFO, including the pass-by-value functions
*/
#define DEFINE_FIFO(__type, __name) \
DEFINE_PTR_FIFO(__type, __name); \
FIFO_DECL FIFO_INLINE FIFO_BOOL_TYPE __name##_write(__name##_t * c, __type b)\
{\
FIFO_CURSOR_TYPE now = c->write;\
FIFO_CURSOR_TYPE next = (now + 1) & (__name##_fifo_size-1);\
if (c->read != next) { \
c->buffer[now] = b;\
FIFO_SYNC; \
c->write = next;\
return 1;\
}\
return 0;\
}\
FIFO_DECL FIFO_INLINE __type __name##_read(__name##_t * c)\
{\
__type res = FIFO_ZERO_INIT; \
FIFO_CURSOR_TYPE read = c->read;\
if (read == c->write)\
return res;\
res = c->buffer[read];\
FIFO_SYNC; \
c->read = (read + 1) & (__name##_fifo_size-1);\
return res;\
}\
FIFO_DECL FIFO_INLINE __type __name##_read_at(__name##_t *c, FIFO_CURSOR_TYPE o)\
{\
return c->buffer[(c->read + o) & (__name##_fifo_size-1)];\
}\
FIFO_DECL FIFO_INLINE void __name##_write_at(__name##_t *c, FIFO_CURSOR_TYPE o, __type b)\
{\
c->buffer[(c->write + o) & (__name##_fifo_size-1)] = b;\
}\
struct __name##_t
#ifdef __cplusplus
};
#endif
#endif

138
libmish/src/minipt.h Normal file
View File

@ -0,0 +1,138 @@
/*
* minipt.h
*
* Created on: 1 Apr 2018
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef MPTOOLS_INCLUDE_MINIPT_H_
#define MPTOOLS_INCLUDE_MINIPT_H_
/*
* Mini Protothread.
*
* A thread or a coroutine would use a stack; this won't,
* Use an old gcc trick of being able to goto and indirect label.
* There are a few caveats: no persistent local variables, as you can't
* have a consistent stack frame. It's easy to work around tho.
*/
#define _CONCAT2(s1, s2) s1##s2
#define _CONCAT(s1, s2) _CONCAT2(s1, s2)
/* this wierd thing with the union is for gcc 12, which doesn't like us
* storing the address of a 'local variable' (which is the label!) */
static inline void _set_gcc_ptr_workaround(void **d, void *s) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-pointer"
*d = s;
#pragma GCC diagnostic pop
}
#define pt_start(_pt) do { \
if (_pt) goto *_pt; \
} while (0);
#define pt_end(_pt) do { \
(_pt) = NULL; \
_pt_exit: ; \
} while(0);
#define pt_yield(_pt) do { \
_set_gcc_ptr_workaround(&(_pt), &&_CONCAT(_label, __LINE__));\
goto _pt_exit;\
_CONCAT(_label, __LINE__): ; \
} while (0);
#define pt_wait(_pt, _condition) do { \
while (!(_condition)) \
pt_yield(_pt); \
} while (0);
#ifdef NEVER
/*
* This version is superior as it allows calling functions and keeping
* a context, but I never actually had a /need/ for this, yet
*/
struct pt_t {
unsigned int sp;
void * st[32];
void * ctx[32];
} pt_t;
#define pt_start(_pt) do { \
if ((_pt)->st[(_pt)->sp]) goto *((_pt)->st[(_pt)->sp]); \
} while (0);
#define pt_end(_pt) do { (_pt)->st[(_pt)->sp] = NULL; return; } while(0);
#define pt_yield(_pt) do { \
(_pt)->st[(_pt)->sp] = &&_CONCAT(_label, __LINE__);\
return;\
_CONCAT(_label, __LINE__): ; \
} while (0);
#define pt_wait(_pt, _condition) do { \
while (!(_condition)) \
pt_yield(_pt); \
} while (0);
#define pt_call(_pt, _func) do { \
(_pt)->sp++; \
(_pt)->st[(_pt)->sp] = NULL; \
do { \
_func(_pt); \
} while ((_pt)->st[(_pt)->sp]); \
(_pt)->sp--; \
} while (0);
#define pt_ctx(_pt) ((_pt)->ctx[(_pt)->sp])
void my_minit_func(struct pt_t * p) {
pt_start(p);
pt_ctx(p) = calloc(1, sizeof(int));
printf("%s start %p\n", __func__, pt_ctx(p));
pt_yield(p);
int * ctx = pt_ctx(p);
printf("%s loop %p\n", __func__, pt_ctx(p));
for (; *ctx < 10; ctx[0]++) {
printf(" loop %d\n", *ctx);
pt_yield(p);
ctx = pt_ctx(p);
}
printf("%s done %p\n", __func__, pt_ctx(p));
free(pt_ctx(p));
pt_ctx(p) = NULL;
pt_end(p);
}
void my_minit(struct pt_t * p) {
pt_start(p);
printf("%s start\n", __func__);
pt_call(p, my_minit_func);
printf("%s done\n", __func__);
pt_end(p);
}
int main() {
struct pt_t pt = {};
pt_call(&pt, my_minit);
}
/*
tcc -run pt_call_test.c
my_minit start
my_minit_func start 0x555a68d970b0
my_minit_func loop 0x555a68d970b0
loop 0
loop 1
loop 2
loop 3
loop 4
loop 5
loop 6
loop 7
loop 8
loop 9
my_minit_func done 0x555a68d970b0
my_minit done
*/
#endif // NEVER
#endif /* MPTOOLS_INCLUDE_MINIPT_H_ */

113
libmish/src/mish.h Normal file
View File

@ -0,0 +1,113 @@
/*
* mish.h
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef LIBMISH_SRC_MISH_H_
#define LIBMISH_SRC_MISH_H_
#ifndef _LIBMISH_HAS_CMD_HANDLER_
/*
* If you register with a non NULL parameter, you get that, otherwise
* you get an (opaque) mish_client_p
*/
typedef void (*mish_cmd_handler_p)(
void * param,
int argc,
const char *argv[]);
#endif
enum {
MISH_CAP_NO_STDERR = (1 << 0),
MISH_CAP_NO_TELNET = (1 << 2),
MISH_CAP_FORCE_PTY = (1 << 1),
};
/*!
* Starts the mish thread, recover the stdout/err discriptor, and starts
* displaying a prompt for your process.
*/
struct mish_t *
mish_prepare(
unsigned long caps );
//! Returns the flags you passed to mish_prepare()
unsigned long
mish_get_flags(
struct mish_t * m);
/*!
* This is normally called automatically at exit() time
*/
void
mish_terminate(
struct mish_t * m);
/*
* Register a command; please use the macros, don't call this directly.
*
* Symbol is weak, so you can have mish commands in a dynamic library,
* without having to drag libmish into programs that use it; the commands
* won't register, but if you then link with a program that uses libmish,
* they will.
*/
void
mish_register_cmd(
const char ** cmd_names,
const char ** cmd_help,
mish_cmd_handler_p cmd_handler,
void * handler_param,
int safe) __attribute__((weak));
/*!
* Poll the mish threads for pending commands, and call their handlers.
* This is only necessary for 'safe' commands that needs to be run on the
* main thread
*/
int
mish_cmd_poll();
/*
* This is how to add a command to your program:
*
* #include <mish.h>
*
* static void _my_command(void * yours, int argc, const char * argv[] ) {
* // do something here, like in main()
* }
*
* MISH_CMD_NAMES(_my_cmd, "cmd", "cmd_alias");
* MISH_CMD_HELP(_my_cmd, "Short help one liner", "Extra help for help <cmd>")
* MISH_CMD_REGISTER(_my_cmd, _my_command);
*
* That's it!
*/
#define MISH_CMD_NAMES(_n, args...) \
static const char * _cmd_##_n[] = { args, 0 }
#define MISH_CMD_HELP(_n, args...) \
static const char * _help_##_n[] = { args, 0 }
/* This gymnastic is required becase GCC has decided that it'll ignore
* ((weak)) symbols can be NULL, and throw me an error instead */
static inline int _gcc_warning_false_pos_workaround(void * func) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Waddress"
return func != NULL;
#pragma GCC diagnostic pop
}
//! These are called on libmish thread, immediately.
#define MISH_CMD_REGISTER(_d, _handler) \
__attribute__((constructor,used)) \
static void _mish_register_##_d() { \
if (_gcc_warning_false_pos_workaround(mish_register_cmd)) \
mish_register_cmd(_cmd_##_d,_help_##_d,_handler,0,0);\
}
//! These are called when the main program calls mish_cmd_poll()
#define MISH_CMD_REGISTER_SAFE(_d, _handler) \
__attribute__((constructor,used)) \
static void _mish_register_##_d() { \
if (_gcc_warning_false_pos_workaround(mish_register_cmd)) \
mish_register_cmd(_cmd_##_d,_help_##_d,_handler,0,1);\
}
#endif /* LIBMISH_SRC_MISH_H_ */

View File

@ -0,0 +1,86 @@
/*
* mish_capture_select.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "mish_priv.h"
#include "mish.h"
/*
* This is a select() based capture thread, it's not /ideal/ in terms of
* performances, but it's portable and should work on OSX/BSD Linux etc.
*/
void *
_mish_capture_select(
void *param)
{
mish_p m = param;
while (!(m->flags & MISH_QUIT)) {
mish_client_p c;
/*
* Call each client state machine, handle new output,
* new input, draw prompts etc etc.
* Also allow clients to tweak their 'output request' flag here
*/
TAILQ_FOREACH(c, &m->clients, self)
c->cr.process(m, c);
fd_set r = m->select.read;
fd_set w = m->select.write;
struct timeval tv = { .tv_sec = 1 };
int max = select(m->select.max, &r, &w, NULL, &tv);
if (max == 0)
continue;
if (max == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
// real error here?
continue;
}
/* check the telnet listen socket */
mish_telnet_in_check(m, &r);
/*
* Get any input from the original terminals, it has been split
* into lines already and queue all into the main backlog.
*/
for (int i = 0; i < 1 + !(m->flags & MISH_CAP_NO_STDERR); i++) {
_mish_input_read(m, &r, &m->origin[i]);
mish_line_p l;
while ((l = TAILQ_FIRST(&m->origin[i].backlog)) != NULL) {
l->err = i == 1; // mark stderr as such
TAILQ_REMOVE(&m->origin[i].backlog, l, self);
TAILQ_INSERT_TAIL(&m->backlog.log, l, self);
m->backlog.size++;
m->backlog.alloc += sizeof(*l) + l->size;
}
}
mish_client_p safe;
// check if any client has input, or was closed down, and remove them
TAILQ_FOREACH_SAFE(c, &m->clients, self, safe) {
_mish_input_read(m, &r, &c->input);
if (c->input.fd == -1 || (c->flags & MISH_CLIENT_DELETE))
mish_client_delete(m, c);
}
}
if ((m->flags & MISH_CONSOLE_TTY) &&
tcsetattr(0, TCSAFLUSH, &m->orig_termios))
perror("thread tcsetattr");
/*
* Try to be nice and tell all clients to cleanup
*/
mish_client_p c;
while ((c = TAILQ_FIRST(&m->clients)) != NULL)
mish_client_delete(m, c);
m->flags &= ~MISH_QUIT;
m->capture = 0; // mark the thread done
exit(0); // this calls mish_terminate, on main thread
// return NULL;
}

384
libmish/src/mish_client.c Normal file
View File

@ -0,0 +1,384 @@
/*
* mish_client.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include "mish_priv.h"
#include "mish.h"
#include "minipt.h"
mish_client_p
mish_client_new(
mish_p m,
int in,
int out,
int is_tty)
{
mish_client_p c = (mish_client_p)calloc(1, sizeof(*c));
_mish_input_init(m, &c->input, in);
if (is_tty) {
c->input.process_char = _mish_client_vt_parse_input;
c->cr.process = _mish_client_interractive_cr;
} else {
c->cr.process = _mish_client_dumb_cr;
}
c->input.refcon = c;
c->output.fd = out;
c->mish = m; // pointer to parent
c->footer_height = 2;
/*
* Mark the in & out descriptors (could be the same, if a socket)
* as non blocking.
*/
int fds[2] = { in, out};
for (int i = 0; i < 2; i++) {
int fd = fds[i];
if (fd >= m->select.max - 1)
m->select.max = fd +1;
int flags = fcntl(fd, F_GETFL, NULL);
if (flags == 1) {
perror("mish: input F_GETFL");
flags = 0;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
perror("mish: input F_SETFL");
}
TAILQ_INSERT_TAIL(&m->clients, c, self);
return c;
}
void
mish_client_delete(
mish_p m,
mish_client_p c)
{
/*
* Turn back synchronous IO here, I don't want to have to deal with EAGAIN
* when I'm actually trying to be nice and close as quickly as I can
*/
int fds[2] = { c->input.fd, c->output.fd };
for (int i = 0; i < 2; i++) {
if (fds[i] == -1)
continue;
FD_CLR(fds[i], &m->select.read);
FD_CLR(fds[i], &m->select.write);
int flags;
if (fcntl(fds[i], F_GETFL, &flags) == 0) {
flags &= ~O_NONBLOCK;
if (fcntl(fds[i], F_SETFL, &flags))
/* don't care */;
}
}
const char * restore = "\033[4l\033[;r\033[999;1H";
if (write(c->output.fd, restore, strlen(restore)))
;
close(c->output.fd);
TAILQ_REMOVE(&m->clients, c, self);
if (c == m->console)
m->console = NULL;
_mish_input_clear(m, &c->input);
free(c);
}
/*
* this calculates the number of glyphs in the prompt, it's needed
* so we know where to position the cursor when displaying the input line.
* Since prompt can have escape sequences like colors, or UTF8 glyphs
*/
static void
_mish_client_set_prompt(
mish_client_p c,
const char *p)
{
if (p != c->prompt)
strncpy(c->prompt, p, sizeof(c->prompt)-1);
mish_vt_sequence_t sq = {};
char *s = c->prompt;
c->prompt_gc = 0;
while (*s) {
if (_mish_vt_sequence_char(&sq, *s))
if (sq.glyph)
c->prompt_gc++;
s++;
}
}
/*
* Remember, NO LOCALS in here -- this is a coroutine with no stack!
*
* Interactive 'client' prompt handling
*/
void
_mish_client_interractive_cr(
mish_p m,
mish_client_p c)
{
pt_start(c->cr.state);
if (c->input.is_telnet)
mish_telnet_send_init(c);
/*
* move cursor to 999;999 then asks the cursor position. That tells
* us the window size! (handy)
*/
_mish_send_queue(c,
"\033[999;999H\033[6n");
while (_mish_send_flush(m, c))
pt_yield(c->cr.state);
/* remember when we started */
c->output.sqb->stamp = _mish_stamp_ms();
/* Wait for the input handler to get the right sequence */
/* can be either the terminal response OR the telnet sequence */
while (!(c->flags &
(MISH_CLIENT_HAS_CURSOR_POS | MISH_CLIENT_HAS_WINDOW_SIZE)) &&
(_mish_stamp_ms() - c->output.sqb->stamp) < (2 * 1000))
pt_yield(c->cr.state);
if (c->flags & MISH_CLIENT_HAS_CURSOR_POS) {
c->window_size.w = c->cursor_pos.x;
c->window_size.h = c->cursor_pos.y;
c->flags |= MISH_CLIENT_HAS_WINDOW_SIZE;
}
if (!(c->flags & MISH_CLIENT_HAS_WINDOW_SIZE)) {
printf("mish: no window size, falling back to dumb scrollback\n");
c->cr.process = _mish_client_dumb_cr;
goto finish;
}
/* We are live scrolling, and we are at the last line of scrollback */
c->flags |= MISH_CLIENT_INIT_SENT | MISH_CLIENT_SCROLLING;
c->bottom = TAILQ_LAST(&m->backlog.log, mish_line_queue_t);
/*
* This is where we arrive to draw the entire screen; to start up,
* and each time you do a control-l (TODO: or if the window is resized)
* Or use the scrollback (page Up/down etc)
*/
redraw:
c->flags |= MISH_CLIENT_UPDATE_PROMPT;
c->sending = c->bottom;
c->current_vpos = c->window_size.h - c->footer_height;
/*
* walk back from the 'bottom' line we want until we reach the top of the
* screen, or ran out of lines.
*/
while (c->sending && c->current_vpos >= 1) {
mish_line_p p = TAILQ_PREV(c->sending, mish_line_queue_t, self);
if (p) {
c->sending = p;
c->current_vpos--;
} else
break;
}
/*
* Now, we've just connected here, and there might be backlog, and we want
* just enough of that to fill our screen
*/
/* Set the scrolling region to all minus the 2 bottom lines */
_mish_send_queue_fmt(c, "\033D\033[1;%dr",
c->window_size.h - c->footer_height);
_mish_send_queue_fmt(c,
"\033[%d;1H"
"\033[J", // clear to bottom of scrolling area
c->current_vpos);
do {
if (c->flags & MISH_CLIENT_UPDATE_WINDOW) {
c->flags &= ~MISH_CLIENT_UPDATE_WINDOW;
goto redraw;
}
/*
* If there isn't anything to send from the stdout/stderr, do
* prompt editing input handling.
*/
if (c->flags & MISH_CLIENT_UPDATE_PROMPT) {
c->flags &= ~MISH_CLIENT_UPDATE_PROMPT;
sprintf(c->prompt, ">>: ");
_mish_client_set_prompt(c, c->prompt);
_mish_send_queue_fmt(c,
// "_" // draw a fake cursor and end of scrolling text
"\033[%d;1H" /* reposition the cursor to prompt area */
"%s" // print prompt
"\033[J" // Clear to end of screen
"\033[4h", // Set insert mode
c->window_size.h - c->footer_height + 1, c->prompt);
if (c->cmd && c->cmd->len) {
_mish_send_queue_fmt(c, "%s", c->cmd->line);
/* if cursor is inside the line, move it back */
if (c->cmd->len > c->cmd->done)
_mish_send_queue_fmt(c, "\033[%dD",
c->cmd->len - c->cmd->done);
}
}
/*
* Now, flush everything we had from the last iteration (inc prompt)
* from the scatter gather vector.
*/
if (c->output.count) {
while (_mish_send_flush(m, c))
pt_yield(c->cr.state);
} else
pt_yield(c->cr.state);
if (!c->sending) {
/* we're starting up, pool the backlog for a line to display */
if (!c->bottom) {
c->bottom = TAILQ_LAST(&m->backlog.log, mish_line_queue_t);
c->sending = c->bottom;
} else {
// we WERE at the bottom, so find a possible next line
mish_line_p next = TAILQ_NEXT(c->bottom, self);
if (c->flags & MISH_CLIENT_SCROLLING) {
if (next) {
c->bottom = c->sending = next;
}
}
}
}
if (!c->sending) // bah, no new output, loop on
continue;
/* Remember you can't yield() until we're out of the while() loop */
size_t start = c->output.total;
/*
* Now there is something to send, reposition the cursor in the
* scrolling area, and start sending backlog lines.
*/
_mish_send_queue_fmt(c,
"\033[s" /* Save cursor position (from the prompt area) */
"\x8\033[%d;1H" /* pos cursor at bottom of scroll area */
"\033[8l" /* replace mode */,
c->current_vpos);
/*
* Send until there's nothing else OR we sent a screen worth
* this allow a chance to service the input prompt
*/
size_t screen_worth = (c->window_size.h * c->window_size.w) / 1;
do {
if (c->sending->err)
_mish_send_queue(c, MISH_COLOR_RED);
_mish_send_queue_line(c, c->sending);
if (c->sending->err)
_mish_send_queue(c, "\033[m");
// if we reach the bottom mark, stop
c->sending = c->sending == c->bottom ?
NULL : TAILQ_NEXT(c->sending, self);
} while (c->sending &&
(c->output.total - start) <= screen_worth);
// update cursor position here -- SHOULD update it with each lines,
// to handle word wrapping, but for now it's OK
// TODO: Update cursor V pos handling line wrap
if (!c->sending)
c->current_vpos = c->window_size.h - c->footer_height;
/* Restore the cursor to the prompt area */
_mish_send_queue(c, "\033[u");
} while(1);
finish:
pt_end(c->cr.state);
}
/*
* this is the dump pooler; just sends the lines, as is, as they appear.
* Still try to max up the buffer if possible, but that's about it.
*/
void
_mish_client_dumb_cr(
mish_p m,
mish_client_p c)
{
pt_start(c->cr.state);
printf(MISH_COLOR_RED "mish: Started dumb console\n" MISH_COLOR_RESET);
do {
pt_yield(c->cr.state);
if (!c->sending) {
/* we're starting up, pool the backlog for a line to display */
if (!c->bottom) {
c->bottom = TAILQ_LAST(&m->backlog.log, mish_line_queue_t);
c->sending = c->bottom;
} else {
// we WERE at the bottom, so find a possible next line
mish_line_p next = TAILQ_NEXT(c->bottom, self);
if (next) {
c->sending = next;
c->bottom = TAILQ_LAST(&m->backlog.log, mish_line_queue_t);
}
}
}
if (!c->sending) // bah, no new output, loop on
continue;
do {
_mish_send_queue_line(c, c->sending);
// if we reach the bottom mark, stop
c->sending = c->sending == c->bottom ?
NULL : TAILQ_NEXT(c->sending, self);
} while (c->sending);
while (_mish_send_flush(m, c))
pt_yield(c->cr.state);
} while(1);
pt_end(c->cr.state);
}
static void
_mish_cmd_history(
void * param,
int argc,
const char * argv[])
{
mish_client_p c = param;
mish_input_p in = &c->input;
mish_line_p l;
int i = 0;
TAILQ_FOREACH(l, &in->backlog, self) {
printf("%3d %s\n", i+1, l->line);
i++;
}
printf(MISH_COLOR_GREEN
"mish: %d history"
MISH_COLOR_RESET "\n", i);
}
MISH_CMD_NAMES(history, "history");
MISH_CMD_HELP(history,
"Display the history of commands.");
MISH_CMD_REGISTER(history, _mish_cmd_history);
static void
_mish_cmd_disconnect(
void * param,
int argc,
const char * argv[])
{
mish_client_p c = param;
if (c == c->mish->console) {
printf(MISH_COLOR_RED
"mish: can't disconnect console"
MISH_COLOR_RESET "\n");
return;
}
printf(MISH_COLOR_GREEN
"mish: telnet: logout"
MISH_COLOR_RESET "\n");
c->flags |= MISH_CLIENT_DELETE;
}
MISH_CMD_NAMES(disconnect, "dis", "disconnect", "logout");
MISH_CMD_HELP(disconnect,
"Disconnect this telnet session. If appropriate");
MISH_CMD_REGISTER(disconnect, _mish_cmd_disconnect);

View File

@ -0,0 +1,233 @@
/*
* mish_client_input.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mish_priv.h"
#include "mish_priv_cmd.h"
#include "mish.h"
/*
* Don't assume this handles every key combination in the world, it doesn't,
* it handles mostly the one I use most from bash!
* You like VI syntax, saaaaad story :-)
*/
//! Parse current input buffer
int
_mish_client_vt_parse_input(
struct mish_t *m,
struct mish_input_t *in,
uint8_t ich)
{
mish_client_p c = in->refcon;
/*
* if the sequence buffer is currently being flushed, we need to store
* the input until it's done.
* So lets store it into the input buffer, and once that output is finished
* we can process it as before.
*/
if (c->output.sqb->done)
return MISH_IN_STORE;
// now flush what was stored, plus the one we just received
for (int i = 0; i <= in->line->done; i++) {
uint8_t ch = i == in->line->done ? ich : in->line->line[i];
if (in->is_telnet) {
if (_mish_telnet_parse(c, ch))
continue;
}
if (!_mish_vt_sequence_char(&c->vts, ch))
continue;
// if no command is editing, create an empty one, add it to history
if (!c->cmd) {
_mish_line_reserve(&c->cmd, 4);
TAILQ_INSERT_TAIL(&in->backlog, c->cmd, self);
} else {
/*
* we need to detach the element if we're going to resize it,
* otherwise the list is completely fubar as the pointer changes
*/
if (c->cmd->size - c->cmd->len <= 4) {
mish_line_p next = TAILQ_NEXT(c->cmd, self);
TAILQ_REMOVE(&in->backlog, c->cmd, self);
_mish_line_reserve(&c->cmd, 4);
if (next)
TAILQ_INSERT_BEFORE(next, c->cmd, self);
else
TAILQ_INSERT_TAIL(&in->backlog, c->cmd, self);
}
}
switch (c->vts.seq) {
case MISH_VT_SEQ(CSI, '~'): {
mish_line_p cursor = c->bottom;
if (c->vts.p[0] == 1) // GNU screen HOME seq
goto kb_home;
else if (c->vts.p[0] == 4) // GNU screen END seq
goto kb_end;
if (c->vts.p[0] == 5) { // Page UP
for (int i = 0; i < c->window_size.h - 3 && cursor; i++)
cursor = TAILQ_PREV(cursor, mish_line_queue_t, self);
if (cursor) {
c->bottom = cursor;
c->flags |= MISH_CLIENT_UPDATE_WINDOW;
c->flags &= ~MISH_CLIENT_SCROLLING;
}
} else if (c->vts.p[0] == 6) { // down
for (int i = 0; i < c->window_size.h - 3 && cursor; i++)
cursor = TAILQ_NEXT(cursor, self);
c->bottom = cursor;
c->flags |= MISH_CLIENT_UPDATE_WINDOW;
if (!c->bottom)
c->flags |= MISH_CLIENT_SCROLLING;
}
} break;
case MISH_VT_SEQ(CSI, 'H'): { // Home
kb_home:
// don't bother if there's not enough backlog
if (m->backlog.size < c->window_size.h - 2)
break;
c->bottom = TAILQ_FIRST(&m->backlog.log);
for (i = 0; i < c->window_size.h - 2 - 1 && c->bottom; i++)
c->bottom = TAILQ_NEXT(c->bottom, self);
c->flags |= MISH_CLIENT_UPDATE_WINDOW;
if (c->bottom)
c->flags &= ~MISH_CLIENT_SCROLLING;
} break;
case MISH_VT_SEQ(CSI, 'F'): { // END
kb_end:
c->flags |= MISH_CLIENT_UPDATE_WINDOW | MISH_CLIENT_SCROLLING;
c->bottom = NULL;
} break;
case MISH_VT_SEQ(CSI, 'R'):
c->flags |= MISH_CLIENT_HAS_CURSOR_POS;
c->cursor_pos.y = c->vts.p[0];
c->cursor_pos.x = c->vts.p[1];
break;
case MISH_VT_SEQ(RAW, 16): { // CTRL-P Prev history
if (TAILQ_FIRST(&in->backlog) != c->cmd) {
c->flags |= MISH_CLIENT_UPDATE_PROMPT;
c->cmd = TAILQ_PREV(c->cmd, mish_line_queue_t, self);
}
} break;
case MISH_VT_SEQ(RAW, 14): // CTRL-N Next history
if (TAILQ_LAST(&in->backlog, mish_line_queue_t) != c->cmd) {
c->flags |= MISH_CLIENT_UPDATE_PROMPT;
c->cmd = TAILQ_NEXT(c->cmd, self);
}
break;
case MISH_VT_SEQ(RAW, 1): // CTRL-A Start of line
if (c->cmd->done) {
_mish_send_queue_fmt(c, "\033[%dD", c->cmd->done);
c->cmd->done = 0;
}
break;
case MISH_VT_SEQ(RAW, 5): // CTRL-E End of Line
if (c->cmd->done < c->cmd->len) {
_mish_send_queue_fmt(c, "\033[%dC",
c->cmd->len - c->cmd->done);
c->cmd->done = c->cmd->len;
}
break;
case MISH_VT_SEQ(RAW, 2): // CTRL-B Prev char
if (c->cmd->done) {
c->cmd->done--;
_mish_send_queue_fmt(c, "\033[%dD", 1);
}
break;
case MISH_VT_SEQ(RAW, 6): // CTRL-F Next Char
if (c->cmd->done < c->cmd->len) {
c->cmd->done++;
_mish_send_queue_fmt(c, "\033[%dC", 1);
}
break;
case MISH_VT_SEQ(RAW, 23): { // CTRL-W Delete prev word
int old_pos = c->cmd->done;
while (c->cmd->done && c->cmd->line[c->cmd->done-1] == ' ')
c->cmd->done--;
while (c->cmd->done && c->cmd->line[c->cmd->done-1] != ' ')
c->cmd->done--;
if (old_pos - c->cmd->done) {
int del = old_pos - c->cmd->done;
memmove(c->cmd->line + c->cmd->done,
c->cmd->line + old_pos,
c->cmd->len - old_pos + 1);
c->cmd->len -= del;
// move back del characters, and delete them
_mish_send_queue_fmt(c, "\033[%dD\033[%dP", del, del);
}
} break;
case MISH_VT_SEQ(RAW, 0x7f): // DEL
case MISH_VT_SEQ(RAW, 8): // CTRL-H
if (c->cmd->done) {
c->cmd->done--;
memmove(c->cmd->line + c->cmd->done,
c->cmd->line + c->cmd->done + 1,
c->cmd->len - c->cmd->done + 1);
c->cmd->len--;
// backspace plus Delete (1) Character
_mish_send_queue(c, "\x8\033[P");
}
break;
case MISH_VT_SEQ(RAW, 11): // CTRL-K Kill rest of line
c->cmd->len = c->cmd->done;
c->cmd->line[c->cmd->len] = 0;
_mish_send_queue(c, "\033[K");
break;
case MISH_VT_SEQ(RAW, 12): // CTRL-L Redraw
c->flags |= MISH_CLIENT_UPDATE_WINDOW;
break;
case MISH_VT_SEQ(RAW, 13): // CTRL-M aka return
c->cmd->line[c->cmd->len] = 0;
if (0) { // debug
fprintf(stdout, "CMD: '%.*s", c->cmd->done, c->cmd->line);
fprintf(stdout, "\033[7m%.*s\033[0m", 1,
c->cmd->line + c->cmd->done);
if (c->cmd->done < c->cmd->len)
fprintf(stdout, "%s", c->cmd->line + c->cmd->done+1);
fprintf(stdout, "'\n");
}
mish_cmd_call(c->cmd->line, c);
c->cmd = NULL; // new one
{ // reuse the last empty one
mish_line_p last = TAILQ_LAST(&in->backlog, mish_line_queue_t);
if (last && last->len == 0)
c->cmd = last;
}
c->flags |= MISH_CLIENT_UPDATE_PROMPT;
break;
default:
if (c->vts.seq & ~0xff) {
printf(MISH_COLOR_RED
"mish: Unknown sequence: %08x ", c->vts.seq);
for (int i = 0; i < c->vts.pc; i++)
printf(":%d", c->vts.p[i]);
printf("'%c%c'", c->vts.seq >> 8, c->vts.seq & 0xff);
printf(MISH_COLOR_RESET "\n");
}
break;
}
if (c->vts.glyph && c->vts.glyph >= ' ' && c->vts.glyph < 0x7f) {
memmove(c->cmd->line + c->cmd->done + 1,
c->cmd->line + c->cmd->done,
c->cmd->len - c->cmd->done);
c->cmd->line[c->cmd->done] = c->vts.glyph;
c->cmd->done++;
c->cmd->len++;
c->cmd->line[c->cmd->len] = 0;
// no need to explicitly insert, terminal should already be setup
_mish_send_queue_fmt(c, "%lc", c->vts.glyph);
}
}
// remove any buffered input.
in->line->len = 0;
return MISH_IN_SKIP;
}

307
libmish/src/mish_cmd.c Normal file
View File

@ -0,0 +1,307 @@
/*
* mish_cmd.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mish_priv_cmd.h"
#include "mish_priv.h"
#include "mish.h"
#include "fifo_declare.h"
#ifndef offsetof
#define offsetof(type, member) __builtin_offsetof (type, member)
#endif
#ifndef container_of
#define container_of(ptr, type, member) ({ \
const char *__mptr = (const char *)(ptr); \
(type *)(__mptr - offsetof(type, member) );})
#endif
typedef struct mish_cmd_t {
TAILQ_ENTRY(mish_cmd_t) self;
mish_cmd_handler_p cmd_cb;
void * param_cb;
uint32_t safe : 1;
const char ** names; // list of aliases for the command
const char ** help;
} mish_cmd_t, *mish_cmd_p;
typedef struct mish_cmd_call_t {
mish_cmd_p cmd;
char ** argv;
int argc;
} mish_cmd_call_t;
DECLARE_FIFO(mish_cmd_call_t, mish_call_queue, 4);
DEFINE_FIFO(mish_cmd_call_t, mish_call_queue);
static TAILQ_HEAD(,mish_cmd_t) _cmd_list = TAILQ_HEAD_INITIALIZER(_cmd_list);
static mish_call_queue_t _cmd_fifo = {0};
void __attribute__((weak))
mish_register_cmd(
const char ** cmd_names,
const char ** cmd_help,
mish_cmd_handler_p cmd_handler,
void * handler_param,
int safe)
{
if (!cmd_names || !cmd_help || !cmd_handler) {
fprintf(stderr, "%s invalid parameters\n", __func__);
return;
}
mish_cmd_p cmd = calloc(1, sizeof(*cmd));
cmd->names = cmd_names;
cmd->help = cmd_help;
cmd->cmd_cb = cmd_handler;
cmd->param_cb = handler_param;
cmd->safe = safe;
// keep the list roughtly sorted
mish_cmd_p c, s;
TAILQ_FOREACH_SAFE(c, &_cmd_list, self, s) {
if (strcmp(cmd_names[0], c->names[0]) < 0) {
TAILQ_INSERT_BEFORE(c, cmd, self);
return;
}
}
TAILQ_INSERT_TAIL(&_cmd_list, cmd, self);
}
/*
* Returns the length of the first word of cmd_line
*/
static int
first_word_length(
const char * cmd_line)
{
const char *d = cmd_line;
while (*d && *d != ' ')
d++;
return d - cmd_line;
}
mish_cmd_p
mish_cmd_lookup(
const char * cmd_line)
{
if (!cmd_line)
return NULL;
int l = first_word_length(cmd_line);
mish_cmd_p cmd;
TAILQ_FOREACH(cmd, &_cmd_list, self) {
for (int i = 0; cmd->names && cmd->names[i]; i++)
if (!strncmp(cmd->names[i], cmd_line, l))
return cmd;
}
return NULL;
}
typedef struct _mish_argv_t {
char * line;
int ac;
char * av[0];
} _mish_argv_t;
/*
* Duplicate 'line', split it into words, store word pointers in an array,
* NULL terminate it. Also return the number of words in the array in argc.
*
* The returned value is made of two malloc()ed blocks. use mish_argv_free
* to free the memory.
* It's OK to change any of the pointers. But no not try to realloc() the
* vector as it hides a structure
*/
static char **
mish_argv_make(
const char * line,
int * argc )
{
const char separator = ' ';
_mish_argv_t * r = calloc(1, sizeof(*r));
r->line = strdup(line);
char *dup = r->line;
char quote;
enum { s_newarg = 0, s_startarg, s_copyquote, s_skip, s_copy };
int state = s_newarg;
do {
switch (state) {
case s_newarg:
r = realloc(r, sizeof(*r) + ((r->ac + 2) * sizeof(char*)));
while (*dup == ' ' || *dup == separator)
dup++;
r->av[r->ac++] = dup;
state = s_startarg;
break;
case s_startarg:
if (*dup == '"' || *dup == '\'') {
quote = *dup++;
state = s_copyquote;
} else
state = s_copy;
break;
case s_copyquote:
if (*dup == '\\')
state = s_skip;
else if (*dup == quote) {
state = s_newarg;
dup++;
if (*dup) *dup++ = 0;
} else if (*dup)
dup++;
break;
case s_skip:
dup++;
state = s_copyquote;
break;
case s_copy:
if (*dup == 0)
break;
if (*dup != separator)
dup++;
else {
state = s_newarg;
if (*dup) *dup++ = 0;
}
break;
}
} while (*dup);
r->av[r->ac] = NULL;
if (argc)
*argc = r->ac;
return r->av;
}
/*
* Free memory allocated by a mish_argv_make
*/
void
mish_argv_free(
char **_av)
{
if (!_av)
return;
_mish_argv_t * r = container_of(_av, _mish_argv_t, av);
free((void*)r->line);
free((void*)r);
}
int
mish_cmd_call(
const char * cmd_line,
void * c)
{
if (!cmd_line || !*cmd_line)
return -1;
mish_cmd_p cmd = mish_cmd_lookup(cmd_line);
if (!cmd) {
int l = first_word_length(cmd_line);
printf(MISH_COLOR_RED
"mish: '%.*s' not found. type 'help'."
MISH_COLOR_RESET "\n",
l, cmd_line);
return -1;
}
int ac = 0;
char ** av = mish_argv_make(cmd_line, &ac);
if (cmd->safe) {
if (!mish_call_queue_isfull(&_cmd_fifo)) {
mish_cmd_call_t fe = {
.cmd = cmd,
.argv = av,
.argc = ac,
};
mish_call_queue_write(&_cmd_fifo, fe);
} else {
fprintf(stderr,
"mish: cmd FIFO full, make sure to call mish_cmd_poll()!\n");
}
} else {
cmd->cmd_cb(cmd->param_cb ? cmd->param_cb : c, ac, (const char**)av);
mish_argv_free(av);
}
return 0;
}
int
mish_cmd_poll()
{
int res = 0;
while (!mish_call_queue_isempty(&_cmd_fifo)) {
mish_cmd_call_t c = mish_call_queue_read(&_cmd_fifo);
c.cmd->cmd_cb(
c.cmd->param_cb, c.argc, (const char**)c.argv);
mish_argv_free(c.argv);
res++;
}
return res;
}
static const char *_help[] = {
"A few of the typical EMACS keys work for editing commands.",
"like, ^A-^E, ^W, ^K - ^P,^N to navigate history and ^L to",
"redraw.",
"BEG/PGUP/DOWN/END to change the view of the backlog buffer.",
0,
};
static void
_mish_cmd_help(
void * param,
int argc,
const char * argv[])
{
mish_cmd_p cmd;
if (argc < 2) {
printf(MISH_COLOR_GREEN "mish: Key binding\n");
for (int i = 0; _help[i]; i++)
printf(" %s\n", _help[i]);
printf(MISH_COLOR_GREEN "List of commands\n");
TAILQ_FOREACH(cmd, &_cmd_list, self) {
printf(" ");
for (int i = 0; cmd->names && cmd->names[i]; i++)
printf("%s%s", i > 0 ? "," : "", cmd->names[i]);
printf(" - %s\n", cmd->help[0]);
}
printf(MISH_COLOR_RESET);
} else {
for (int i = 1; i < argc; i++) {
mish_cmd_p cmd = mish_cmd_lookup(argv[i]);
if (!cmd) {
printf(MISH_COLOR_RED
"mish: Unknown command '%s'"
MISH_COLOR_RESET "\n", argv[i]);
continue;
}
printf(MISH_COLOR_GREEN);
for (int i = 0; cmd->names && cmd->names[i]; i++)
printf("%s%s", i > 0 ? "," : "", cmd->names[i]);
printf("\n");
for (int i = 0; cmd->help && cmd->help[i]; i++)
printf(" %s\n", cmd->help[i]);
printf(MISH_COLOR_RESET);
}
}
}
MISH_CMD_NAMES(help, "help");
MISH_CMD_HELP(help,
"[cmd...] - Display command list, or help for commands",
"(optional) [cmd...] will display all the help for [cmd]");
MISH_CMD_REGISTER(help, _mish_cmd_help);

View File

@ -0,0 +1,72 @@
/*
* mish_cmd_env.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "mish.h"
extern char ** environ;
static void
_mish_cmd_env(
void * param,
int argc,
const char * argv[])
{
if (argc < 2) {
for (int i = 0; environ[i]; i++)
if (strncmp(environ[i], "LS_COLORS=", 10))
printf("%s\n", environ[i]);
return;
}
for (int i = 0; environ[i]; i++)
for (int ei = 1; ei < argc; ei++) {
int l = strlen(argv[ei]);
if (!strncmp(environ[i], argv[ei], l))
printf("%s\n", environ[i]);
}
}
MISH_CMD_NAMES(env, "env");
MISH_CMD_HELP(env,
"[names ...] display all environment, or variables",
"Apart from LS_COLORS: that is just spam.",
"If you specify names it'll just show the ones whose name",
"start with that prefix");
MISH_CMD_REGISTER(env, _mish_cmd_env);
static void
_mish_cmd_setenv(
void * param,
int argc,
const char * argv[])
{
for (int ei = 1; ei < argc; ei++) {
char *equal = strchr(argv[ei], '=');
if (!equal) {
printf("mish: setenv: '%s' requires an '='\n", argv[ei]);
return;
}
*equal++ = 0;
printf("mish: %s%s%s%s\n", *equal ? "" : "unset ",
argv[ei], *equal ? " = ": "",
*equal ? equal : "");
if (!*equal)
unsetenv(argv[ei]);
else
setenv(argv[ei], equal, 1);
}
}
MISH_CMD_NAMES(setenv, "setenv");
MISH_CMD_HELP(setenv,
"[<name>=<value>...] set/clear environment variable(s)",
"Set <name> to <value> .. if <value> is omitted, clears it.",
"The '=' is required, even when clearing.");
MISH_CMD_REGISTER(setenv, _mish_cmd_setenv);

139
libmish/src/mish_input.c Normal file
View File

@ -0,0 +1,139 @@
/*
* mish_input.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "mish_priv.h"
#include "mish_priv_line.h"
#ifdef MISH_INPUT_TEST
#define D(_w) _w
#else
#define D(_w)
#endif
void
_mish_input_init(
mish_p m,
mish_input_p in,
int fd)
{
TAILQ_INIT(&in->backlog);
// fprintf(stderr, "%s %d\n", __func__, fd);
in->fd = fd;
in->line = NULL;
FD_SET(in->fd, &m->select.read);
if (in->fd >= m->select.max - 1)
m->select.max = in->fd +1;
int flags = fcntl(fd, F_GETFL, NULL);
if (flags == 1) {
perror("mish: input F_GETFL");
flags = 0;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
perror("mish: input F_SETFL");
}
void
_mish_input_clear(
mish_p m,
mish_input_p in)
{
mish_line_p l;
while ((l = TAILQ_FIRST(&in->backlog)) != NULL) {
TAILQ_REMOVE(&in->backlog, l, self);
free(l);
}
if (in->line)
free(in->line);
in->line = NULL;
if (in->fd != -1) {
FD_CLR(in->fd, &m->select.read);
FD_CLR(in->fd, &m->select.write);
close(in->fd);
in->fd = -1;
}
}
int
_mish_input_read(
mish_p m,
fd_set * fds,
mish_input_p in)
{
if (!FD_ISSET(in->fd, fds))
return 0;
do {
if (_mish_line_reserve(&in->line, 80)) {
D(printf(" reserve bailed us\n");)
break;
}
ssize_t rd = read(in->fd,
in->line->line + in->line->len,
in->line->size - in->line->len - 1);
if (rd == -1 && (errno == EWOULDBLOCK || errno == EAGAIN))
break;
if (rd <= 0) {
close(in->fd);
FD_CLR(in->fd, &m->select.read);
FD_CLR(in->fd, &m->select.write);
in->fd = -1;
printf(MISH_COLOR_RED "mish: telnet: disconnected"
MISH_COLOR_RESET "\n");
return -1;
}
in->line->len += rd;
} while (1);
uint8_t * s = (uint8_t*) in->line->line + in->line->done;
uint8_t * d = s;
int added = in->line->len - in->line->done;
D(printf(" buffer added %d done %d len %d size %d\n", added,
(int)in->line->done, (int)in->line->len, (int)in->line->size);
if (in->line->done)
printf(" buffer: '%.*s'\n", in->line->done, in->line->line);)
/*
* This loop re-parse the data, passes it to the optional handler,
* and then store processed lines into the backlog. This is not super
* optimal in the case there isn't a process_char() callback, so perhaps
* I should make another less 'generic' input parser that doesn't copy
* stuff around (in place, often).
*/
while (added) {
int r = MISH_IN_STORE;
if (in->process_char)
r = in->process_char(m, in, *s);
else
r = *s == '\n' ? MISH_IN_SPLIT : MISH_IN_STORE;
if (r == MISH_IN_STORE || r == MISH_IN_SPLIT) {
*d++ = *s;
in->line->done++;
}
if (r == MISH_IN_SPLIT) {
D(printf(" split size %d remains %d : '%.*s'\n", in->line->done,
(int)added, in->line->done-1, in->line->line);)
_mish_line_add(&in->backlog, in->line->line, in->line->done);
d = (uint8_t*)in->line->line;
in->line->len = in->line->done = 0;
}
s++; added--;
}
*d = 0; // NUL terminate for debug purpose!
D(printf(" exit added %d done %d len %d size %d\n", added,
(int)in->line->done, (int)in->line->len, (int)in->line->size);
in->line->len = in->line->done;
if (in->line->done)
printf(" buffer: '%s'\n", in->line->line);)
return TAILQ_FIRST(&in->backlog) != NULL;
}

59
libmish/src/mish_line.c Normal file
View File

@ -0,0 +1,59 @@
/*
* mish_line.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include "mish_priv.h"
#include "mish_priv_line.h"
static const mish_line_t zero = {};
mish_line_p
_mish_line_add(
mish_line_queue_t * q,
char * buffer,
size_t length )
{
size_t l = length + 1;
mish_line_p nl = (mish_line_p)malloc(sizeof(*nl) + l);
*nl = zero;
nl->size = l;
nl->len = length;
memcpy(nl->line, buffer, length);
nl->line[l] = 0;
nl->stamp = _mish_stamp_ms();
TAILQ_INSERT_TAIL(q, nl, self);
return nl;
}
int
_mish_line_reserve(
mish_line_p *line,
uint32_t count)
{
if (!line)
return -1;
mish_line_p l = *line;
if (count < 40)
count = 40;
if (!l) {
l = (mish_line_p)calloc(1, sizeof(mish_line_t) + count);
*l = zero;
l->size = count;
*line = l;
}
if (l->size + count >= MISH_MAX_LINE_SIZE)
return 1;
if (l->size - l->len < count) {
l = (mish_line_p)realloc(l,
sizeof(mish_line_t) + l->size + count);
l->size += count;
*line = l;
}
return 0;
}

273
libmish/src/mish_priv.h Normal file
View File

@ -0,0 +1,273 @@
/*
* mish_priv.h
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef LIBMISH_SRC_MISH_PRIV_H_
#define LIBMISH_SRC_MISH_PRIV_H_
#include <sys/select.h>
#include <stdint.h>
#include <pthread.h>
#include <termios.h>
#include "bsd_queue.h"
#include "mish_priv_vt.h"
#include "mish_priv_line.h"
struct mish_t;
/*
* The process_char callback of mish_input_t returns one of these.
*/
enum {
MISH_IN_SKIP = 0, // skip current character (don't store)
MISH_IN_STORE, // store current character in input buffer
MISH_IN_SPLIT, // store current line, add it to backlog
};
/*
* This receives stuff from a file descriptor, 'processes' it, and split it
* into lines to eventually store new lines into the 'backlog'.
*/
typedef struct mish_input_t {
// lines read from fd are queued in here when \n has been received
mish_line_queue_t backlog;
uint32_t is_telnet : 1, flush_on_nl : 1;
// return 1 for the character to be stored, 0 for it to be skipped
int (*process_char)(
struct mish_t *m,
struct mish_input_t *i,
uint8_t ch);
void * refcon; // reference constant, for the callbacks
int fd;
mish_line_p line;
} mish_input_t, *mish_input_p;
/* various internal states for the client */
enum {
MISH_CLIENT_INIT_SENT = (1 << 0),
MISH_CLIENT_HAS_WINDOW_SIZE = (1 << 1),
MISH_CLIENT_HAS_CURSOR_POS = (1 << 2),
MISH_CLIENT_UPDATE_PROMPT = (1 << 3),
MISH_CLIENT_UPDATE_WINDOW = (1 << 4),
MISH_CLIENT_SCROLLING = (1 << 5),
MISH_CLIENT_DELETE = (1 << 6),
};
typedef struct mish_client_t {
TAILQ_ENTRY(mish_client_t) self;
struct mish_t * mish;
uint32_t flags;
// "coroutine" processing received lines from the stdout/err
struct {
void * state; // state for the prompt coroutine
// _mish_client_interractive_cr() or _mish_client_dumb_cr()
void (*process)(
struct mish_t * m,
struct mish_client_t* c);
} cr;
int footer_height; // # static lines at bottom of screen
int current_vpos;
mish_line_p bottom;
// Line we are currently sending (or NULL)
mish_line_p sending;
/*
* Output sent to the client is made of bits we want to send to move
* the cursor and so on, and, the actual lines we captured from the program.
* So a vector list is built as the output is built, and it's made of bits
* we've copied in "sqb" as well as lines from the mish backlog.
* Once we deem a good time to output stuff, we send that vector and wait
* until it's all gone before starting again.
*/
struct {
int fd; // index of fd we writev to
struct iovec *v; // starts at NULL
int count;// how many are filled in
int size; // how many are allocated
// temporary sequence buffer, for composite output.
mish_line_p sqb;
size_t total; // total bytes we sent
} output;
char prompt[64];
/* Since the prompt will contains more bytes than displayed glyphs,
* we keep track of the number of glyphs so we can reposition the
* cursor accurately. */
int prompt_gc; // prompt real glyph count
/*
* Unfiltered console input from the client. This get stored in there
* until the input code notices any output has been sent. Then it is
* passed down to 'vts'
*/
mish_input_t input;
/*
* this handles the vt sequences received at the command prompt,
* like direction keys etc -also handles UTF8 sequences- (but doesn't do
* anything with them). It is also subverted by the telnet decoder to
* do it's own things, separately.
* Sequences coming out are processed in mish_client_input.
*/
mish_vt_sequence_t vts;
/*
* Stuff received from 'input' side gets filtered via 'vts' and
* stored in this.
* Once a command has been validated, it is added back to the input
* backlog to make this client 'history'
*/
mish_line_p cmd;
struct {
int w, h; } window_size; // valid if MISH_CLIENT_HAS_WINDOW_SIZE
struct {
int x, y; } cursor_pos; // valid if MISH_CLIENT_HAS_CURSOR_POS
} mish_client_t, *mish_client_p;
// supplement the public ones from mish.h
enum {
MISH_QUIT = (1 << 31),
// the process console we started was a tty, so has terminal settings
MISH_CONSOLE_TTY = (1 << 30),
};
typedef struct mish_t {
uint32_t flags;
struct termios orig_termios; // original terminal settings
int originals[2]; // backup the original 1,2 fds
/* These are the ones we use to read the output from the main program. */
mish_input_t origin[2];
uint64_t stamp_start;
TAILQ_HEAD(, mish_client_t) clients;
mish_client_p console; // client that is also the original terminal.
pthread_t capture; // libmish main thread
pthread_t main; // todo: allow pause/stop/resume?
struct {
mish_line_queue_t log;
unsigned int size; // number of lines in backlog
size_t alloc; // number of bytes in the backlog
} backlog;
struct {
int listen; // listen socket
int port; // port we're listening on
} telnet;
// Used by the select thread in mish_capture_select.c
struct {
fd_set read, write;
int max;
} select;
} mish_t, *mish_p;
mish_client_p
mish_client_new(
mish_p m,
int in,
int out,
int is_tty);
void
mish_client_delete(
mish_p m,
mish_client_p c);
/*
* Sequence buffer handling
*/
int
_mish_send_flush(
mish_p m,
mish_client_p c);
void
_mish_send_queue(
mish_client_p c,
const char * b);
void
_mish_send_queue_fmt(
mish_client_p c,
const char *fmt, ...);
void
_mish_send_queue_line(
mish_client_p c,
mish_line_p line );
//! Parse current input buffer fro VT sequences, like keys.
int
_mish_client_vt_parse_input(
struct mish_t *m,
struct mish_input_t *in,
uint8_t ich);
/*
* This is the main interactive client coroutine. This one is interesting.
*/
void
_mish_client_interractive_cr(
mish_p m,
mish_client_p c);
void
_mish_client_dumb_cr(
mish_p m,
mish_client_p c);
uint64_t
_mish_stamp_ms();
/*
* telnet handling
*/
int
mish_telnet_prepare(
mish_p m,
uint16_t port);
int
mish_telnet_in_check(
mish_p m,
fd_set * r);
void
mish_telnet_send_init(
mish_client_p c);
int
_mish_telnet_parse(
mish_client_p c,
uint8_t ch);
/*
* input handling
*/
void
_mish_input_init(
mish_p m,
mish_input_p in,
int fd);
void
_mish_input_clear(
mish_p m,
mish_input_p in);
int
_mish_input_read(
mish_p m,
fd_set * fds,
mish_input_p in);
/*
* Capture thread function
*/
void *
_mish_capture_select(
void *param);
/*
* https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_output_sequences
*/
#define MISH_COLOR_RED "\033[38;5;125m"
#define MISH_COLOR_GREEN "\033[38;5;28m"
#define MISH_COLOR_RESET "\033[0m"
#endif /* LIBMISH_SRC_MISH_PRIV_H_ */

View File

@ -0,0 +1,37 @@
/*
* mish_priv_cmd.h
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef LIBMISH_SRC_MISH_PRIV_CMD_H_
#define LIBMISH_SRC_MISH_PRIV_CMD_H_
/*
* I decided that the command list would't be attached to the mish_t,
*
* In any other API I would, for consistency sake, but here I'm more
* interested in having a convenient way to add commands, regardless of the
* state of mish_t, and have macro that register them before main() is called
* and that sort of things.
*
* In the same vein, I also don't provide a way to remove a command, I don't
* think it's terribly necessary at the minute
*/
#include "bsd_queue.h"
typedef void (*mish_cmd_handler_p)(
void * param,
int argc,
const char *argv[]);
#define _LIBMISH_HAS_CMD_HANDLER_
int
mish_cmd_call(
const char * cmd_line,
void * c);
#endif /* LIBMISH_SRC_MISH_PRIV_CMD_H_ */

View File

@ -0,0 +1,47 @@
/*
* mish_priv_line.h
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef LIBMISH_SRC_MISH_PRIV_LINE_H_
#define LIBMISH_SRC_MISH_PRIV_LINE_H_
#include <stdint.h>
#include "bsd_queue.h"
#define MISH_MAX_LINE_SIZE 0xffff
struct mish_line_t {
TAILQ_ENTRY(mish_line_t) self;
/* The stamp is made with _mish_get_stamp, which returns epoch
* milliseconds, so we don't need the 64 bits for the stamp here,
* lets say 34 bits seconds + 10 bits milliseconds
*/
uint64_t stamp : 44;
uint64_t err: 1, use: 4, draw_stamp: 1,
size : 16, len: 16, // len <= size
done: 16; // done <= len
char line[0];
};
typedef struct mish_line_t mish_line_t, *mish_line_p;
typedef TAILQ_HEAD(mish_line_queue_t, mish_line_t) mish_line_queue_t;
/*
* mish_line tools
*/
int
_mish_line_reserve(
mish_line_p *line,
uint32_t count);
mish_line_p
_mish_line_add(
mish_line_queue_t * q,
char * buffer,
size_t length );
#endif /* LIBMISH_SRC_MISH_PRIV_LINE_H_ */

View File

@ -0,0 +1,48 @@
/*
* mish_priv_vt.h
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef LIBMISH_SRC_MISH_PRIV_VT_H_
#define LIBMISH_SRC_MISH_PRIV_VT_H_
#include <stdint.h>
/*
* Input sequence decoder, deals with all the ESC bits and
* any UTF8 bits that gets encountered
*/
#define MISH_VT_RAW 0
#define MISH_VT_TELNET (0xff)
#define MISH_VT_ESC (0x1b)
#define MISH_VT_CSI ((MISH_VT_ESC << 8) | ('['))
#define MISH_VT_CSIQ ((MISH_VT_CSI << 8) | ('?'))
#define MISH_VT_UTF8 0x80
// allow writing MISH_VT_SEQ(CSI, 'H')
#define MISH_VT_SEQ(_kind, _ch) (((MISH_VT_ ## _kind) << 8) | (_ch))
typedef struct mish_vt_sequence_t {
uint32_t seq; // ESC, CSI, CSI?
int p[8];
union {
struct {
uint32_t pc : 4, // CSI; parameter count
seq_want : 4, // how many more bytes we want (UTF8)
done : 1, // sequence is done
error : 1; // sequence was not valid
};
uint32_t flags;
};
uint32_t glyph; // for RAW and UTF8
} mish_vt_sequence_t, *mish_vt_sequence_p;
int
_mish_vt_sequence_char(
mish_vt_sequence_p s,
uint8_t ch);
#endif /* LIBMISH_SRC_MISH_PRIV_VT_H_ */

200
libmish/src/mish_send.c Normal file
View File

@ -0,0 +1,200 @@
/*
* mish_send.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/uio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include "mish_priv.h"
#include "mish_priv_line.h"
/*
* We arrive here with an array of iovec buffers already prepared by
* _mish_send_queue_*();
* This is called repeatedly, and we continually call writev, gather how
* many bytes have been written, and mark each corresponding buffer as done.
* Once all the iovec buffers have been filled, we're done!
*
* All of this is made to reduce to the max the number of 'write' system
* calls.
*
* There are two kinds of buffers in the vectors:
* 1) the "backlog" lines themselves. These have a pointer, all is well and good.
* 2) sequences we are making up on the fly, like prompt, colors etc; these
* are allocated in a 'sqb' (sequence buffer) which can grow (via realloc)
* as more stuff is added.
* Therefore, we can't pre-populate the vector array with the iov_base
* pointers, we keep these to NULL, and in this function, the first thing
* we do is derivate the pointers from their 'iov_len' in the current 'sqb'.
*
* Oh and we "lock" sqb to prevent any other things to be added.
*/
int
_mish_send_flush(
mish_p m,
mish_client_p c)
{
/* If it's a new sequence, 'lock' it and prepare it's iovec pointers */
if (c->output.sqb) {
if (!c->output.sqb->done) {
/*
* Here we construct the base address of out character buffers.
* They start with a NULL address as the sqb could grow when
* adding sequences.
* So once we are ready to send, we need to compute the
* base addresses of everyone that ought to point into the sqb,
* then we 'lock' it by marking it 'done'.
*/
char * base = c->output.sqb->line;
for (int i = 0; i < c->output.count; i++) {
if (!c->output.v[i].iov_base) {
c->output.v[i].iov_base = base;
base += c->output.v[i].iov_len;
}
}
c->output.sqb->done = 1; // "lock" the sqb until all is sent.
}
}
if (!c->output.count)
return 0;
/* if we don't have 'permission' to write yet, ask for it */
if (!FD_ISSET(c->output.fd, &m->select.write)) {
FD_SET(c->output.fd, &m->select.write);
return 1;
}
/* skip what we've already done */
struct iovec * io = c->output.v;
int ioc = c->output.count;
while (io->iov_len == 0 && ioc) {
io++; ioc--;
}
int res = 1;
if (ioc) {
ssize_t got = writev(c->output.fd, io, ioc);
if (got == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
// we've been close down?
FD_CLR(c->output.fd, &m->select.write);
goto done;
}
}
/* fill up vectors with what was written */
while (got > 0) {
ssize_t b = got > io->iov_len ? io->iov_len : got;
io->iov_len -= b;
io->iov_base += b;
if (io->iov_len == 0) {
io++;
ioc--;
}
got -= b;
}
}
if (ioc == 0) { // done!
done:
res = 0;
c->output.count = 0;
// also flush out temporary buffers, and unlock it
if (c->output.sqb) {
c->output.sqb->done = 0;
c->output.sqb->len = 0;
}
/* if nothing else is ready to send, clear us from the select loop */
if (!c->sending)
FD_CLR(c->output.fd, &m->select.write);
}
return res;
}
/*
* Add up a bit of output to something we want to send as a sequence
*/
static char *
_mish_send_prep(
mish_client_p c,
size_t l)
{
if (c->output.sqb && c->output.sqb->done) {
fprintf(stderr, "%s problem\n", __func__);
abort();
}
c->output.total += l;
/* allocate enough in the character buffer */
_mish_line_reserve(&c->output.sqb, l + 1);
/* allocate enough in the vector buffer */
if (c->output.count == c->output.size) {
c->output.size += 8;
c->output.v = realloc(c->output.v, c->output.size * sizeof(c->output.v[0]));
}
/* this is where the calling function will copy it's data */
char * res = (char*)c->output.sqb->line + c->output.sqb->len;
c->output.sqb->len += l;
/* try to concatenate buffers together, if they are 'ours' */
if (c->output.count && c->output.v[c->output.count-1].iov_base == NULL) {
struct iovec *v = c->output.v + c->output.count - 1;
v->iov_len += l;
} else {
struct iovec *v = c->output.v + c->output.count;
v->iov_base = NULL; // important, see top of the file for details
v->iov_len = l;
v++;
c->output.count++;
}
return res;
}
/*
* Add up a bit of output to something we want to send as a sequence
*/
void
_mish_send_queue(
mish_client_p c,
const char * b)
{
int l = strlen(b);
char *d = _mish_send_prep(c, l);
strcpy(d, b);
}
/*
* Same as above, but with formatting.
*/
void
_mish_send_queue_fmt(
mish_client_p c,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
int l = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
char *d = _mish_send_prep(c, l);
va_start(ap, fmt);
vsnprintf(d, l + 1, fmt, ap);
va_end(ap);
}
void
_mish_send_queue_line(
mish_client_p c,
mish_line_p line )
{
/* allocate enough in the vector buffer */
if (c->output.count == c->output.size) {
c->output.size += 8;
c->output.v = realloc(c->output.v, c->output.size * sizeof(c->output.v[0]));
}
struct iovec *v = c->output.v + c->output.count;
v->iov_base = line->line;
v->iov_len = line->len;
c->output.count++;
c->output.total += line->len;
}

282
libmish/src/mish_session.c Normal file
View File

@ -0,0 +1,282 @@
/*
* mish_session.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for isatty etc
#ifdef __MACH__
#include <mach/clock.h>
#include <mach/mach.h>
#include <util.h> // for openpty
#endif
#ifdef __linux__
#include <pty.h>
#endif
#include "mish_priv.h"
#include "mish.h"
/*
* get a localtime equivalent, with milliseconds added
*/
uint64_t
_mish_stamp_ms()
{
struct timespec tim;
#ifdef __MACH__
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
tim.tv_sec = mts.tv_sec;
tim.tv_nsec = mts.tv_nsec;
#else
clock_gettime(CLOCK_REALTIME, &tim);
#endif
uint64_t res = (tim.tv_sec * 1000) + (tim.tv_nsec / 1000000);
return res;
}
/*
* need to keep this around for atexit()
*/
static mish_p _mish = NULL;
static void
_mish_atexit()
{
if (_mish)
mish_terminate(_mish);
}
mish_p
mish_prepare(
unsigned long caps )
{
/* In production, of if you want to make sure mish is disabled without
* re-linking, set this env variable.
*/
if (getenv("MISH_OFF")) {
if (atoi(getenv("MISH_OFF"))) {
unsetenv("MISH_TELNET_PORT");
printf("mish: Disabled my MISH_OFF\n");
return NULL;
}
}
mish_p m = (mish_p)calloc(1, sizeof(*m));
FD_ZERO(&m->select.read);
FD_ZERO(&m->select.write);
TAILQ_INIT(&m->backlog.log);
TAILQ_INIT(&m->clients);
m->flags = caps;
int tty = 0;
if (getenv("MISH_TTY")) {
tty = atoi(getenv("MISH_TTY"));
} else
tty = (isatty(0) && isatty(1) && isatty(2)) ||
(caps & MISH_CAP_FORCE_PTY);
int io[2]; // stdout pipe
int ie[2]; // stderr pipe
#if !defined(__wasm__)
{
char pty[80];
if (openpty(&io[0], &io[1], pty, NULL, NULL) == -1) {
perror("openpty");
goto error;
}
if (!(caps & MISH_CAP_NO_STDERR)) {
if (openpty(&ie[0], &ie[1], pty, NULL, NULL) == -1) {
perror("openpty");
goto error;
}
}
if (tty)
m->flags |= MISH_CONSOLE_TTY;
if (tcgetattr(0, &m->orig_termios))
perror("tcgetattr");
struct termios raw = m->orig_termios;
raw.c_iflag &= ~(ICRNL | IXON);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); // ISIG
if (tcsetattr(0, TCSAFLUSH, &raw))
perror("tcsetattr");
}
#endif
if (!(caps & MISH_CAP_NO_TELNET)) {
uint16_t port = 0; // suggested telnet port
if (getenv("MISH_TELNET_PORT"))
port = atoi(getenv("MISH_TELNET_PORT"));
if (mish_telnet_prepare(m, port) == 0) {
char port[8];
snprintf(port, sizeof(port), "%d", m->telnet.port);
setenv("MISH_TELNET_PORT", port, 1);
} else
unsetenv("MISH_TELNET_PORT");
}
// backup the existing descriptors, to make a 'client', we replace
// the original 1,2 with out own pipe/pty
m->originals[0] = dup(1);
m->originals[1] = dup(2);
// we do another dup(0,1) here, as that client will close() them
m->console = mish_client_new(m, dup(0), dup(1), tty);
m->stamp_start = _mish_stamp_ms();
_mish_input_init(m, &m->origin[0], io[0]);
if (dup2(io[1], 1) == -1) {
perror("dup2");
goto error;
}
if (!(caps & MISH_CAP_NO_STDERR)) {
_mish_input_init(m, &m->origin[1], ie[0]);
if (dup2(ie[1], 2) == -1) {
perror("dup2");
goto error;
}
}
atexit(_mish_atexit);
// m->main = pthread_self();
// TODO: Make an epoll polling thread for linux
pthread_create(&m->capture, NULL, _mish_capture_select, m);
_mish = m;
return m;
error:
if (io[0]) {
close(io[0]);
close(io[1]);
}
if (ie[0]) {
close(ie[0]);
close(ie[1]);
}
free(m);
return NULL;
}
unsigned long
mish_get_flags(
struct mish_t *m)
{
return m ? m->flags : 0;
}
/*
* This can be called at any time; if the thread is running, tell it to
* close everything and exit... then wait for a little bit for it to
* terminate, then restore the terminal.
*/
void
mish_terminate(
mish_p m)
{
if (!_mish)
return;
// restore normal IOs
dup2(m->originals[0], 1);
dup2(m->originals[1], 2);
// the thread does this, but we also do it in case this function is
// called from another exit(x)
#if !defined(__wasm__)
if ((m->flags & MISH_CONSOLE_TTY) &&
tcsetattr(0, TCSAFLUSH, &m->orig_termios))
perror("mish_terminate tcsetattr");
#endif
close(m->originals[0]); close(m->originals[1]);
if (m->capture) {
m->flags |= MISH_QUIT;
// this will wake the select() call from sleep
if (write(1, "\n", 1))
;
time_t start = time(NULL);
time_t now;
while (((now = time(NULL)) - start < 2) && m->capture)
usleep(1000);
}
printf("\033[4l\033[;r\033[999;1H"); fflush(stdout);
printf("%s done\n", __func__);
free(m);
_mish = NULL;
}
static void
_mish_cmd_quit(
void * param,
int argc,
const char * argv[])
{
printf(MISH_COLOR_RED
"mish: Quitting."
MISH_COLOR_RESET "\n");
mish_p m = ((mish_client_p)param)->mish;
m->flags |= MISH_QUIT;
}
MISH_CMD_NAMES(quit, "q", "quit");
MISH_CMD_HELP(quit,
"exit running program",
"Close all clients and exit(0)");
MISH_CMD_REGISTER(quit, _mish_cmd_quit);
#define VT_COL(_c) "\033[" #_c "G"
static void
_mish_cmd_mish(
void * param,
int argc,
const char * argv[])
{
printf(MISH_COLOR_RED
"mish: mish command."
MISH_COLOR_RESET "\n");
mish_p m = ((mish_client_p)param)->mish;
printf("Backlog: %6d lines (%5dKB)" VT_COL(40) "Telnet Port: %5d\n",
m->backlog.size,
(int)m->backlog.alloc / 1024,
m->telnet.port);
#if 0
printf(" read: ");
for (int i = 0; i < m->select.max; i++)
if (FD_ISSET(i, &m->select.read)) printf("%d ", i);
printf(VT_COL(40) " write: ");
for (int i = 0; i < m->select.max; i++)
if (FD_ISSET(i, &m->select.write)) printf("%d ", i);
printf("\n");
#endif
mish_client_p c;
TAILQ_FOREACH(c, &m->clients, self) {
printf(" Client: r: %d w: %d %s %s\n",
c->input.fd, c->output.fd,
c->input.is_telnet ? "telnet session" :
c == m->console ? "console" : "*unknown*",
c == m->console ?
m->flags & MISH_CONSOLE_TTY ? "(tty)": "(dumb)"
: "");
printf(" max sizes: vector: %d input: %d\n",
c->output.size, c->input.line ? c->input.line->size : 0);
}
}
MISH_CMD_NAMES(mish, "mish");
MISH_CMD_HELP(mish,
"Displays mish status.",
"Show status and a few bits of internals.");
MISH_CMD_REGISTER(mish, _mish_cmd_mish);

241
libmish/src/mish_telnet.c Normal file
View File

@ -0,0 +1,241 @@
/*
* mish_telnet.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include "mish_priv.h"
#ifdef DEBUG_TELNET
#define TELCMDS
#define TELOPTS
#define TV(w) w
#else
#define TV(w)
#endif
#include <arpa/telnet.h>
/*
* Ask remote telnet client to turn off echo, and send us the NAWS
*/
void
mish_telnet_send_init(
mish_client_p c)
{
const unsigned char init[] = {
IAC, DO, TELOPT_ECHO,
IAC, DO, TELOPT_NAWS,
IAC, WILL, TELOPT_ECHO,
IAC, WILL, TELOPT_SGA,
0,
};
_mish_send_queue(c, (char*)init);
}
/*
* This looks for the TELNET IAC prefix, and handles /some/ of them.
* The one we're really interested in anyway is the NAWS aka Window Size
* sequence.
* All the other ones are ignored, but parsed. Ish.
*/
int
_mish_telnet_parse(
mish_client_p c,
uint8_t ch)
{
TV(printf("%02x(%d) %s (seq %06x)\n",
ch, ch, ch >= xEOF ? telcmds[ch-xEOF] : "",
c->vts.seq);)
/*
* To handle telnet sequences, we pause the VT decoder
*/
switch (c->vts.seq) {
case MISH_VT_RAW:
if (ch == IAC) {
c->vts.seq = MISH_VT_TELNET;
return 1;
}
break;
case MISH_VT_TELNET: {
switch (ch) {
case WILL:
case WONT:
case DO:
case DONT:
case SB:
c->vts.seq = c->vts.seq << 8 | ch;
return 1;
default:
case SE:
c->vts.seq = MISH_VT_RAW;
return 1;
}
} break;
case MISH_VT_SEQ(TELNET, IAC):
TV(printf("IAC %d %s\n\n", ch, telopts[ch]);)
c->vts.seq = MISH_VT_RAW;
break; // let this one thru!!!
case MISH_VT_SEQ(TELNET, WILL):
TV(printf("WILL %d %s\n\n", ch, telopts[ch]);)
c->vts.seq = MISH_VT_RAW;
return 1;
case MISH_VT_SEQ(TELNET, WONT):
TV(printf("WONT %d %s\n", ch, telopts[ch]);)
c->vts.seq = MISH_VT_RAW;
return 1;
case MISH_VT_SEQ(TELNET, DO):
TV(printf("DO %d %s\n", ch, telopts[ch]);)
c->vts.seq = MISH_VT_RAW;
return 1;
case MISH_VT_SEQ(TELNET, DONT):
TV(printf("DONT %d %s\n", ch, telopts[ch]);)
c->vts.seq = MISH_VT_RAW;
return 1;
case MISH_VT_SEQ(TELNET, SB):
TV(printf("SB %d %s\n", ch, telopts[ch]);)
c->vts.seq_want = 0;
c->vts.p[0] = 0;
switch (ch) {
case TELOPT_NAWS:
c->vts.seq = c->vts.seq << 8 | ch;
return 1;
default:
if (ch == IAC) {
c->vts.seq = MISH_VT_TELNET;
return 1;
}
}
return 1;
case (MISH_VT_SEQ(TELNET, SB) << 8 | (TELOPT_NAWS)): {
switch (c->vts.seq_want++) {
case 0:
c->vts.p[0] = 0;
c->vts.p[1] = ch;
return 1;
case 1: c->vts.p[1] = (c->vts.p[1] << 8) | ch; return 1;
case 2: c->vts.p[0] = ch; return 1;
case 3: c->vts.p[0] = (c->vts.p[0] << 8) | ch; /* fallthru */
}
if (c->vts.seq_want == 4) {
TV(printf("GOT NAWS: %dx%d\n", c->vts.p[0], c->vts.p[1]);)
c->window_size.h = c->vts.p[0];
c->window_size.w = c->vts.p[1];
c->flags |= MISH_CLIENT_HAS_WINDOW_SIZE |
MISH_CLIENT_UPDATE_WINDOW;
c->vts.seq = MISH_VT_RAW;
c->vts.done = 1;
return 1;
}
} return 1;
}
return 0;
}
extern const char *__progname;
/*
* not going to go thru all the bits regarding sockets here, you know it.
*
* We allocate a random port, and listen to it, then add it to the
* main select() thread.
*/
int
mish_telnet_prepare(
mish_p m,
uint16_t port)
{
m->telnet.listen = socket(AF_INET, SOCK_STREAM, 0);
int flag = 1;
signal(SIGPIPE, SIG_IGN);
if (setsockopt(m->telnet.listen, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)))
perror("SO_REUSEADDR");
struct sockaddr_in b = {
.sin_family = AF_INET,
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
};
// attempts to generate a pseudo random port based on the executable name
// before having to rely on random(). This gives a chance of 'unique' ports
// per programs.
if (port == 0) {
for (int i = 0; __progname[i]; i++)
port += __progname[i] + i;
if (port < 1024)
port += 1024;
port = port & 0x3fff;
}
int tries = 5;
do {
b.sin_port = htons(port);
if (bind(m->telnet.listen, (struct sockaddr *)&b, sizeof(b)) == -1) {
fprintf(stderr, "%s can't bind %d\n", __func__, ntohs(b.sin_port));
port = port + (random() & 0x3ff);
continue;
}
m->telnet.port = ntohs(b.sin_port);
printf(MISH_COLOR_GREEN
"mish: telnet port on %d"
MISH_COLOR_RESET "\n",
m->telnet.port);
break;
} while (tries-- > 0);
if (tries) {
if (listen(m->telnet.listen, 2) == -1) {
perror("mish_telnet_prepare listen");
goto error;
}
FD_SET(m->telnet.listen, &m->select.read);
return 0;
}
fprintf(stderr, "mish: %s failed\n", __func__);
error:
close(m->telnet.listen);
m->telnet.listen = -1;
return -1;
}
/*
* Accept a new connection from the telnet port, add a new client
*/
int
mish_telnet_in_check(
mish_p m,
fd_set * r)
{
if (!FD_ISSET(m->telnet.listen, r))
return 0;
struct sockaddr_in a = {};
socklen_t al = sizeof(a);
int tf = accept(m->telnet.listen, (struct sockaddr *) &a, &al);
if (tf < 0) {
perror(__func__);
return -1;
}
/*
* it is necessary to have TWO file descriptors here, otherwise
* the FD_SET/FD_CLR logic could be confusing as using the same bits.
* a dup() isn't terribly expensive and guarantees we don't have to
* worry about it.
*/
mish_client_p c = mish_client_new(m, tf, dup(tf), 1 /* tty */);
c->input.is_telnet = 1;
printf(MISH_COLOR_GREEN
"mish: telnet: connected."
MISH_COLOR_RESET "\n");
return 0;
}

105
libmish/src/mish_vt.c Normal file
View File

@ -0,0 +1,105 @@
/*
* mish_vt.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "mish_priv_vt.h"
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(_a) (sizeof(_a) / sizeof((_a)[0]))
#endif
/*
* Handle sequences of VT100 and UTF8 chars.
*
* Uses the sequence 's' as temporary buffer, and returns 1 when a complete
* sequence OR a glyph has been decoded.
*/
int
_mish_vt_sequence_char(
mish_vt_sequence_p s,
uint8_t ch)
{
// if previous sequence was done, reset it.
if (s->done)
s->flags = s->seq = s->glyph = s->pc = s->p[0] = 0;
switch (s->seq) {
case MISH_VT_RAW: {
switch (ch) {
case 0x1b:
s->seq = MISH_VT_ESC;
break;
default: {
if (ch & 0x80) { // UTF8 !??!
int mask = 0x40;
s->seq_want = 0;
while ((ch & mask) && s->seq_want < 5) {
s->seq_want++;
mask >>= 1;
}
// clear header bits (ought to be 2; 1 allows encoding of 0xffffff)
s->glyph = ch & (0xff >> (s->seq_want + 1));
s->seq = MISH_VT_UTF8;
} else {
s->seq = (s->seq << 8) | ch;
s->glyph = ch; // technically it's a glyph
s->done = 1;
}
}
}
} break;
case MISH_VT_ESC: {
if (ch == '[')
s->seq = MISH_VT_CSI;
else {
s->seq = (s->seq << 8) | ch;
s->done = 1;
}
} break;
case MISH_VT_CSIQ:
case MISH_VT_CSI: {
switch (ch) {
case '?': {
// note this would still accept somelike like CSI ? 00?; 1
if ((s->seq == MISH_VT_CSIQ) || s->pc) {
s->done = s->error = 1;
} else
s->seq = MISH_VT_CSIQ;
} break;
case '0' ... '9':
s->p[s->pc] = (s->p[s->pc] * 10) + (ch - '0');
break;
case ';': {
if (s->pc < ARRAY_SIZE(s->p)) {
s->pc++;
s->p[s->pc] = 0;
} else {
s->done = s->error = 1;
}
} break;
default:
if (s->p[s->pc])
s->pc++;
s->seq = (s->seq << 8) | ch;
s->done = 1;
break;
}
} break;
case MISH_VT_UTF8: {
s->glyph = (s->glyph << 6) | (ch & 0x3f);
if (s->seq_want) s->seq_want--;
s->done = s->seq_want == 0;
} break;
}
#ifdef MISH_VT_DEBUG
if (s->done)
fprintf(stderr, "in %x(%c) state %x done:%d error:%d pc:%d/%d\n",
ch, ch < ' '?'.':ch,s->seq, s->done, s->error,
s->pc, (int)ARRAY_SIZE(s->p));
#endif
return s->done;
}

View File

@ -0,0 +1,95 @@
// small test unit for mish_argv_make
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
typedef struct _mish_argv_t {
char * line;
int ac;
char * av[0];
} _mish_argv_t;
/*
* Duplicate 'line', split it into words, store word pointers in an array,
* NULL terminate it. Also return the number of words in the array in argc.
*
* The returned value is made of two malloc()ed blocks. use mish_argv_free
* to free the memory.
* It's OK to change any of the pointers. But no not try to realloc() the
* vector as it hides a structure
*/
static char **
mish_argv_make(
const char * line,
int * argc )
{
const char separator = ' ';
_mish_argv_t * r = calloc(1, sizeof(*r));
r->line = strdup(line);
char *dup = r->line;
char quote;
enum { s_newarg = 0, s_startarg, s_copyquote, s_skip, s_copy };
int state = s_newarg;
do {
switch (state) {
case s_newarg:
r = realloc(r, sizeof(*r) + ((r->ac + 2) * sizeof(char*)));
while (*dup == ' ' || *dup == separator)
dup++;
r->av[r->ac++] = dup;
state = s_startarg;
break;
case s_startarg:
if (*dup == '"' || *dup == '\'') {
quote = *dup++;
state = s_copyquote;
} else
state = s_copy;
break;
case s_copyquote:
if (*dup == '\\')
state = s_skip;
else if (*dup == quote) {
state = s_newarg;
dup++;
if (*dup) *dup++ = 0;
} else if (*dup)
dup++;
break;
case s_skip:
dup++;
state = s_copyquote;
break;
case s_copy:
if (*dup == 0)
break;
if (*dup != separator)
dup++;
else {
state = s_newarg;
if (*dup) *dup++ = 0;
}
break;
}
} while (*dup);
r->av[r->ac] = NULL;
if (argc)
*argc = r->ac;
return r->av;
}
int main() {
int argc = 0;
char **argv = mish_argv_make("testing \"one escape two\" lala ", &argc);
printf("argc = %d\n", argc);
for (int i = 0; argv[i]; i++)
printf("%2d:'%s'\n", i, argv[i]);
argv = mish_argv_make("command with some \" quoted\\\"words \" should work\n", &argc);
printf("argc = %d\n", argc);
for (int i = 0; argv[i]; i++)
printf("%2d:'%s'\n", i, argv[i]);
}

View File

@ -0,0 +1,20 @@
/*
* mish_cmd_test.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mish_cmd.c"
int main()
{
int argc;
char ** argv = mish_argv_make(
"command with some \" quoted\\\"words \" should work\n", &argc);
for (int i = 0; i < argc; i++)
printf("%2d: '%s'\n", i, argv[i]);
mish_argv_free(argv);
}

View File

@ -0,0 +1,94 @@
/*
* mish_debug_test.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* NOTE this program is made for debug purpose only, it basically give you
* telnet command access remotely
* IT IS JUST MADE TO DEBUG LIBMISH, DO NOT USE IN PROD!
*/
#define _GNU_SOURCE // for asprintf
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include "mish.h"
static void
sigchld_handler(int s)
{
int status = 0;
pid_t pid = waitpid(-1, &status, WNOHANG);
if (pid > 0) {
if (WIFEXITED(status) && WEXITSTATUS(status))
fprintf(stderr, "pid %d returned %d\n", (int)pid, WEXITSTATUS(status));
if (WIFSIGNALED(status))
fprintf(stderr, "pid %d terminated with %s\n",
(int)pid, strsignal(WTERMSIG(status)));
}
}
int main()
{
signal(SIGCHLD, sigchld_handler);
mish_prepare(0);
while (1) {
sleep(1);
}
}
static const char *
_shell_which(
const char * cmd)
{
if (cmd[0] == '.' || cmd[0] == '/')
return strdup(cmd);
char * path = strdup(getenv("PATH"));
char * cur = path, *e;
while ((e = strsep(&cur, ":")) != NULL) {
char *pe;
asprintf(&pe, "%s/%s", e, cmd);
struct stat st = {};
if (stat(pe, &st) == 0) {
return pe;
}
free(pe);
}
fprintf(stderr, "%s: command not found\n", cmd);
return NULL;
}
static void
_test_shell_cnt(
void * param,
int argc,
const char * argv[])
{
const char * av[argc + 1];
av[0] = _shell_which(argv[1]);
if (av[0] == NULL)
return;
for (int ai = 1; ai <= argc; ai++)
av[ai] = argv[ai+1];
pid_t pid = fork();
if (pid == 0) {
execv(av[0], (void*)av);
perror(av[0]);
close(0);close(1);close(2);
exit(1);
}
}
MISH_CMD_NAMES(shell, "shell");
MISH_CMD_HELP(shell,
"run a shell command",
"test command for libmish!");
MISH_CMD_REGISTER(shell, _test_shell_cnt);

View File

@ -0,0 +1,146 @@
/*
* This is a stress/scenario test for the mish_input_read function
*/
#define MISH_INPUT_TEST
#include <unistd.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include "mish.h"
static uint8_t *words = NULL;
static size_t words_size = 0;
static size_t words_offset = 0;
static int ccount = 0;
static ssize_t _test_read(int fd, void *w, size_t count) {
errno = 0;
if (!(ccount++ % 17)) {
errno = EAGAIN;
printf("%s fake EAGAIN\n", __func__);
return -1;
}
if (words_offset + count > words_size)
count = words_size - words_offset;
memcpy(w, words + words_offset, count);
words_offset += count;
if (!count) {
printf("%s all input sent\n", __func__);
}
return count;
}
#define read(_a,_b,_c) _test_read(_a,_b,_c)
#include "mish_input.c"
#include "mish_line.c"
#undef read
/* duplicate from mish_session.c */
/*
* get a localtime equivalent, with milliseconds added
*/
uint64_t
_mish_stamp_ms()
{
return 0;
}
int main()
{
const char *filen = "/usr/share/dict/american-english";
struct stat st;
int fd;
if ((lstat(filen, &st) == -1) || (fd = open(filen, O_RDONLY)) == -1) {
perror(filen);
exit(1);
}
words_size = st.st_size;
words = malloc(words_size + 1);
if (read(fd, words, st.st_size) != st.st_size) {
perror("EOF unexpected");
exit(1);
}
words[words_size] = 0;
printf("File %s read\n", filen);
/* That's not needed for the test, but init() relies on it */
mish_t mish = {};
mish_p m = &mish;
FD_ZERO(&m->select.read);
FD_ZERO(&m->select.write);
TAILQ_INIT(&m->backlog.log);
TAILQ_INIT(&m->clients);
/* we don't need the file descriptor as we bypass "read" but the
* init function relies on it so here is a socketpair for it to play
* with */
int sk[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk)) {
perror("socketpair");
exit(1);
}
mish_input_t input = {};
_mish_input_init(m, &input, sk[0]);
uint8_t *cursor = words;
int linec = 0;
mish_line_p ln = NULL;
/*
* Right so now we can actually test the function. We keep a running
* pointer on the source data, and the "last" line we compared, and
* compared line's content with the source buffer. If all goes
* well, they should match exactly.
*/
do {
_mish_input_read(m, &m->select.read, &input);
printf(" read up to o: %d/%d\n", (int)words_offset, (int)words_size);
if (!ln)
ln = TAILQ_FIRST(&input.backlog);
else if (TAILQ_NEXT(ln, self))
ln = TAILQ_NEXT(ln, self);
else
continue;
while (ln) {
linec++;
// printf("line %4d: %s", linec, ln->line);
uint8_t * l = (uint8_t*)ln->line;
for (int ic = 0; ic < ln->len; ic++, cursor++) {
if (l[ic] != *cursor) {
printf("ERROR: Mismatch Offset %ld char #%d/%d (%x/%x) line %d: %s\n",
cursor - words, ic,
ln->len,
l[ic], *cursor,
linec, l);
goto done;
}
}
if (TAILQ_LAST(&input.backlog, mish_line_queue_t) == ln)
break;
ln = TAILQ_NEXT(ln, self);
}
} while (words_offset < words_size);
printf("%d lines were read and compared\n", linec);
done:
exit(0);
}

43
libmish/tests/mish_test.c Normal file
View File

@ -0,0 +1,43 @@
/*
* mish_test.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "mish.h"
int cnt = 0;
int main()
{
mish_prepare(0);
while (1) {
sleep(1);
printf("Count %d\n", cnt++);
}
}
/* And here is a command line action that can reset the 'cnt' variable */
static void _test_set_cnt(void * param, int argc, const char * argv[])
{
if (argc > 1) {
cnt = atoi(argv[1]);
} else {
fprintf(stderr,
"%s: syntax 'set XXX' to set the variable\n", argv[0]);
}
}
MISH_CMD_NAMES(set, "set");
MISH_CMD_HELP(set,
"set 'cnt' variable",
"test command for libmish!");
MISH_CMD_REGISTER(set, _test_set_cnt);

View File

@ -0,0 +1,33 @@
/*
* mish_vt_test.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stdio.h>
#include "../src/mish_vt.c"
/* small test unit for the VT decoder, mostly for UTF8 */
int main()
{
const char * input = "Hello 😈 \033[1mThere\033[0m";
mish_vt_sequence_t sq = {};
printf("%s\n", input);
const char *s = input;
int cg = 0;
while (*s) {
if (_mish_vt_sequence_char(&sq, *s)) {
printf("glyph s:%08x g:%08x\n", sq.seq, sq.glyph);
if (sq.glyph)
cg++;
}
s++;
}
printf("%d glyphs in string\n", cg);
}

322
nuklear/mii_emu.c Normal file
View File

@ -0,0 +1,322 @@
/*
* mii_emu.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <time.h>
#include <limits.h>
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#define NK_BUTTON_TRIGGER_ON_RELEASE
#include "nuklear.h"
#define NK_XLIB_GL3_IMPLEMENTATION
#define NK_XLIB_LOAD_OPENGL_EXTENSIONS
#include "nuklear_xlib_gl3.h"
#include "mii.h"
#include "mish.h"
#define WINDOW_WIDTH 1280
#define WINDOW_HEIGHT 720 // (10 + (192) * 3)
#define MAX_VERTEX_BUFFER 512 * 1024
#define MAX_ELEMENT_BUFFER 128 * 1024
void
mii_nuklear(
mii_t *mii,
struct nk_context *ctx);
void
mii_nuklear_init(
mii_t *mii,
struct nk_context *ctx);
/* ===============================================================
*
* DEMO
*
* ===============================================================*/
struct XWindow {
Display *dpy;
Window win;
XVisualInfo *vis;
Colormap cmap;
XSetWindowAttributes swa;
XWindowAttributes attr;
GLXFBConfig fbc;
Atom wm_delete_window;
int width, height;
};
static int gl_err = nk_false;
static int gl_error_handler(Display *dpy, XErrorEvent *ev)
{NK_UNUSED(dpy); NK_UNUSED(ev); gl_err = nk_true;return 0;}
static void
die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputs("\n", stderr);
exit(EXIT_FAILURE);
}
static int
has_extension(const char *string, const char *ext)
{
const char *start, *where, *term;
where = strchr(ext, ' ');
if (where || *ext == '\0')
return nk_false;
for (start = string;;) {
where = strstr((const char*)start, ext);
if (!where) break;
term = where + strlen(ext);
if (where == start || *(where - 1) == ' ') {
if (*term == ' ' || *term == '\0')
return nk_true;
}
start = term;
}
return nk_false;
}
struct nk_font *nk_main_font = NULL;
/* global now, mish commands use it */
mii_t g_mii = {};
int
main(
int argc,
const char *argv[])
{
/* Platform */
int running = 1;
struct XWindow win;
GLXContext glContext;
struct nk_context *ctx;
struct nk_colorf bg;
mii_init(&g_mii);
int idx = 0;
uint32_t flags = MII_INIT_DEFAULT;
int r = mii_argv_parse(&g_mii, argc, argv, &idx, &flags);
if (r == 0) {
printf("mii: Invalid argument %s, skipped\n", argv[idx]);
} else if (r == -1)
exit(1);
mii_prepare(&g_mii, flags);
mish_prepare(1);
printf("MISH_TELNET_PORT = %s\n", getenv("MISH_TELNET_PORT"));
memset(&win, 0, sizeof(win));
win.dpy = XOpenDisplay(NULL);
if (!win.dpy) die("Failed to open X display\n");
{
/* check glx version */
int glx_major, glx_minor;
if (!glXQueryVersion(win.dpy, &glx_major, &glx_minor))
die("[X11]: Error: Failed to query OpenGL version\n");
if ((glx_major == 1 && glx_minor < 3) || (glx_major < 1))
die("[X11]: Error: Invalid GLX version!\n");
}
{
/* find and pick matching framebuffer visual */
int fb_count;
static GLint attr[] = {
GLX_X_RENDERABLE, True,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_ALPHA_SIZE, 8,
GLX_DEPTH_SIZE, 24,
GLX_STENCIL_SIZE, 8,
GLX_DOUBLEBUFFER, True,
None
};
GLXFBConfig *fbc;
fbc = glXChooseFBConfig(win.dpy, DefaultScreen(win.dpy), attr, &fb_count);
if (!fbc) die("[X11]: Error: failed to retrieve framebuffer configuration\n");
{
/* pick framebuffer with most samples per pixel */
int i;
int fb_best = -1, best_num_samples = -1;
for (i = 0; i < fb_count; ++i) {
XVisualInfo *vi = glXGetVisualFromFBConfig(win.dpy, fbc[i]);
if (vi) {
int sample_buffer, samples;
glXGetFBConfigAttrib(win.dpy, fbc[i], GLX_SAMPLE_BUFFERS, &sample_buffer);
glXGetFBConfigAttrib(win.dpy, fbc[i], GLX_SAMPLES, &samples);
if ((fb_best < 0) || (sample_buffer && samples > best_num_samples))
fb_best = i, best_num_samples = samples;
XFree(vi);
}
}
win.fbc = fbc[fb_best];
XFree(fbc);
win.vis = glXGetVisualFromFBConfig(win.dpy, win.fbc);
}
}
{
/* create window */
win.cmap = XCreateColormap(win.dpy, RootWindow(win.dpy, win.vis->screen), win.vis->visual, AllocNone);
win.swa.colormap = win.cmap;
win.swa.background_pixmap = None;
win.swa.border_pixel = 0;
win.swa.event_mask =
ExposureMask | KeyPressMask | KeyReleaseMask |
ButtonPress | ButtonReleaseMask| ButtonMotionMask |
Button1MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask|
PointerMotionMask| StructureNotifyMask;
win.win = XCreateWindow(win.dpy, RootWindow(win.dpy, win.vis->screen), 0, 0,
WINDOW_WIDTH, WINDOW_HEIGHT, 0, win.vis->depth, InputOutput,
win.vis->visual, CWBorderPixel|CWColormap|CWEventMask, &win.swa);
if (!win.win) die("[X11]: Failed to create window\n");
XFree(win.vis);
char title[128];
sprintf(title, "MII //e Emulator");
char *telnet = getenv("MISH_TELNET_PORT");
if (telnet)
sprintf(title + strlen(title), " (telnet port %s)", telnet);
else
sprintf(title + strlen(title), " (telnet disabled)");
XStoreName(win.dpy, win.win, title);
XMapWindow(win.dpy, win.win);
win.wm_delete_window = XInternAtom(win.dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(win.dpy, win.win, &win.wm_delete_window, 1);
}
{
/* create opengl context */
typedef GLXContext(*glxCreateContext)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
int(*old_handler)(Display*, XErrorEvent*) = XSetErrorHandler(gl_error_handler);
const char *extensions_str = glXQueryExtensionsString(win.dpy, DefaultScreen(win.dpy));
glxCreateContext create_context = (glxCreateContext)
glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB");
gl_err = nk_false;
if (!has_extension(extensions_str, "GLX_ARB_create_context") || !create_context) {
fprintf(stdout, "[X11]: glXCreateContextAttribARB() not found...\n");
fprintf(stdout, "[X11]: ... using old-style GLX context\n");
glContext = glXCreateNewContext(win.dpy, win.fbc, GLX_RGBA_TYPE, 0, True);
} else {
GLint attr[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 0,
None
};
glContext = create_context(win.dpy, win.fbc, 0, True, attr);
XSync(win.dpy, False);
if (gl_err || !glContext) {
/* Could not create GL 3.0 context. Fallback to old 2.x context.
* If a version below 3.0 is requested, implementations will
* return the newest context version compatible with OpenGL
* version less than version 3.0.*/
attr[1] = 1; attr[3] = 0;
gl_err = nk_false;
fprintf(stdout, "[X11] Failed to create OpenGL 3.0 context\n");
fprintf(stdout, "[X11] ... using old-style GLX context!\n");
glContext = create_context(win.dpy, win.fbc, 0, True, attr);
}
}
XSync(win.dpy, False);
XSetErrorHandler(old_handler);
if (gl_err || !glContext)
die("[X11]: Failed to create an OpenGL context\n");
glXMakeCurrent(win.dpy, win.win, glContext);
}
//extern const unsigned char mii_proggy_data[];
//extern const unsigned int mii_proggy_size;
extern const unsigned char mii_droid_data[];
extern const unsigned int mii_droid_size;
ctx = nk_x11_init(win.dpy, win.win);
/* Load Fonts: if none of these are loaded a default font will be used */
{
struct nk_font_atlas *atlas;
nk_x11_font_stash_begin(&atlas);
struct nk_font_config cfg = nk_font_config(0);
#if 0
nk_rune ranges[] = {
0x0020, 0x007E, /* Ascii */
0x00A1, 0x00FF, /* Symbols + Umlaute */
0
};
#endif
cfg.range = nk_font_default_glyph_ranges();
cfg.oversample_h = cfg.oversample_v = 1;
cfg.pixel_snap = true;
nk_main_font = nk_font_atlas_add_from_memory(atlas,
(void*)mii_droid_data, mii_droid_size, 20, &cfg);
nk_x11_font_stash_end();
/*nk_style_load_all_cursors(ctx, atlas->cursors);*/
if (nk_main_font)
nk_style_set_font(ctx, &nk_main_font->handle);
}
mii_nuklear_init(&g_mii, ctx);
bg.r = 0.0f; bg.g = 0.0f; bg.b = 0.0f; bg.a = 1.0f;
// bg.r = 0.8f; bg.g = 0.8f; bg.b = 0.8f; bg.a = 1.0f;
while (running) {
/* Input */
XEvent evt;
nk_input_begin(ctx);
while (XPending(win.dpy)) {
XNextEvent(win.dpy, &evt);
if (evt.type == ClientMessage) goto cleanup;
if (XFilterEvent(&evt, win.win)) continue;
nk_x11_handle_event(&evt);
}
nk_input_end(ctx);
/* GUI */
mii_nuklear(&g_mii, ctx);
/* Draw */
XGetWindowAttributes(win.dpy, win.win, &win.attr);
glViewport(0, 0, win.width, win.height);
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(bg.r, bg.g, bg.b, bg.a);
/* IMPORTANT: `nk_x11_render` modifies some global OpenGL state
* with blending, scissor, face culling, depth test and viewport and
* defaults everything back into a default state.
* Make sure to either a.) save and restore or b.) reset your own state after
* rendering the UI. */
nk_x11_render(NK_ANTI_ALIASING_ON, MAX_VERTEX_BUFFER, MAX_ELEMENT_BUFFER);
glXSwapBuffers(win.dpy, win.win);
}
cleanup:
nk_x11_shutdown();
glXMakeCurrent(win.dpy, 0, 0);
glXDestroyContext(win.dpy, glContext);
XUnmapWindow(win.dpy, win.win);
XFreeColormap(win.dpy, win.cmap);
XDestroyWindow(win.dpy, win.win);
XCloseDisplay(win.dpy);
return 0;
}

9
nuklear/mii_fonts.c Normal file
View File

@ -0,0 +1,9 @@
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#define INCBIN_PREFIX mii_
#include "incbin.h"
INCBIN(proggy, "fonts/ProggyClean.ttf");
INCBIN(droid, "fonts/DroidSans.ttf");

436
nuklear/mii_mish.c Normal file
View File

@ -0,0 +1,436 @@
/*
* mii_mish.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include "mii.h"
#include "mii_bank.h"
#include "mii_sw.h"
#include "mii_65c02_ops.h"
#include "mii_65c02_disasm.h"
extern mii_t g_mii;
void
_mii_mish_text(
void * param,
int argc,
const char * argv[])
{
// load 0x400, calculate the 24 line addresses from the code in video
// and show the 40 or 80 chars, depending on col80
mii_t * mii = &g_mii;
uint16_t a = 0x400;
int page2 = mii_read_one(mii, SWPAGE2);
// int col80 = mii_read_one(mii, SW80COL);
for (int li = 0; li < 24; li++) {
int i = li;
a = (0x400 + (0x400 * page2));
a += ((i & 0x07) << 7) | ((i >> 3) << 5) | ((i >> 3) << 3);
printf("%04x: ", a);
for (int ci = 0; ci < 40; ci++) {
uint8_t c = (mii_read_one(mii, a++) & 0x3f);
printf("%c", c >= 0x20 && c < 0x7f ? c : '.');
}
printf("\n");
}
}
static void
_mii_mish_cmd(
void * param,
int argc,
const char * argv[])
{
const char * state[] = { "RUNNING", "STOPPED", "STEP" };
mii_t * mii = &g_mii;
if (!argv[1]) {
show_state:
printf("mii: %s Target speed: %.3fMHz Current: %.3fMHz\n",
state[mii->state], mii->speed, mii->speed_current);
mii_dump_run_trace(mii);
mii_dump_trace_state(mii);
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
bool text = !!mii_bank_peek(main, SWTEXT);
bool page2 = !!mii_bank_peek(main, SWPAGE2);
bool col80 = !!mii_bank_peek(main, SW80COL);
bool mixed = !!mii_bank_peek(main, SWMIXED);
bool hires = !!mii_bank_peek(main, SWHIRES);
bool dhires = !!mii_bank_peek(main, SWRDDHIRES);
printf("text:%d page2:%d col80:%d mixed:%d hires:%d dhires:%d\n",
text, page2, col80, mixed, hires, dhires);
return;
}
if (!strcmp(argv[1], "reset")) {
mii_reset(mii, 0);
return;
}
if (!strcmp(argv[1], "mem")) {
for (int i = 0; i < 16; i++) {
printf("%02x: ", i * 16);
for (int j = 0; j < 16; j++)
printf("%d:%d ", mii->mem[(i * 16) + j].read,
mii->mem[(i * 16) + j].write);
printf("\n");
}
return;
}
if (!strcmp(argv[1], "trace")) {
mii->trace_cpu = !mii->trace_cpu;
printf("trace_cpu %d\n", mii->trace_cpu);
return;
}
if (!strcmp(argv[1], "poke")) {
if (argc < 4) {
printf("poke: missing argument\n");
return;
}
uint16_t addr = strtol(argv[2], NULL, 16);
uint8_t val = strtol(argv[3], NULL, 16);
mii_mem_access(mii, addr, &val, false, true);
return;
}
if (!strcmp(argv[1], "peek")) {
if (argc < 3) {
printf("peek: missing argument\n");
return;
}
uint16_t addr = strtol(argv[2], NULL, 16);
uint8_t val;
mii_mem_access(mii, addr, &val, false, false);
printf("%04x: %02x\n", addr, val);
return;
}
if (!strcmp(argv[1], "speed")) {
if (argc < 3) {
printf("speed: missing argument\n");
return;
}
float speed = atof(argv[2]);
if (speed < 0.1 || speed > 30.0) {
printf("speed: speed must be between 0.0 and 30.0\n");
return;
}
mii->speed = speed;
printf("speed: %.3fMHz\n", speed);
return;
}
if (!strcmp(argv[1], "stop")) {
mii->state = MII_STOPPED;
goto show_state;
}
printf("mii: unknown command %s\n", argv[1]);
}
void
_mii_mish_bp(
void * param,
int argc,
const char * argv[])
{
mii_t * mii = &g_mii;
if (!argv[1] || !strcmp(argv[1], "list")) {
printf("breakpoints: map %04x\n", mii->debug.bp_map);
for (int i = 0; i < (int)sizeof(mii->debug.bp_map)*8; i++) {
printf("%2d %c %04x %c%c%c size:%2d\n", i,
(mii->debug.bp_map & (1 << i)) ? '*' : ' ',
mii->debug.bp[i].addr,
(mii->debug.bp[i].kind & MII_BP_R) ? 'r' : '-',
(mii->debug.bp[i].kind & MII_BP_W) ? 'w' : '-',
(mii->debug.bp[i].kind & MII_BP_STICKY) ? 's' : '-',
mii->debug.bp[i].size);
}
return;
}
const char *p = argv[1];
if (p[0] == '+') {
p++;
int addr = strtol(p, NULL, 16);
int kind = 0;
if (strchr(p, 'r'))
kind |= MII_BP_R;
if (strchr(p, 'w'))
kind |= MII_BP_W;
if (strchr(p, 's'))
kind |= MII_BP_STICKY;
if (!kind || kind == MII_BP_STICKY)
kind |= MII_BP_R;
int size = 1;
if (argv[2])
size = strtol(argv[2], NULL, 16);
if (!size) size++;
for (int i = 0; i < (int)sizeof(mii->debug.bp_map)*8; i++) {
if (!(mii->debug.bp_map & (1 << i)) || mii->debug.bp[i].addr == addr) {
mii->debug.bp_map |= 1 << i;
mii->debug.bp[i].addr = addr;
mii->debug.bp[i].kind = kind;
mii->debug.bp[i].size = size;
printf("breakpoint %d set at %04x size %d\n", i, addr, size);
break;
}
}
return;
}
if (p[0] == '-') {
p++;
int idx = strtol(p, NULL, 10);
if (idx >= 0 && idx < 7) {
mii->debug.bp_map &= ~(1 << idx);
printf("breakpoint %d cleared\n", idx);
}
}
}
static void
_mii_mish_il(
void * param,
int argc,
const char * argv[])
{
static uint16_t addr = 0x800;
if (argv[1]) {
addr = strtol(argv[1], NULL, 16);
if (addr >= 0xffff)
addr = 0xfff0;
}
mii_t * mii = &g_mii;
for (int li = 0; li < 20; li++) {
uint8_t op[16];
for (int bi = 0; bi < 4; bi++)
mii_mem_access(mii, addr + bi, op + bi, false, false);
char dis[64];
addr += mii_cpu_disasm_one(op, addr, dis, sizeof(dis),
MII_DUMP_DIS_PC | MII_DUMP_DIS_DUMP_HEX);
printf("%s\n", dis);
}
}
static void
_mii_mish_dm(
void * param,
int argc,
const char * argv[])
{
static uint16_t addr = 0x800;
if (argv[1]) {
addr = strtol(argv[1], NULL, 16);
if (addr >= 0xffff)
addr = 0xfff0;
}
mii_t * mii = &g_mii;
if (!strcmp(argv[0], "dm")) {
printf("dm: %04x\n", addr);
for (int i = 0; i < 8; i++) {
printf("%04x: ", addr);
for (int j = 0; j < 16; j++)
printf("%02x ", mii_read_one(mii, addr++));
printf("\n");
}
return;
}
if (!strcmp(argv[0], "db")) {
printf("%s: %04x: ", argv[0], addr);
printf("%02x ", mii_read_one(mii, addr++));
printf("\n");
return;
}
if (!strcmp(argv[0], "dw") || !strcmp(argv[0], "da")) {
printf("%s: %04x: ", argv[0], addr);
uint8_t l = mii_read_one(mii, addr++);
uint8_t h = mii_read_one(mii, addr++);
printf("%02x%02x", h, l);
printf("\n");
return;
}
}
static void
_mii_mish_step(
void * param,
int argc,
const char * argv[])
{
if (argv[0][0] == 's') {
mii_t * mii = &g_mii;
if (argv[1]) {
int n = strtol(argv[1], NULL, 10);
mii->trace.step_inst = n;
} else
mii->trace.step_inst = 1;
mii->state = MII_STEP;
return;
}
if (argv[0][0] == 'n') {
mii_t * mii = &g_mii;
// read current opcode, find how how many bytes it take,
// then put a temporary breakpoint to the next PC.
// all of that if this is not a relative branch of course, in
// which case we use a normal 'step' behaviour
uint8_t op;
mii_mem_access(mii, mii->cpu.PC, &op, false, false);
if (op == 0x20) {
// set a temp breakpoint on reading 3 bytes from PC
for (int i = 0; i < (int)sizeof(mii->debug.bp_map) * 8; i++) {
if ((mii->debug.bp_map & (1 << i)))
continue;
mii->debug.bp[i].addr = mii->cpu.PC + 3;
mii->debug.bp[i].kind = MII_BP_R;
mii->debug.bp[i].size = 1;
mii->debug.bp[i].silent = 1;
mii->debug.bp_map |= 1 << i;
mii->state = MII_RUNNING;
return;
}
printf("no more breakpoints available\n");
} else {
mii->trace.step_inst = 1;
mii->state = MII_STEP;
}
return;
}
if (argv[0][0] == 'c') {
mii_t * mii = &g_mii;
mii->trace.step_inst = 0;
mii->state = MII_RUNNING;
return;
}
if (argv[0][0] == 'h') {
mii_t * mii = &g_mii;
mii->trace.step_inst = 0;
mii->state = MII_STOPPED;
return;
}
}
extern int g_mii_audio_record_fd;
#include <math.h>
static void
_mii_mish_audio(
void * param,
int argc,
const char * argv[])
{
if (argc < 2) {
printf("audio: missing argument\n");
return;
}
if (!strcmp(argv[1], "record")) {
if (g_mii_audio_record_fd != -1) {
close(g_mii_audio_record_fd);
g_mii_audio_record_fd = -1;
printf("audio: stop recording\n");
} else {
g_mii_audio_record_fd = open("audio.raw",
O_WRONLY | O_CREAT | O_TRUNC, 0644);
printf("audio: start recording\n");
}
} else if (!strcmp(argv[1], "mute")) {
if (argv[2] && !strcmp(argv[2], "off"))
g_mii.speaker.muted = false;
else if (argv[2] && !strcmp(argv[2], "on"))
g_mii.speaker.muted = true;
else if (!argv[2] || (argv[2] && !strcmp(argv[2], "toggle")))
g_mii.speaker.muted = !g_mii.speaker.muted;
printf("audio: %s\n", g_mii.speaker.muted ? "muted" : "unmuted");
} else if (!strcmp(argv[1], "volume")) {
if (argc < 3) {
printf("audio: missing volume\n");
return;
}
// convert a linear volume from 0 to 10 into a float from 0.0 to 1.0
float vol = atof(argv[2]);
if (vol < 0) vol = 0;
else if (vol > 10) vol = 10;
mii_speaker_volume(&g_mii.speaker, vol);
printf("audio: volume %.3f (amp: %.4f)\n",
vol, g_mii.speaker.vol_multiplier);
} else {
printf("audio: unknown command %s\n", argv[1]);
}
}
#include "mish.h"
MISH_CMD_NAMES(mii, "mii");
MISH_CMD_HELP(mii,
"mii: access internals, trace, reset, speed, etc",
" <default> : dump current state",
" reset : reset the cpu",
" t|trace : toggle trace_cpu (WARNING HUGE traces!))",
" mem : dump memory and bank map",
" poke <addr> <val> : poke a value in memory (respect SW)",
" peek <addr> : peek a value in memory (respect SW)",
" speed <speed> : set speed in MHz",
" stop : stop the cpu"
);
MISH_CMD_REGISTER(mii, _mii_mish_cmd);
MISH_CMD_NAMES(bp, "bp");
MISH_CMD_HELP(bp,
"mii: breakpoints. 'sticky' means the breakpoint is re-armed after hit",
" <default> : dump state",
" +<addr>[r|w][s] [size]: add at <addr> for read/write, sticky",
" -<index> : disable (don't clear) breakpoint <index>"
);
MISH_CMD_REGISTER(bp, _mii_mish_bp);
MISH_CMD_NAMES(il, "il");
MISH_CMD_HELP(il,
"mii: disassembly",
" <default> : list next 20 instructions.",
" [addr]: start at address addr"
);
MISH_CMD_REGISTER(il, _mii_mish_il);
MISH_CMD_NAMES(dm, "dm","db","dw","da");
MISH_CMD_HELP(dm,
"mii: dump memory, byte, word, address",
" <default>|dm <addr>: dump 64 bytes.",
" db [<addr>]: dump one byte.",
" dw [<addr>]: dump one word.",
" da [<addr>]: dump one address.",
" [addr]: start at address addr"
);
MISH_CMD_REGISTER(dm, _mii_mish_dm);
MISH_CMD_NAMES(step, "s","step","n","next","cont","h","halt");
MISH_CMD_HELP(step,
"mii: step instructions",
" s|step [num]: step [num, or one] instruction.",
" n|next : step one instruction, skip subroutines.",
" cont : continue execution."
);
MISH_CMD_REGISTER(step, _mii_mish_step);
MISH_CMD_NAMES(text, "text");
MISH_CMD_HELP(text,
"mii: show text page [buggy]",
" <default> : that's it"
);
MISH_CMD_REGISTER(text, _mii_mish_text);
MISH_CMD_NAMES(audio, "audio");
MISH_CMD_HELP(audio,
"audio: audio control/debug",
" record: record/stop debug file.",
" mute: mute/unmute audio.",
" volume: set volume (0.0 to 1.0)."
);
MISH_CMD_REGISTER(audio, _mii_mish_audio);

46
nuklear/mii_mish_dd.c Normal file
View File

@ -0,0 +1,46 @@
/*
* mii_mish_dd.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mii.h"
extern mii_t g_mii;
static void
_mii_mish_dd(
void * param,
int argc,
const char * argv[])
{
if (!argv[1] || !strcmp(argv[1], "list")) {
mii_dd_t *d = g_mii.dd.drive;
printf(" ID %-16s %-20s\n", "Card", "Name");
while (d) {
printf("%d:%d %-16s %-20s : %s\n",
d->slot_id, d->drive,
d->slot->drv->desc,
d->name, d->file ? d->file->pathname : "*empty*");
d = d->next;
}
return;
}
}
#include "mish.h"
MISH_CMD_NAMES(dd, "dd", "disk");
MISH_CMD_HELP(dd,
"mii: disk commands",
" <default>|list: list all disk drives"
);
MISH_CMD_REGISTER(dd, _mii_mish_dd);

626
nuklear/mii_nuklear.c Normal file
View File

@ -0,0 +1,626 @@
/*
* mii_nuklear.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include "mii.h"
#include "mii_bank.h"
#include "mii_sw.h"
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#define NK_BUTTON_TRIGGER_ON_RELEASE
#include "nuklear.h"
#include "nuklear_xlib_gl3.h"
//#include "stb_image_write.h"
#include <GL/gl.h>
#include <GL/glx.h>
#define _NK_RGBA(_r,_g,_b,_a) {.r=_r,.g=_g,.b=_b,.a=_a}
static const struct nk_color style[NK_COLOR_COUNT] = {
[NK_COLOR_TEXT] = _NK_RGBA(0, 0, 0, 255),
[NK_COLOR_WINDOW] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_HEADER] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_BORDER] = _NK_RGBA(0, 0, 0, 255),
[NK_COLOR_BUTTON] = _NK_RGBA(185, 185, 185, 255),
[NK_COLOR_BUTTON_HOVER] = _NK_RGBA(170, 170, 170, 255),
[NK_COLOR_BUTTON_ACTIVE] = _NK_RGBA(160, 160, 160, 255),
[NK_COLOR_TOGGLE] = _NK_RGBA(150, 150, 150, 255),
[NK_COLOR_TOGGLE_HOVER] = _NK_RGBA(120, 120, 120, 255),
[NK_COLOR_TOGGLE_CURSOR] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_SELECT] = _NK_RGBA(190, 190, 190, 255),
[NK_COLOR_SELECT_ACTIVE] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_SLIDER] = _NK_RGBA(190, 190, 190, 255),
[NK_COLOR_SLIDER_CURSOR] = _NK_RGBA(80, 80, 80, 255),
[NK_COLOR_SLIDER_CURSOR_HOVER] = _NK_RGBA(70, 70, 70, 255),
[NK_COLOR_SLIDER_CURSOR_ACTIVE] = _NK_RGBA(60, 60, 60, 255),
[NK_COLOR_PROPERTY] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_EDIT] = _NK_RGBA(150, 150, 150, 255),
[NK_COLOR_EDIT_CURSOR] = _NK_RGBA(0, 0, 0, 255),
[NK_COLOR_COMBO] = _NK_RGBA(175, 175, 175, 255),
[NK_COLOR_CHART] = _NK_RGBA(160, 160, 160, 255),
[NK_COLOR_CHART_COLOR] = _NK_RGBA(45, 45, 45, 255),
[NK_COLOR_CHART_COLOR_HIGHLIGHT] = _NK_RGBA( 255, 0, 0, 255),
[NK_COLOR_SCROLLBAR] = _NK_RGBA(180, 180, 180, 255),
[NK_COLOR_SCROLLBAR_CURSOR] = _NK_RGBA(140, 140, 140, 255),
[NK_COLOR_SCROLLBAR_CURSOR_HOVER] = _NK_RGBA(150, 150, 150, 255),
[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE] = _NK_RGBA(160, 160, 160, 255),
[NK_COLOR_TAB_HEADER] = _NK_RGBA(180, 180, 180, 255),
};
static GLuint screen_texture;
static struct nk_image screen_nk;
#include <time.h>
typedef uint64_t mii_time_t;
enum {
MII_TIME_RES = 1,
MII_TIME_SECOND = 1000000,
MII_TIME_MS = (MII_TIME_SECOND/1000),
};
mii_time_t
mii_get_time()
{
struct timespec tim;
clock_gettime(CLOCK_MONOTONIC_RAW, &tim);
uint64_t time = ((uint64_t)tim.tv_sec) * (1000000 / MII_TIME_RES) +
tim.tv_nsec / (1000 * MII_TIME_RES);
return time;
}
#include <pthread.h>
#include "fifo_declare.h"
static pthread_t mii_thread;
static bool mii_thread_running = false;
//static mii_trace_t mii_trace = {};
float default_fps = 60;
enum {
SIGNAL_RESET,
SIGNAL_STOP,
SIGNAL_STEP,
SIGNAL_RUN,
};
typedef struct {
uint8_t cmd;
uint8_t data;
} signal_t;
DECLARE_FIFO(signal_t, signal_fifo, 16);
DEFINE_FIFO(signal_t, signal_fifo);
signal_fifo_t signal_fifo;
static void *mii_thread_func(void *arg)
{
mii_t *mii = (mii_t *) arg;
mii_thread_running = true;
__uint128_t last_cycles = mii->cycles;
uint32_t running = 1;
unsigned long target_fps_us = 1000000 / default_fps;
long sleep_time = target_fps_us;
//mii_time_t base = mii_get_time(NULL);
uint32_t last_frame = mii->video.frame_count;
mii_time_t last_frame_stamp = mii_get_time();
while (mii_thread_running) {
signal_t sig;
while (!signal_fifo_isempty(&signal_fifo)) {
sig = signal_fifo_read(&signal_fifo);
switch (sig.cmd) {
case SIGNAL_RESET:
mii_reset(mii, sig.data);
break;
case SIGNAL_STOP:
mii_dump_run_trace(mii);
mii_dump_trace_state(mii);
mii->state = MII_STOPPED;
break;
case SIGNAL_STEP:
mii->state = MII_STEP;
running = 1;
break;
case SIGNAL_RUN:
mii->state = MII_RUNNING;
last_frame_stamp = mii_get_time();
running = 1;
break;
}
}
if (mii->state != MII_STOPPED)
mii_run(mii);
switch (mii->state) {
case MII_STOPPED:
usleep(1000);
break;
case MII_STEP:
if (running) {
running--;
mii_dump_trace_state(mii);
usleep(1000);
running = 1;
if (mii->trace.step_inst)
mii->trace.step_inst--;
if (mii->trace.step_inst == 0)
mii->state = MII_STOPPED;
}
break;
case MII_RUNNING:
break;
}
if (mii->video.frame_count != last_frame) {
sleep_time = target_fps_us;
mii_time_t now = mii_get_time();
if (mii->state == MII_RUNNING) {
mii_time_t delta = now - last_frame_stamp;
// printf("frame time %d/%d sleep time %d\n",
// (int)delta, (int)target_fps_us,
// (int)target_fps_us - delta);
sleep_time = target_fps_us - delta;
if (sleep_time < 0)
sleep_time = 0;
last_frame = mii->video.frame_count;
while (last_frame_stamp <= now)
last_frame_stamp += target_fps_us;
// calculate the MHz
__uint128_t cycles = mii->cycles;
__uint128_t delta_cycles = cycles - last_cycles;
last_cycles = cycles;
mii->speed_current = delta_cycles / (float)target_fps_us;
}
usleep(sleep_time);
}
}
return NULL;
}
extern struct nk_font *nk_main_font;
struct nk_font *nk_mono_font = NULL;
extern const unsigned char mii_proggy_data[];
extern const unsigned int mii_proggy_size;
extern const unsigned char mii_droid_data[];
extern const unsigned int mii_droid_size;
void
mii_nuklear_init(
mii_t *mii,
struct nk_context *ctx)
{
nk_style_from_table(ctx, style);
{
struct nk_font_atlas *atlas;
nk_x11_font_stash_begin(&atlas);
struct nk_font_config cfg = nk_font_config(0);
#if 0
nk_rune ranges[] = {
0x0020, 0x007E, /* Ascii */
0x00A1, 0x00FF, /* Symbols + Umlaute */
0
};
#endif
cfg.range = nk_font_default_glyph_ranges();
cfg.oversample_h = cfg.oversample_v = 1;
cfg.pixel_snap = true;
struct nk_font *nkf = nk_font_atlas_add_from_memory(atlas,
(void*)mii_proggy_data, mii_proggy_size, 20, &cfg);
nk_x11_font_stash_end();
nk_mono_font = nkf;
}
glGenTextures(1, &screen_texture);
glBindTexture(GL_TEXTURE_2D, screen_texture);
glTexImage2D(GL_TEXTURE_2D, 0, 4,
MII_VRAM_WIDTH,
MII_VRAM_HEIGHT, 0, GL_RGBA,
GL_UNSIGNED_BYTE,
mii->video.pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// printf("%s texture created %d\n", __func__, screen_texture);
// display opengl error
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
printf("Error creating texture: %d\n", err);
}
screen_nk = nk_subimage_id(
screen_texture, MII_VRAM_WIDTH, MII_VRAM_HEIGHT,
nk_rect(0, 0, MII_VIDEO_WIDTH, MII_VIDEO_HEIGHT));
/* start the thread */
pthread_create(&mii_thread, NULL, mii_thread_func, mii);
}
extern int disk2_debug;
int show_zero_page = 0;
static void
mii_nuklear_handle_input(
mii_t *mii,
struct nk_context *ctx)
{
struct nk_input *in = &ctx->input;
if (in->keyboard.text_len) {
// printf("INPUT %d %s\n", in->keyboard.text_len, in->keyboard.text);
if (in->keyboard.text_len < 4) {
mii_keypress(mii, in->keyboard.text[0]);
} else if (in->keyboard.text_len > 1) {
uint32_t *raw = ((uint32_t *) in->keyboard.text);
for (int ki = 0; ki < in->keyboard.text_len / 4; ki ++) {
uint32_t key = (raw[ki] >> 16) & 0xffff;
uint8_t down = raw[ki] & 0xff;
printf("KEY %04x %s\n", key, down ? "down" : "up");
if (down) {
if (key == 0xffc9) { // F12
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
signal_t sig = {
.cmd = SIGNAL_RESET,
.data = nk_input_is_key_down(in, NK_KEY_SHIFT)
};
signal_fifo_write(&signal_fifo, sig);
printf("RESET\n");
}
} else if (key == 0xffc8) { // F11
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
signal_t sig = {
.cmd = SIGNAL_STOP,
};
signal_fifo_write(&signal_fifo, sig);
printf("STOP\n");
}
} else if (key == 0xffc7) { // F10
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
signal_t sig = {
.cmd = SIGNAL_STEP,
};
signal_fifo_write(&signal_fifo, sig);
printf("STEP\n");
}
} else if (key == 0xffc6) { // F9
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
signal_t sig = {
.cmd = SIGNAL_RUN,
};
signal_fifo_write(&signal_fifo, sig);
printf("RUN\n");
}
} else if (key == 0xffc2) { // F5
mii->speed = 1.0;
printf("Speed Normal (1MHz)\n");
} else if (key == 0xffc3) { // F6
mii->speed = 4;
printf("Speed Fast (4MHz)\n");
}
}
if (key == 0xffeb || key == 0xffec) { // super left/right
key -= 0xffeb;
mii_bank_t *bank = &mii->bank[MII_BANK_MAIN];
uint8_t old = mii_bank_peek(bank, 0xc061 + key);
mii_bank_poke(bank, 0xc061 + key, down ? 0x80 : 0);
if (!!down != !!old) {
printf("Apple %s %s\n", key ? "Open" : "Close",
down ? "down" : "up");
}
}
}
}
in->keyboard.text_len = 0;
} else {
signal_t sig = {.cmd = -1 };
if (nk_input_is_key_pressed(in, NK_KEY_ENTER))
sig.data = 0x0d;
else if (nk_input_is_key_pressed(in, NK_KEY_BACKSPACE))
sig.data = 0x08;
else if (nk_input_is_key_pressed(in, NK_KEY_DEL))
sig.data = 0x7f;
else if (nk_input_is_key_pressed(in, NK_KEY_UP))
sig.data = 'K' - 'A' + 1;
else if (nk_input_is_key_pressed(in, NK_KEY_DOWN))
sig.data = 'J' - 'A' + 1;
else if (nk_input_is_key_pressed(in, NK_KEY_LEFT))
sig.data = 'H' - 'A' + 1;
else if (nk_input_is_key_pressed(in, NK_KEY_RIGHT))
sig.data = 'U' - 'A' + 1;
else if (nk_input_is_key_pressed(in, NK_KEY_ESCAPE))
sig.data = 0x1b;
if (sig.data) {
// signal_fifo_write(&signal_fifo, sig);
// printf("Key %d\n", sig.data);
mii_keypress(mii, sig.data);
}
}
}
void
mii_nuklear(
mii_t *mii,
struct nk_context *ctx)
{
if (mii->video.frame_count != mii->video.frame_drawn) {
mii->video.frame_drawn = mii->video.frame_count;
// update texture with new pixels; we only need 192 lines, the others
// are padding for the power of 2 texture
glBindTexture(GL_TEXTURE_2D, screen_texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
MII_VRAM_WIDTH,
MII_VIDEO_HEIGHT, GL_RGBA,
GL_UNSIGNED_BYTE,
mii->video.pixels);
}
mii_nuklear_handle_input(mii, ctx);
int height = 720 - 8;
int width = MII_VIDEO_WIDTH * (height / (float)MII_VIDEO_HEIGHT);
int xpos = 1280 / 2 - width / 2;
{
struct nk_style *s = &ctx->style;
nk_style_push_color(ctx, &s->window.background,
nk_rgba(0,0,0, 255));
nk_style_push_style_item(ctx, &s->window.fixed_background,
nk_style_item_color(nk_rgba(0,0,0, 255)));
if (nk_begin(ctx, "Apple //e Enhanced",
nk_rect(xpos, 0, width + 10, height + 20),
NK_WINDOW_NO_SCROLLBAR)) {
nk_layout_row_static(ctx, height, width, 1);
static int was_in = -1;
if (nk_widget_is_hovered(ctx) && mii->mouse.enabled) {
if (was_in != 1) {
was_in = 1;
ctx->input.mouse.grab = 1;
// printf("IN\n");
}
struct nk_rect bounds = nk_widget_bounds(ctx);
// normalize mouse coordinates
double x = ctx->input.mouse.pos.x - bounds.x;
double y = ctx->input.mouse.pos.y - bounds.y;
// get mouse button state
int button = ctx->input.mouse.buttons[NK_BUTTON_LEFT].down;
// clamp coordinates inside bounds
double vw = bounds.w;
double vh = bounds.h;
double mw = mii->mouse.max_x - mii->mouse.min_x;
double mh = mii->mouse.max_y - mii->mouse.min_y;
mii->mouse.x = mii->mouse.min_x + (x * mw / vw) + 0.5;
mii->mouse.y = mii->mouse.min_y + (y * mh / vh) + 0.5;
mii->mouse.button = button;
// printf("Mouse is %d %d\n", (int)mii->mouse.x, (int)mii->mouse.y);
} else {
if (was_in == 1) {
was_in = 0;
ctx->input.mouse.ungrab = 1;
// printf("OUT\n");
}
}
nk_image(ctx, screen_nk);
nk_end(ctx);
nk_style_pop_color(ctx);
nk_style_pop_style_item(ctx);
}
}
struct nk_rect bounds = { .x = 0, .y = 0, .w = xpos, .h = height };
// nk_window_get_bounds(ctx);
bool in = nk_input_is_mouse_hovering_rect(&ctx->input, bounds);
static bool menu_open = false;
if (in || menu_open) {
struct nk_style *s = &ctx->style;
nk_style_push_color(ctx, &s->window.background,
nk_rgba(175, 175, 175, 255));
nk_style_push_style_item(ctx, &s->window.fixed_background,
nk_style_item_color(nk_rgba(175, 175, 175, 255)));
if (nk_begin(ctx, "Left Bar",
nk_rect(0, 0, xpos, height + 20),
NK_WINDOW_NO_SCROLLBAR)) {
#if 0
nk_menubar_begin(ctx);
nk_layout_row_begin(ctx, NK_STATIC, 25, 2);
nk_layout_row_push(ctx, 60);
bool menu = false;
if (nk_menu_begin_label(ctx, "MII", NK_TEXT_LEFT,
nk_vec2(140, 200))) {
static size_t prog = 40;
static float slider = 0.5;
static int check = nk_true;
nk_layout_row_dynamic(ctx, 25, 1);
if (nk_menu_item_label(ctx, "Hide", NK_TEXT_LEFT))
;//show_menu = nk_false;
if (nk_menu_item_label(ctx, "About", NK_TEXT_LEFT))
;//show_app_about = nk_true;
nk_progress(ctx, &prog, 100, NK_MODIFIABLE);
nk_slider_float(ctx, 0.01, &slider, 1.0, 0.05);
nk_checkbox_label(ctx, "Mute", &check);
nk_menu_end(ctx);
menu = true;
}
menu_open = menu;
nk_menubar_end(ctx);
nk_layout_space_end(ctx);
#endif
int rw = xpos - 8;
// nk_layout_row_dynamic(ctx, 4, 1);
// nk_spacing(ctx, 1);
nk_layout_row_static(ctx, 30, rw, 1);
if (nk_button_label(ctx, "C-RESET"))
mii_reset(mii, false);
if (nk_button_label(ctx, "C-OA-RESET"))
mii_reset(mii, true);
// nk_layout_row_dynamic(ctx, 0, 1);
nk_layout_row_begin(ctx, NK_STATIC, 30, 2);
nk_layout_row_push(ctx, 20);
nk_label(ctx, "V:", NK_TEXT_LEFT);
nk_layout_row_push(ctx, rw - 20 - 4);
const char *video_modes[] = {
"Color",
"Green",
"Amber",
};
mii->video.color_mode = nk_combo(ctx,
video_modes, NK_LEN(video_modes),
mii->video.color_mode, 25, nk_vec2(80,200));
nk_layout_space_end(ctx);
nk_layout_row_begin(ctx, NK_STATIC, 30, 1);
nk_layout_row_push(ctx, rw);
const char *speed[] = {
"1 MHz",
"Slow",
"Fast",
};
nk_label(ctx, "Speed:", NK_TEXT_LEFT);
nk_layout_row_push(ctx, 100);
int spi = 0;
spi = nk_combo(ctx,
speed, NK_LEN(speed),
spi, 25, nk_vec2(80,200));
nk_layout_space_end(ctx);
}
nk_end(ctx);
nk_style_pop_color(ctx);
nk_style_pop_style_item(ctx);
}
if ( 0 && nk_begin(ctx, "Controls",
nk_rect(width, 0, 350, 10 + 192 * 3),
NK_WINDOW_NO_SCROLLBAR)) {
nk_layout_row_static(ctx, 30, 110, 2);
if (nk_button_label(ctx, "C-RESET"))
mii_reset(mii, false);
if (nk_button_label(ctx, "C-OA-RESET"))
mii_reset(mii, true);
#if 0
if (nk_button_label(ctx, "Screenshot")) {
stbi_write_png("screen.png",
MII_VIDEO_WIDTH, MII_VIDEO_HEIGHT, 4, mii->video.pixels,
MII_VRAM_WIDTH * 4);
printf("Screenshot taken\n");
}
#endif
nk_layout_row_dynamic(ctx, 30, 4);
nk_label(ctx, "Speed:", NK_TEXT_CENTERED);
if (nk_option_label(ctx, "Slow", mii->speed < 0.9)) mii->speed = 0.2;
if (nk_option_label(ctx, "1 MHz", mii->speed > .95 && mii->speed < 1.05)) mii->speed = 1.0;
if (nk_option_label(ctx, "Fast", mii->speed > 1.1 && mii->speed < 4.1)) mii->speed = 4.0;
nk_layout_row_dynamic(ctx, 30, 4);
nk_label(ctx, "Video:", NK_TEXT_CENTERED);
if (nk_option_label(ctx, "Color", mii->video.color_mode == MII_VIDEO_COLOR))
mii->video.color_mode = MII_VIDEO_COLOR;
if (nk_option_label(ctx, "Green", mii->video.color_mode == MII_VIDEO_GREEN))
mii->video.color_mode = MII_VIDEO_GREEN;
if (nk_option_label(ctx, "Amber", mii->video.color_mode == MII_VIDEO_AMBER))
mii->video.color_mode = MII_VIDEO_AMBER;
#if 0
nk_layout_row_dynamic(ctx, 20, 1);
nk_style_set_font(ctx, &nk_mono_font->handle);
struct nk_color save = ctx->style.window.background;
ctx->style.window.background = (struct nk_color)_NK_RGBA(0, 0, 0, 255);
struct nk_color fore = (struct nk_color)_NK_RGBA(0, 255, 0, 255);
char label[64];
mii_dasm_t _dasm = {};
mii_dasm_t *dasm = &_dasm;
mii_dasm_init(dasm, mii, 0);
dasm->mii = mii;
// display the last few cycles up to the PC
for (int di = 0; di < 3; di++) {
int pci = (mii_trace.idx + MII_PC_LOG_SIZE - 3 + di) % MII_PC_LOG_SIZE;
dasm->pc = mii_trace.log[pci];
mii_dasm_step(dasm, label, sizeof(label));
if (di == 2)
label[0] = '*';
nk_label_colored(ctx, label, NK_TEXT_LEFT, fore);
}
// and the (potentially) next instruction here
mii_dasm_step(dasm, label, sizeof(label));
nk_label_colored(ctx, label, NK_TEXT_LEFT, fore);
sprintf(label, "A:%02X X:%02X Y:%02X S:%02X",
mii->cpu.A, mii->cpu.X, mii->cpu.Y, mii->cpu.S);
nk_label_colored(ctx, label, NK_TEXT_CENTERED, fore);
char n[] = {'C','Z','I','D','B','V','N'};
label[0] = 0;
sprintf(label, "%04x ", mii->cpu.PC);
for (int i = 0; i < 7; i++)
sprintf(label + strlen(label), "%c%d ", n[i],
mii->cpu.P.P[i]);
nk_label_colored(ctx, label, NK_TEXT_CENTERED, fore);
ctx->style.window.background = save;
nk_style_set_font(ctx, &nk_main_font->handle);
#endif
nk_layout_row_static(ctx, 30, 30, 3);
if (nk_button_symbol(ctx, NK_SYMBOL_RECT_SOLID)) {
signal_fifo_write(&signal_fifo, (signal_t){.cmd = SIGNAL_STOP});
}
if (nk_button_symbol(ctx, NK_SYMBOL_PLUS)) {
signal_fifo_write(&signal_fifo, (signal_t){.cmd = SIGNAL_STEP});
}
if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_RIGHT)) {
signal_fifo_write(&signal_fifo, (signal_t){.cmd = SIGNAL_RUN});
}
nk_layout_row_dynamic(ctx, 20, 3);
{
char label[32];
sprintf(label, "CPU: %.1fMHz", mii->speed_current);
nk_label(ctx, label, NK_TEXT_CENTERED);
}
#if 0
nk_layout_row_dynamic(ctx, 20, 2);
nk_checkbox_label(ctx, "Disk II Debug", &disk2_debug);
nk_checkbox_label(ctx, "Slowmo", &mii_SLOW);
// nk_checkbox_label(ctx, "Zero Page", &show_zero_page);
#endif
nk_end(ctx);
}
if (show_zero_page) {
if (nk_begin(ctx, "Zero Page",
nk_rect(0, 10 + 192 * 3, 600, 10 + 16 * 20),
0 )) {
nk_layout_row_dynamic(ctx, 20, 1);
nk_style_set_font(ctx, &nk_mono_font->handle);
struct nk_color fore = (struct nk_color)_NK_RGBA(0, 255, 0, 255);
uint8_t *zp = mii->bank[0].mem;
char label[128];
for (int ri = 0; ri < (256 / 16); ri++) {
sprintf(label, "%02x: ", ri * 16);
for (int ci = 0; ci < 16; ci++) {
sprintf(label + strlen(label), "%02X ",
zp[ri * 16 + ci]);
}
nk_label_colored(ctx, label, NK_TEXT_LEFT, fore);
}
nk_style_set_font(ctx, &nk_main_font->handle);
nk_end(ctx);
}
}
}

View File

@ -0,0 +1,22 @@
/*
* mii_stb.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#define NK_BUTTON_TRIGGER_ON_RELEASE
#define NK_IMPLEMENTATION
#include "nuklear.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

30034
nuklear/nuklear.h Normal file

File diff suppressed because it is too large Load Diff

756
nuklear/nuklear_xlib_gl3.h Normal file
View File

@ -0,0 +1,756 @@
/*
* Nuklear - v1.17 - public domain
* no warrenty implied; use at your own risk.
* authored from 2015-2016 by Micha Mettke
*/
/*
* ==============================================================
*
* API
*
* ===============================================================
*/
#ifndef NK_XLIB_GL3_H_
#define NK_XLIB_GL3_H_
#include <X11/Xlib.h>
NK_API struct nk_context* nk_x11_init(Display *dpy, Window win);
NK_API void nk_x11_font_stash_begin(struct nk_font_atlas **atlas);
NK_API void nk_x11_font_stash_end(void);
NK_API int nk_x11_handle_event(XEvent *evt);
NK_API void nk_x11_render(enum nk_anti_aliasing, int max_vertex_buffer, int max_element_buffer);
NK_API void nk_x11_shutdown(void);
NK_API int nk_x11_device_create(void);
NK_API void nk_x11_device_destroy(void);
#endif
/*
* ==============================================================
*
* IMPLEMENTATION
*
* ===============================================================
*/
#ifdef NK_XLIB_GL3_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/Xlocale.h>
#include <GL/gl.h>
#include <GL/glx.h>
#ifndef NK_X11_DOUBLE_CLICK_LO
#define NK_X11_DOUBLE_CLICK_LO 20
#endif
#ifndef NK_X11_DOUBLE_CLICK_HI
#define NK_X11_DOUBLE_CLICK_HI 200
#endif
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
#include <GL/glxext.h>
/* GL_ARB_vertex_buffer_object */
typedef void(*nkglGenBuffers)(GLsizei, GLuint*);
typedef void(*nkglBindBuffer)(GLenum, GLuint);
typedef void(*nkglBufferData)(GLenum, GLsizeiptr, const GLvoid*, GLenum);
typedef void(*nkglBufferSubData)(GLenum, GLintptr, GLsizeiptr, const GLvoid*);
typedef void*(*nkglMapBuffer)(GLenum, GLenum);
typedef GLboolean(*nkglUnmapBuffer)(GLenum);
typedef void(*nkglDeleteBuffers)(GLsizei, GLuint*);
/* GL_ARB_vertex_array_object */
typedef void (*nkglGenVertexArrays)(GLsizei, GLuint*);
typedef void (*nkglBindVertexArray)(GLuint);
typedef void (*nkglDeleteVertexArrays)(GLsizei, const GLuint*);
/* GL_ARB_vertex_program / GL_ARB_fragment_program */
typedef void(*nkglVertexAttribPointer)(GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid*);
typedef void(*nkglEnableVertexAttribArray)(GLuint);
typedef void(*nkglDisableVertexAttribArray)(GLuint);
/* GL_ARB_framebuffer_object */
typedef void(*nkglGenerateMipmap)(GLenum target);
/* GLSL/OpenGL 2.0 core */
typedef GLuint(*nkglCreateShader)(GLenum);
typedef void(*nkglShaderSource)(GLuint, GLsizei, const GLchar**, const GLint*);
typedef void(*nkglCompileShader)(GLuint);
typedef void(*nkglGetShaderiv)(GLuint, GLenum, GLint*);
typedef void(*nkglGetShaderInfoLog)(GLuint, GLsizei, GLsizei*, GLchar*);
typedef void(*nkglDeleteShader)(GLuint);
typedef GLuint(*nkglCreateProgram)(void);
typedef void(*nkglAttachShader)(GLuint, GLuint);
typedef void(*nkglDetachShader)(GLuint, GLuint);
typedef void(*nkglLinkProgram)(GLuint);
typedef void(*nkglUseProgram)(GLuint);
typedef void(*nkglGetProgramiv)(GLuint, GLenum, GLint*);
typedef void(*nkglGetProgramInfoLog)(GLuint, GLsizei, GLsizei*, GLchar*);
typedef void(*nkglDeleteProgram)(GLuint);
typedef GLint(*nkglGetUniformLocation)(GLuint, const GLchar*);
typedef GLint(*nkglGetAttribLocation)(GLuint, const GLchar*);
typedef void(*nkglUniform1i)(GLint, GLint);
typedef void(*nkglUniform1f)(GLint, GLfloat);
typedef void(*nkglUniformMatrix3fv)(GLint, GLsizei, GLboolean, const GLfloat*);
typedef void(*nkglUniformMatrix4fv)(GLint, GLsizei, GLboolean, const GLfloat*);
static nkglGenBuffers glGenBuffers;
static nkglBindBuffer glBindBuffer;
static nkglBufferData glBufferData;
static nkglBufferSubData glBufferSubData;
static nkglMapBuffer glMapBuffer;
static nkglUnmapBuffer glUnmapBuffer;
static nkglDeleteBuffers glDeleteBuffers;
static nkglGenVertexArrays glGenVertexArrays;
static nkglBindVertexArray glBindVertexArray;
static nkglDeleteVertexArrays glDeleteVertexArrays;
static nkglVertexAttribPointer glVertexAttribPointer;
static nkglEnableVertexAttribArray glEnableVertexAttribArray;
static nkglDisableVertexAttribArray glDisableVertexAttribArray;
static nkglGenerateMipmap glGenerateMipmap;
static nkglCreateShader glCreateShader;
static nkglShaderSource glShaderSource;
static nkglCompileShader glCompileShader;
static nkglGetShaderiv glGetShaderiv;
static nkglGetShaderInfoLog glGetShaderInfoLog;
static nkglDeleteShader glDeleteShader;
static nkglCreateProgram glCreateProgram;
static nkglAttachShader glAttachShader;
static nkglDetachShader glDetachShader;
static nkglLinkProgram glLinkProgram;
static nkglUseProgram glUseProgram;
static nkglGetProgramiv glGetProgramiv;
static nkglGetProgramInfoLog glGetProgramInfoLog;
static nkglDeleteProgram glDeleteProgram;
static nkglGetUniformLocation glGetUniformLocation;
static nkglGetAttribLocation glGetAttribLocation;
static nkglUniform1i glUniform1i;
static nkglUniform1f glUniform1f;
static nkglUniformMatrix3fv glUniformMatrix3fv;
static nkglUniformMatrix4fv glUniformMatrix4fv;
enum graphics_card_vendors {
VENDOR_UNKNOWN,
VENDOR_NVIDIA,
VENDOR_AMD,
VENDOR_INTEL
};
struct opengl_info {
/* info */
const char *vendor_str;
const char *version_str;
const char *extensions_str;
const char *renderer_str;
const char *glsl_version_str;
enum graphics_card_vendors vendor;
/* version */
float version;
int major_version;
int minor_version;
/* extensions */
int glsl_available;
int vertex_buffer_obj_available;
int vertex_array_obj_available;
int map_buffer_range_available;
int fragment_program_available;
int frame_buffer_object_available;
};
#endif
struct nk_x11_vertex {
float position[2];
float uv[2];
nk_byte col[4];
};
struct nk_x11_device {
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
struct opengl_info info;
#endif
struct nk_buffer cmds;
struct nk_draw_null_texture tex_null;
GLuint vbo, vao, ebo;
GLuint prog;
GLuint vert_shdr;
GLuint frag_shdr;
GLint attrib_pos;
GLint attrib_uv;
GLint attrib_col;
GLint uniform_tex;
GLint uniform_proj;
GLuint font_tex;
};
static struct nk_x11 {
struct nk_x11_device ogl;
struct nk_context ctx;
struct nk_font_atlas atlas;
Cursor cursor;
Display *dpy;
Window win;
long last_button_click;
} x11;
#ifdef __APPLE__
#define NK_SHADER_VERSION "#version 150\n"
#else
#define NK_SHADER_VERSION "#version 300 es\n"
#endif
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
#include <GL/glx.h>
NK_INTERN long
nk_timestamp(void)
{
struct timeval tv;
if (gettimeofday(&tv, NULL) < 0) return 0;
return (long)((long)tv.tv_sec * 1000 + (long)tv.tv_usec/1000);
}
NK_INTERN int
nk_x11_stricmpn(const char *a, const char *b, int len)
{
int i = 0;
for (i = 0; i < len && a[i] && b[i]; ++i)
if (a[i] != b[i]) return 1;
if (i != len) return 1;
return 0;
}
NK_INTERN int
nk_x11_check_extension(struct opengl_info *GL, const char *ext)
{
const char *start, *where, *term;
where = strchr(ext, ' ');
if (where || *ext == '\0')
return nk_false;
for (start = GL->extensions_str;;) {
where = strstr((const char*)start, ext);
if (!where) break;
term = where + strlen(ext);
if (where == start || *(where - 1) == ' ') {
if (*term == ' ' || *term == '\0')
return nk_true;
}
start = term;
}
return nk_false;
}
#define GL_EXT(name) (nk##name)nk_gl_ext(#name)
NK_INTERN __GLXextFuncPtr
nk_gl_ext(const char *name)
{
__GLXextFuncPtr func;
func = glXGetProcAddress((const GLubyte*)name);
if (!func) {
fprintf(stdout, "[GL]: failed to load extension: %s", name);
return NULL;
}
return func;
}
NK_INTERN int
nk_load_opengl(struct opengl_info *gl)
{
int failed = nk_false;
gl->version_str = (const char*)glGetString(GL_VERSION);
glGetIntegerv(GL_MAJOR_VERSION, &gl->major_version);
glGetIntegerv(GL_MINOR_VERSION, &gl->minor_version);
if (gl->major_version < 2) {
fprintf(stderr, "[GL]: Graphics card does not fulfill minimum OpenGL 2.0 support\n");
return 0;
}
gl->version = (float)gl->major_version + (float)gl->minor_version * 0.1f;
gl->renderer_str = (const char*)glGetString(GL_RENDERER);
gl->extensions_str = (const char*)glGetString(GL_EXTENSIONS);
gl->glsl_version_str = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
gl->vendor_str = (const char*)glGetString(GL_VENDOR);
if (!nk_x11_stricmpn(gl->vendor_str, "ATI", 4) ||
!nk_x11_stricmpn(gl->vendor_str, "AMD", 4))
gl->vendor = VENDOR_AMD;
else if (!nk_x11_stricmpn(gl->vendor_str, "NVIDIA", 6))
gl->vendor = VENDOR_NVIDIA;
else if (!nk_x11_stricmpn(gl->vendor_str, "Intel", 5))
gl->vendor = VENDOR_INTEL;
else gl->vendor = VENDOR_UNKNOWN;
/* Extensions */
gl->glsl_available = (gl->version >= 2.0f);
if (gl->glsl_available) {
/* GLSL core in OpenGL > 2 */
glCreateShader = GL_EXT(glCreateShader);
glShaderSource = GL_EXT(glShaderSource);
glCompileShader = GL_EXT(glCompileShader);
glGetShaderiv = GL_EXT(glGetShaderiv);
glGetShaderInfoLog = GL_EXT(glGetShaderInfoLog);
glDeleteShader = GL_EXT(glDeleteShader);
glCreateProgram = GL_EXT(glCreateProgram);
glAttachShader = GL_EXT(glAttachShader);
glDetachShader = GL_EXT(glDetachShader);
glLinkProgram = GL_EXT(glLinkProgram);
glUseProgram = GL_EXT(glUseProgram);
glGetProgramiv = GL_EXT(glGetProgramiv);
glGetProgramInfoLog = GL_EXT(glGetProgramInfoLog);
glDeleteProgram = GL_EXT(glDeleteProgram);
glGetUniformLocation = GL_EXT(glGetUniformLocation);
glGetAttribLocation = GL_EXT(glGetAttribLocation);
glUniform1i = GL_EXT(glUniform1i);
glUniform1f = GL_EXT(glUniform1f);
glUniformMatrix3fv = GL_EXT(glUniformMatrix3fv);
glUniformMatrix4fv = GL_EXT(glUniformMatrix4fv);
}
gl->vertex_buffer_obj_available = nk_x11_check_extension(gl, "GL_ARB_vertex_buffer_object");
if (gl->vertex_buffer_obj_available) {
/* GL_ARB_vertex_buffer_object */
glGenBuffers = GL_EXT(glGenBuffers);
glBindBuffer = GL_EXT(glBindBuffer);
glBufferData = GL_EXT(glBufferData);
glBufferSubData = GL_EXT(glBufferSubData);
glMapBuffer = GL_EXT(glMapBuffer);
glUnmapBuffer = GL_EXT(glUnmapBuffer);
glDeleteBuffers = GL_EXT(glDeleteBuffers);
}
gl->fragment_program_available = nk_x11_check_extension(gl, "GL_ARB_fragment_program");
if (gl->fragment_program_available) {
/* GL_ARB_vertex_program / GL_ARB_fragment_program */
glVertexAttribPointer = GL_EXT(glVertexAttribPointer);
glEnableVertexAttribArray = GL_EXT(glEnableVertexAttribArray);
glDisableVertexAttribArray = GL_EXT(glDisableVertexAttribArray);
}
gl->vertex_array_obj_available = nk_x11_check_extension(gl, "GL_ARB_vertex_array_object");
if (gl->vertex_array_obj_available) {
/* GL_ARB_vertex_array_object */
glGenVertexArrays = GL_EXT(glGenVertexArrays);
glBindVertexArray = GL_EXT(glBindVertexArray);
glDeleteVertexArrays = GL_EXT(glDeleteVertexArrays);
}
gl->frame_buffer_object_available = nk_x11_check_extension(gl, "GL_ARB_framebuffer_object");
if (gl->frame_buffer_object_available) {
/* GL_ARB_framebuffer_object */
glGenerateMipmap = GL_EXT(glGenerateMipmap);
}
if (!gl->vertex_buffer_obj_available) {
fprintf(stdout, "[GL] Error: GL_ARB_vertex_buffer_object is not available!\n");
failed = nk_true;
}
if (!gl->fragment_program_available) {
fprintf(stdout, "[GL] Error: GL_ARB_fragment_program is not available!\n");
failed = nk_true;
}
if (!gl->vertex_array_obj_available) {
fprintf(stdout, "[GL] Error: GL_ARB_vertex_array_object is not available!\n");
failed = nk_true;
}
if (!gl->frame_buffer_object_available) {
fprintf(stdout, "[GL] Error: GL_ARB_framebuffer_object is not available!\n");
failed = nk_true;
}
return !failed;
}
#endif
NK_API int
nk_x11_device_create(void)
{
GLint status;
static const GLchar *vertex_shader =
NK_SHADER_VERSION
"uniform mat4 ProjMtx;\n"
"in vec2 Position;\n"
"in vec2 TexCoord;\n"
"in vec4 Color;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main() {\n"
" Frag_UV = TexCoord;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n"
"}\n";
static const GLchar *fragment_shader =
NK_SHADER_VERSION
"precision mediump float;\n"
"uniform sampler2D Texture;\n"
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"out vec4 Out_Color;\n"
"void main(){\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
"}\n";
struct nk_x11_device *dev = &x11.ogl;
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
if (!nk_load_opengl(&dev->info)) return 0;
#endif
nk_buffer_init_default(&dev->cmds);
dev->prog = glCreateProgram();
dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER);
dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(dev->vert_shdr, 1, &vertex_shader, 0);
glShaderSource(dev->frag_shdr, 1, &fragment_shader, 0);
glCompileShader(dev->vert_shdr);
glCompileShader(dev->frag_shdr);
glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status);
assert(status == GL_TRUE);
glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status);
assert(status == GL_TRUE);
glAttachShader(dev->prog, dev->vert_shdr);
glAttachShader(dev->prog, dev->frag_shdr);
glLinkProgram(dev->prog);
glGetProgramiv(dev->prog, GL_LINK_STATUS, &status);
assert(status == GL_TRUE);
dev->uniform_tex = glGetUniformLocation(dev->prog, "Texture");
dev->uniform_proj = glGetUniformLocation(dev->prog, "ProjMtx");
dev->attrib_pos = glGetAttribLocation(dev->prog, "Position");
dev->attrib_uv = glGetAttribLocation(dev->prog, "TexCoord");
dev->attrib_col = glGetAttribLocation(dev->prog, "Color");
{
/* buffer setup */
GLsizei vs = sizeof(struct nk_x11_vertex);
size_t vp = offsetof(struct nk_x11_vertex, position);
size_t vt = offsetof(struct nk_x11_vertex, uv);
size_t vc = offsetof(struct nk_x11_vertex, col);
glGenBuffers(1, &dev->vbo);
glGenBuffers(1, &dev->ebo);
glGenVertexArrays(1, &dev->vao);
glBindVertexArray(dev->vao);
glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
glEnableVertexAttribArray((GLuint)dev->attrib_pos);
glEnableVertexAttribArray((GLuint)dev->attrib_uv);
glEnableVertexAttribArray((GLuint)dev->attrib_col);
glVertexAttribPointer((GLuint)dev->attrib_pos, 2, GL_FLOAT, GL_FALSE, vs, (void*)vp);
glVertexAttribPointer((GLuint)dev->attrib_uv, 2, GL_FLOAT, GL_FALSE, vs, (void*)vt);
glVertexAttribPointer((GLuint)dev->attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, vs, (void*)vc);
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
return 1;
}
NK_INTERN void
nk_x11_device_upload_atlas(const void *image, int width, int height)
{
struct nk_x11_device *dev = &x11.ogl;
glGenTextures(1, &dev->font_tex);
glBindTexture(GL_TEXTURE_2D, dev->font_tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, image);
}
NK_API void
nk_x11_device_destroy(void)
{
struct nk_x11_device *dev = &x11.ogl;
glDetachShader(dev->prog, dev->vert_shdr);
glDetachShader(dev->prog, dev->frag_shdr);
glDeleteShader(dev->vert_shdr);
glDeleteShader(dev->frag_shdr);
glDeleteProgram(dev->prog);
glDeleteTextures(1, &dev->font_tex);
glDeleteBuffers(1, &dev->vbo);
glDeleteBuffers(1, &dev->ebo);
nk_buffer_free(&dev->cmds);
}
NK_API void
nk_x11_render(enum nk_anti_aliasing AA, int max_vertex_buffer, int max_element_buffer)
{
int width, height;
XWindowAttributes attr;
struct nk_x11_device *dev = &x11.ogl;
GLfloat ortho[4][4] = {
{2.0f, 0.0f, 0.0f, 0.0f},
{0.0f,-2.0f, 0.0f, 0.0f},
{0.0f, 0.0f,-1.0f, 0.0f},
{-1.0f,1.0f, 0.0f, 1.0f},
};
XGetWindowAttributes(x11.dpy, x11.win, &attr);
width = attr.width;
height = attr.height;
ortho[0][0] /= (GLfloat)width;
ortho[1][1] /= (GLfloat)height;
/* setup global state */
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_SCISSOR_TEST);
glActiveTexture(GL_TEXTURE0);
/* setup program */
glUseProgram(dev->prog);
glUniform1i(dev->uniform_tex, 0);
glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]);
glViewport(0,0,(GLsizei)width,(GLsizei)height);
{
/* convert from command queue into draw list and draw to screen */
const struct nk_draw_command *cmd;
void *vertices, *elements;
const nk_draw_index *offset = NULL;
struct nk_buffer vbuf, ebuf;
/* allocate vertex and element buffer */
glBindVertexArray(dev->vao);
glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
glBufferData(GL_ARRAY_BUFFER, max_vertex_buffer, NULL, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_element_buffer, NULL, GL_STREAM_DRAW);
/* load draw vertices & elements directly into vertex + element buffer */
vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
{
/* fill convert configuration */
struct nk_convert_config config;
static const struct nk_draw_vertex_layout_element vertex_layout[] = {
{NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_x11_vertex, position)},
{NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_x11_vertex, uv)},
{NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct nk_x11_vertex, col)},
{NK_VERTEX_LAYOUT_END}
};
memset(&config, 0, sizeof(config));
config.vertex_layout = vertex_layout;
config.vertex_size = sizeof(struct nk_x11_vertex);
config.vertex_alignment = NK_ALIGNOF(struct nk_x11_vertex);
config.tex_null = dev->tex_null;
config.circle_segment_count = 22;
config.curve_segment_count = 22;
config.arc_segment_count = 22;
config.global_alpha = 1.0f;
config.shape_AA = AA;
config.line_AA = AA;
/* setup buffers to load vertices and elements */
nk_buffer_init_fixed(&vbuf, vertices, (size_t)max_vertex_buffer);
nk_buffer_init_fixed(&ebuf, elements, (size_t)max_element_buffer);
nk_convert(&x11.ctx, &dev->cmds, &vbuf, &ebuf, &config);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
/* iterate over and execute each draw command */
nk_draw_foreach(cmd, &x11.ctx, &dev->cmds)
{
if (!cmd->elem_count) continue;
// GLint unit = cmd->texture.id
glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id);
glScissor(
(GLint)(cmd->clip_rect.x),
(GLint)((height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h))),
(GLint)(cmd->clip_rect.w),
(GLint)(cmd->clip_rect.h));
glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset);
offset += cmd->elem_count;
}
nk_clear(&x11.ctx);
nk_buffer_clear(&dev->cmds);
}
/* default OpenGL state */
glUseProgram(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
}
NK_API void
nk_x11_font_stash_begin(struct nk_font_atlas **atlas)
{
nk_font_atlas_init_default(&x11.atlas);
nk_font_atlas_begin(&x11.atlas);
*atlas = &x11.atlas;
}
NK_API void
nk_x11_font_stash_end(void)
{
const void *image; int w, h;
image = nk_font_atlas_bake(&x11.atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
nk_x11_device_upload_atlas(image, w, h);
nk_font_atlas_end(&x11.atlas, nk_handle_id((int)x11.ogl.font_tex), &x11.ogl.tex_null);
if (x11.atlas.default_font)
nk_style_set_font(&x11.ctx, &x11.atlas.default_font->handle);
}
NK_API int
nk_x11_handle_event(XEvent *evt)
{
struct nk_context *ctx = &x11.ctx;
/* optional grabbing behavior */
if (ctx->input.mouse.grab) {
XDefineCursor(x11.dpy, x11.win, x11.cursor);
ctx->input.mouse.grab = 0;
} else if (ctx->input.mouse.ungrab) {
XWarpPointer(x11.dpy, None, x11.win, 0, 0, 0, 0,
(int)ctx->input.mouse.pos.x, (int)ctx->input.mouse.pos.y);
XUndefineCursor(x11.dpy, x11.win);
ctx->input.mouse.ungrab = 0;
}
if (evt->type == KeyPress || evt->type == KeyRelease)
{
/* Key handler */
int ret, down = (evt->type == KeyPress);
KeySym *code = XGetKeyboardMapping(x11.dpy, (KeyCode)evt->xkey.keycode, 1, &ret);
if (*code == XK_Shift_L || *code == XK_Shift_R) nk_input_key(ctx, NK_KEY_SHIFT, down);
else if (*code == XK_Control_L || *code == XK_Control_R) nk_input_key(ctx, NK_KEY_CTRL, down);
else if (*code == XK_Alt_L || *code == XK_Alt_R) nk_input_key(ctx, NK_KEY_ALT, down);
else if (*code == XK_Delete) nk_input_key(ctx, NK_KEY_DEL, down);
else if (*code == XK_Return) nk_input_key(ctx, NK_KEY_ENTER, down);
else if (*code == XK_Tab) nk_input_key(ctx, NK_KEY_TAB, down);
else if (*code == XK_Left) nk_input_key(ctx, NK_KEY_LEFT, down);
else if (*code == XK_Right) nk_input_key(ctx, NK_KEY_RIGHT, down);
else if (*code == XK_Up) nk_input_key(ctx, NK_KEY_UP, down);
else if (*code == XK_Down) nk_input_key(ctx, NK_KEY_DOWN, down);
else if (*code == XK_BackSpace) nk_input_key(ctx, NK_KEY_BACKSPACE, down);
else if (*code == XK_Escape) nk_input_key(ctx, NK_KEY_ESCAPE, down);
// else if (*code == XK_space && !down) nk_input_char(ctx, ' ');
else if (*code == XK_Page_Up) nk_input_key(ctx, NK_KEY_SCROLL_UP, down);
else if (*code == XK_Page_Down) nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down);
else if (*code == XK_Home) {
nk_input_key(ctx, NK_KEY_TEXT_START, down);
nk_input_key(ctx, NK_KEY_SCROLL_START, down);
} else if (*code == XK_End) {
nk_input_key(ctx, NK_KEY_TEXT_END, down);
nk_input_key(ctx, NK_KEY_SCROLL_END, down);
} else {
if (*code == 'c' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_COPY, down);
else if (*code == 'v' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_PASTE, down);
else if (*code == 'x' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_CUT, down);
else if (*code == 'z' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_UNDO, down);
else if (*code == 'r' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_REDO, down);
else if (*code == XK_Left && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, down);
else if (*code == XK_Right && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, down);
else if (*code == 'b' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_LINE_START, down);
else if (*code == 'e' && (evt->xkey.state & ControlMask))
nk_input_key(ctx, NK_KEY_TEXT_LINE_END, down);
else {
if (*code == 'i')
nk_input_key(ctx, NK_KEY_TEXT_INSERT_MODE, down);
else if (*code == 'r')
nk_input_key(ctx, NK_KEY_TEXT_REPLACE_MODE, down);
char buf[32];
KeySym keysym = 0;
if (XLookupString((XKeyEvent*)evt, buf, 32, &keysym, NULL) != NoSymbol) {
if (down)
nk_input_glyph(ctx, buf);
} else {
// printf("Unknown key %lx, pass on RAW %d\n", *code,
// ctx->input.keyboard.text_len);
uint32_t *d = (uint32_t *) ctx->input.keyboard.text;
d[ctx->input.keyboard.text_len / 4] =
(*code << 16) | !!down;
ctx->input.keyboard.text_len += 4;
}
}
}
XFree(code);
return 1;
} else if (evt->type == ButtonPress || evt->type == ButtonRelease) {
/* Button handler */
int down = (evt->type == ButtonPress);
const int x = evt->xbutton.x, y = evt->xbutton.y;
if (evt->xbutton.button == Button1) {
if (down) { /* Double-Click Button handler */
long dt = nk_timestamp() - x11.last_button_click;
if (dt > NK_X11_DOUBLE_CLICK_LO && dt < NK_X11_DOUBLE_CLICK_HI)
nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y, nk_true);
x11.last_button_click = nk_timestamp();
} else nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y, nk_false);
nk_input_button(ctx, NK_BUTTON_LEFT, x, y, down);
} else if (evt->xbutton.button == Button2)
nk_input_button(ctx, NK_BUTTON_MIDDLE, x, y, down);
else if (evt->xbutton.button == Button3)
nk_input_button(ctx, NK_BUTTON_RIGHT, x, y, down);
else if (evt->xbutton.button == Button4)
nk_input_scroll(ctx, nk_vec2(0,1.0f));
else if (evt->xbutton.button == Button5)
nk_input_scroll(ctx, nk_vec2(0,-1.0f));
else return 0;
return 1;
} else if (evt->type == MotionNotify) {
/* Mouse motion handler */
const int x = evt->xmotion.x, y = evt->xmotion.y;
nk_input_motion(ctx, x, y);
if (ctx->input.mouse.grabbed) {
ctx->input.mouse.pos.x = ctx->input.mouse.prev.x;
ctx->input.mouse.pos.y = ctx->input.mouse.prev.y;
XWarpPointer(x11.dpy, None, x11.win, 0, 0, 0, 0, (int)ctx->input.mouse.pos.x, (int)ctx->input.mouse.pos.y);
}
return 1;
} else if (evt->type == KeymapNotify) {
XRefreshKeyboardMapping(&evt->xmapping);
return 1;
}
return 0;
}
NK_API struct nk_context*
nk_x11_init(Display *dpy, Window win)
{
if (!setlocale(LC_ALL,"")) return 0;
if (!XSupportsLocale()) return 0;
if (!XSetLocaleModifiers("@im=none")) return 0;
if (!nk_x11_device_create()) return 0;
x11.dpy = dpy;
x11.win = win;
/* create invisible cursor */
{static XColor dummy; char data[1] = {0};
Pixmap blank = XCreateBitmapFromData(dpy, win, data, 1, 1);
if (blank == None) return 0;
x11.cursor = XCreatePixmapCursor(dpy, blank, blank, &dummy, &dummy, 0, 0);
XFreePixmap(dpy, blank);}
nk_init_default(&x11.ctx, 0);
return &x11.ctx;
}
NK_API void
nk_x11_shutdown(void)
{
nk_font_atlas_clear(&x11.atlas);
nk_free(&x11.ctx);
nk_x11_device_destroy();
XFreeCursor(x11.dpy, x11.cursor);
memset(&x11, 0, sizeof(x11));
}
#endif

379
src/drivers/mii_disk2.c Normal file
View File

@ -0,0 +1,379 @@
/*
* mii_disk2.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
// Shamelesly lifted from periph/disk2.c
//
// Copyright (c) 2023 Micah John Cowan.
// This code is licensed under the MIT license.
// See the accompanying LICENSE file for details.
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include "mii.h"
#include "mii_bank.h"
#include "mii_disk2.h"
#include "mii_rom_disk2.h"
#include "mii_disk_format.h"
//#define DISK_DEBUG
#ifdef DISK_DEBUG
# define D2DBG(...) do { \
if (c->pr_count == 1) \
fprintf(stderr, __VA_ARGS__); \
} while(0)
#else
# define D2DBG(...)
#endif
typedef struct mii_card_disk2_t {
uint32_t timer;
bool motor_on;
bool drive_two;
bool write_mode;
union {
struct {
DiskFormatDesc disk1;
DiskFormatDesc disk2;
};
DiskFormatDesc disk[2];
};
uint8_t data_register; // only used for write
bool steppers[4];
int cog1;
int cog2;
// int pr_count; // debug print
} mii_card_disk2_t;
//static const size_t dsk_disksz = 143360;
int disk2_debug = 0;
static DiskFormatDesc *
active_disk_obj(
mii_card_disk2_t *c)
{
return c->drive_two ? &c->disk2 : &c->disk1;
}
#if 0
bool
drive_spinning(
mii_card_disk2_t *c)
{
return c->motor_on;
}
int
active_disk(
mii_card_disk2_t *c)
{
return c->drive_two ? 2 : 1;
}
int
eject_disk(
mii_card_disk2_t *c,
int drive)
{
if (c->motor_on && active_disk(c) == drive) {
return -1;
}
init(c); // to make sure
if (drive == 1) {
c->disk1.eject(&c->disk1);
c->disk1 = disk_format_load(NULL);
} else if (drive == 2) {
c->disk2.eject(&c->disk2);
c->disk2 = disk_format_load(NULL);
}
return 0;
}
int
insert_disk(
mii_card_disk2_t *c,
int drive,
const char *path)
{
int err = eject_disk(c, drive);
if (err != 0) return err;
// Make sure we're inserted to slot 6
// XXX should check for error/distinguish if we're already in that slot
(void) periph_slot_reg(6, &disk2card);
if (err) {
return -1; // XXX should be a distinguishable err code
}
if (drive == 1) {
c->disk1 = disk_format_load(path);
} else if (drive == 2) {
c->disk2 = disk_format_load(path);
}
return 0;
}
#endif
// NOTE: cog "left" and "right" refers only to the number line,
// and not the physical cog or track head movement.
static bool
cogleft(
mii_card_disk2_t *c,
int *cog)
{
int cl = ((*cog) + 3) % 4; // position to the "left" of cog
return (!c->steppers[(*cog)] && c->steppers[cl]);
}
// NOTE: cog "left" and "right" refers only to the number line,
// and not the physical cog or track head movement.
static bool
cogright(
mii_card_disk2_t *c,
int *cog)
{
int cr = ((*cog) + 1) % 4; // position to the "right" of cog
return (!c->steppers[(*cog)] && c->steppers[cr]);
}
static int *
active_cog(
mii_card_disk2_t *c)
{
return c->drive_two? &c->cog2 : &c->cog1;
}
static void
adjust_track(
mii_card_disk2_t *c)
{
DiskFormatDesc *disk = active_disk_obj(c);
int *cog = active_cog(c);
D2DBG("halftrack: ");
if (cogleft(c, cog)) {
*cog = ((*cog) + 3) % 4;
if (disk->halftrack > 0) --disk->halftrack;
D2DBG("dec to %d", disk->halftrack);
} else if (cogright(c, cog)) {
*cog = ((*cog) + 1) % 4;
if (disk->halftrack < 69) ++disk->halftrack;
D2DBG("inc to %d", disk->halftrack);
} else {
D2DBG("no change (%d)", disk->halftrack);
}
}
static void
stepper_motor(
mii_card_disk2_t *c,
uint8_t psw)
{
uint8_t phase = psw >> 1;
bool onoff = (psw & 1) != 0;
D2DBG("phase %d %s ", (int)phase, onoff? "on, " : "off,");
c->steppers[phase] = onoff;
adjust_track(c);
}
static inline uint8_t encode4x4(mii_card_disk2_t *c, uint8_t orig)
{
return (orig | 0xAA);
}
static void turn_off_motor(mii_card_disk2_t *c)
{
c->motor_on = false;
DiskFormatDesc *disk = active_disk_obj(c);
disk->spin(disk, false);
// event_fire_disk_active(0);
}
static int
_mii_disk2_init(
mii_t * mii,
struct mii_slot_t *slot )
{
mii_card_disk2_t *c = calloc(1, sizeof(*c));
slot->drv_priv = c;
c->disk1 = disk_format_load(NULL);
c->disk2 = disk_format_load(NULL);
printf("%s loading in slot %d\n", __func__, slot->id + 1);
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr, mii_rom_disk2, 256);
return 0;
}
static void
_mii_disk2_run(
mii_t * mii,
struct mii_slot_t *slot)
{
mii_card_disk2_t *c = slot->drv_priv;
if (c->timer && c->timer <= mii->video.frame_count) {
printf("%s turning off motor\n", __func__);
c->timer = 0;
turn_off_motor(c);
}
}
static uint8_t
_mii_disk2_access(
mii_t * mii, struct mii_slot_t *slot,
uint16_t addr, uint8_t byte, bool write)
{
mii_card_disk2_t *c = slot->drv_priv;
uint8_t ret = 0;
int psw = addr & 0x0F;
if (c->timer)
c->timer = mii->video.frame_count + 60;
uint8_t last = -1;
if (disk2_debug && last != psw + (write? 0x10 : 0))
printf("disk sw $%02X, PC = $%04X\n", psw, mii->cpu.PC);
last = psw + (write? 0x10 : 0);
if (write)
c->data_register = byte; // ANY write sets data register
if (psw < 8) {
stepper_motor(c, psw);
} else switch (psw) {
case 0x08:
if (c->motor_on) {
c->timer = mii->video.frame_count + 60;
//frame_timer(60, turn_off_motor);
}
break;
case 0x09: {
// frame_timer_cancel(turn_off_motor);
c->timer = 0;
c->motor_on = true;
DiskFormatDesc *disk = active_disk_obj(c);
disk->spin(disk, true);
if (disk2_debug)
printf("%s turning on motor %d\n", __func__, c->drive_two);
// event_fire_disk_active(drive_two? 2 : 1);
} break;
case 0x0A:
if (c->motor_on && c->drive_two) {
c->disk2.spin(&c->disk2, false);
c->disk1.spin(&c->disk1, true);
}
c->drive_two = false;
if (c->motor_on) {
// event_fire_disk_active(1);
}
break;
case 0x0B:
if (c->motor_on && !c->drive_two) {
c->disk1.spin(&c->disk1, false);
c->disk2.spin(&c->disk2, true);
}
c->drive_two = true;
if (c->motor_on) {
// event_fire_disk_active(2);
}
break;
case 0x0C: {
DiskFormatDesc *disk = active_disk_obj(c);
if (!c->motor_on) {
// do nothing
} else if (c->write_mode) {
// XXX ignores timing
disk->write_byte(disk, c->data_register);
c->data_register = 0; // "shifted out".
} else {
// XXX any even-numbered switch can be used
// to read a byte. But for now we do so only
// through the sanctioned switch for that purpose.
ret = c->data_register = disk->read_byte(disk);
// printf("read byte %02X\n", ret);
}
} break;
case 0x0D:
#if 0
if (!motor_on || drive_two) {
// do nothing
} else if (write_mode) {
data_register = (val == -1? 0: val);
} else {
// XXX should return whether disk is write-protected...
}
#endif
break;
case 0x0E:
c->write_mode = false;
if (disk2_debug)
printf("%s write mode off\n", __func__);
break;
case 0x0F:
c->write_mode = true;
if (disk2_debug)
printf("%s write mode on\n", __func__);
break;
default:
;
}
D2DBG("\n");
return ret;
}
static int
_mii_disk2_command(
mii_t * mii,
struct mii_slot_t *slot,
uint8_t cmd,
void * param)
{
mii_card_disk2_t *c = slot->drv_priv;
switch (cmd) {
case MII_SLOT_DRIVE_COUNT:
if (param)
*(int *)param = 2;
break;
case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + 2 - 1:
if (param) {
int drive = cmd - MII_SLOT_DRIVE_LOAD;
const char *filename = param;
if (c->disk[drive].privdat) {
c->disk[drive].eject(&c->disk[drive]);
}
printf("%s: drive %d loading %s\n", __func__, drive,
filename);
c->disk[drive] = disk_format_load(filename);
return 0;
}
break;
}
return 0;
}
static mii_slot_drv_t _driver = {
.name = "disk2",
.desc = "Apple Disk ][",
.init = _mii_disk2_init,
.access = _mii_disk2_access,
.run = _mii_disk2_run,
.command = _mii_disk2_command,
};
MI_DRIVER_REGISTER(_driver);

9
src/drivers/mii_disk2.h Normal file
View File

@ -0,0 +1,9 @@
/*
* mii_disk2.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once

View File

@ -0,0 +1,89 @@
/*
* mii_epromcard.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*
* This is a driver for these eprom/flash cards from
* Terence J. Boldt and the likes
*/
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include "mii.h"
#include "mii_bank.h"
typedef struct mii_card_ee_t {
uint8_t * file;
uint16_t latch;
} mii_card_ee_t;
int mmapfile(const char *fname, uint8_t **buf, size_t *sz, int flags);
static int
_mii_ee_init(
mii_t * mii,
struct mii_slot_t *slot )
{
uint8_t * file;
size_t sz;
const char *fname = "disks/Apple IIe Diagnostic 2.1.po";
//const char *fname = "disks/GamesWithFirmware.po";
if (mmapfile(fname, &file, &sz, O_RDONLY) != 0) {
printf("Failed to load %s\n", fname);
return -1;
}
mii_card_ee_t *c = calloc(1, sizeof(*c));
slot->drv_priv = c;
c->file = file;
printf("%s loading in slot %d\n", __func__, slot->id);
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr, c->file + 0x300, 256);
return 0;
}
static uint8_t
_mii_ee_access(
mii_t * mii, struct mii_slot_t *slot,
uint16_t addr, uint8_t byte, bool write)
{
mii_card_ee_t *c = slot->drv_priv;
// printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
// mii->cpu.PC, addr, byte, write);
int psw = addr & 0x0F;
if (write) {
switch (psw) {
case 0:
c->latch = (c->latch & 0xff00) | byte;
break;
case 1:
c->latch = (c->latch & 0x00ff) | (byte << 8);
break;
}
} else {
return c->file[(c->latch << 4) + psw];
}
return 0;
}
static mii_slot_drv_t _driver = {
.name = "eecard",
.desc = "EEPROM 1MB card",
.init = _mii_ee_init,
.access = _mii_ee_access,
};
MI_DRIVER_REGISTER(_driver);

187
src/drivers/mii_mouse.c Normal file
View File

@ -0,0 +1,187 @@
#include "mii.h"
// https://github.com/ivanizag/izapple2/blob/master/cardMouse.go
// https://hackaday.io/project/19925-aiie-an-embedded-apple-e-emulator/log/188017-entry-23-here-mousie-mousie-mousie
// https://github.com/ct6502/apple2ts/blob/main/src/emulator/mouse.ts
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mii.h"
#include "mii_bank.h"
enum {
CLAMP_MIN_LO = 0x478,
CLAMP_MIN_HI = 0x578,
CLAMP_MAX_LO = 0x4F8,
CLAMP_MAX_HI = 0x5F8,
// RAM Locations
// (Add $Cn where n is slot to these)
MOUSE_X_LO = 0x03B8,
MOUSE_X_HI = 0x04B8,
MOUSE_Y_LO = 0x0438,
MOUSE_Y_HI = 0x0538,
MOUSE_STATUS = 0x06B8,
MOUSE_MODE = 0x0738,
};
enum {
mouseEnabled = 1,
mouseIntMoveEnabled = 2,
mouseIntButtonEnabled = 4,
mouseIntVBlankEnabled = 8,
};
typedef struct mii_card_mouse_t {
struct mii_slot_t * slot;
uint8_t slot_offset;
uint8_t mode; // cached mode byte
struct {
uint16_t x, y;
bool button;
} last;
} mii_card_mouse_t;
static int
_mii_mouse_init(
mii_t * mii,
struct mii_slot_t *slot )
{
mii_card_mouse_t *c = calloc(1, sizeof(*c));
c->slot = slot;
slot->drv_priv = c;
printf("%s loading in slot %d\n", __func__, slot->id + 1);
c->slot_offset = slot->id + 1 + 0xc0;
uint8_t data[256] = {};
// Identification as a mouse card
// From Technical Note Misc #8, "Pascal 1.1 Firmware Protocol ID Bytes":
data[0x05] = 0x38;
data[0x07] = 0x18;
data[0x0b] = 0x01;
data[0x0c] = 0x20;
// From "AppleMouse // User's Manual", Appendix B:
//data[0x0c] = 0x20
data[0xfb] = 0xd6;
// Set 8 entrypoints to sofstwitches 2 to 1f
for (int i = 0; i < 14; i++) {
uint8_t base = 0x60 + 0x05 * i;
data[0x12+i] = base;
data[base+0] = 0x8D; // STA $C0x2
data[base+1] = 0x82 + i + ((slot->id + 1) << 4);
data[base+2] = 0xC0;
data[base+3] = 0x18; // CLC ;no error
data[base+4] = 0x60; // RTS
}
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr, data, 256);
return 0;
}
static uint8_t
_mii_mouse_access(
mii_t * mii,
struct mii_slot_t *slot,
uint16_t addr,
uint8_t byte,
bool write)
{
mii_card_mouse_t *c = slot->drv_priv;
int psw = addr & 0x0F;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
switch (psw) {
case 2: {
if (write) {
byte &= 0xf;
mii_bank_poke(main, MOUSE_MODE + c->slot_offset, byte);
printf("%s: mouse mode %02x\n", __func__, byte);
mii->mouse.enabled = byte & mouseEnabled;
printf("Mouse %s\n", mii->mouse.enabled ? "enabled" : "disabled");
printf(" Interupt: %s\n", byte & mouseIntMoveEnabled ? "enabled" : "disabled");
printf(" Button: %s\n", byte & mouseIntButtonEnabled ? "enabled" : "disabled");
printf(" VBlank: %s\n", byte & mouseIntVBlankEnabled ? "enabled" : "disabled");
c->mode = byte;
mii->video.vbl_irq = !!(byte & mouseIntVBlankEnabled);
}
} break;
case 4: {// read mouse
if (!mii->mouse.enabled)
break;
uint8_t status = 0;
if (mii->mouse.button)
status |= 1 << 7;
if (c->last.button) {
status |= 1 << 6;
}
if ((mii->mouse.x != c->last.x) || (mii->mouse.y != c->last.y)) {
status |= 1 << 5;
}
mii_bank_poke(main, MOUSE_X_HI + c->slot_offset, mii->mouse.x >> 8);
mii_bank_poke(main, MOUSE_Y_HI + c->slot_offset, mii->mouse.y >> 8);
mii_bank_poke(main, MOUSE_X_LO + c->slot_offset, mii->mouse.x);
mii_bank_poke(main, MOUSE_Y_LO + c->slot_offset, mii->mouse.y);
mii_bank_poke(main, MOUSE_STATUS + c->slot_offset, status);
// mii_bank_poke(main, MOUSE_MODE + c->slot_offset, 0xf); // already in place
c->last.x = mii->mouse.x;
c->last.y = mii->mouse.y;
c->last.button = mii->mouse.button;
} break;
case 5: // clear mouse
break;
case 7: // set mouse
if (byte == 0) {
mii->mouse.min_x = mii_bank_peek(main, CLAMP_MIN_LO) |
(mii_bank_peek(main, CLAMP_MIN_HI) << 8);
mii->mouse.max_x = mii_bank_peek(main, CLAMP_MAX_LO) |
(mii_bank_peek(main, CLAMP_MAX_HI) << 8);
} else if (byte == 1) {
mii->mouse.min_y = mii_bank_peek(main, CLAMP_MIN_LO) |
(mii_bank_peek(main, CLAMP_MIN_HI) << 8);
mii->mouse.max_y = mii_bank_peek(main, CLAMP_MAX_LO) |
(mii_bank_peek(main, CLAMP_MAX_HI) << 8);
}
printf("Mouse clamp to %d,%d - %d,%d\n",
mii->mouse.min_x, mii->mouse.min_y,
mii->mouse.max_x, mii->mouse.max_y);
break;
case 8: // home mouse
mii->mouse.x = mii->mouse.min_x;
mii->mouse.y = mii->mouse.min_y;
break;
case 0xc: // init mouse
mii->mouse.min_x = mii->mouse.min_y = 0;
mii->mouse.max_x = mii->mouse.max_y = 1023;
mii->mouse.enabled = 0;
mii_bank_poke(main, MOUSE_MODE + c->slot_offset, 0x0);
break;
default:
printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
mii->cpu.PC, addr, byte, write);
break;
}
return 0;
}
static mii_slot_drv_t _driver = {
.name = "mouse",
.desc = "Mouse card",
.init = _mii_mouse_init,
.access = _mii_mouse_access,
};
MI_DRIVER_REGISTER(_driver);

21
src/drivers/mii_mouse.h Normal file
View File

@ -0,0 +1,21 @@
/*
* mii_mouse.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
// mins/max are set by the card, x,y,button are set by the UI
typedef struct mii_mouse_t {
bool enabled; // read only, set by driver
uint16_t min_x, max_x,
min_y, max_y; // set by driver when enabled
uint16_t x, y;
bool button;
} mii_mouse_t;

View File

@ -0,0 +1,177 @@
/*
* mii_noslotclock.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*
*/
// http://ctrl.pomme.reset.free.fr/index.php/hardware/no-slot-clock-ds1216e/
// Disassembly:
// https://gist.github.com/mgcaret/ae2860c754fd029d2640107c4fe0bffd
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "mii.h"
#include "mii_bank.h"
#include "minipt.h"
const uint64_t nsc_pattern = 0x5CA33AC55CA33AC5ULL;
typedef struct mii_nsc_t {
mii_t * mii;
uint64_t pattern;
void * state; // protothread state
int bitcount;
int mode; // read or write;
} mii_nsc_t;
static void
_mii_nsc_maketime(
mii_nsc_t *nsc)
{
uint64_t ret = 0;
time_t now = time(NULL);
struct tm t;
localtime_r(&now, &t);
int year = t.tm_year % 100;
ret = year / 10;
ret <<= 4;
ret += year % 10;
ret <<= 4;
int month = t.tm_mon + 1;
ret += month / 10;
ret <<= 4;
ret += month % 10;
ret <<= 4;
int day = t.tm_mday;
ret += day / 10;
ret <<= 4;
ret += day % 10;
ret <<= 4;
// Bits 4 and 5 of the day ret are used to control the RST and oscillator
// functions. These bits are shipped from the factory set to logic 1.
ret += 0x0; //0x3, but zero on read.
ret <<= 4;
ret += t.tm_wday + 1; // uint64(now.Weekday()) + 1
ret <<= 4;
int hour = t.tm_hour;
ret += 0x0; // 0x8 for 24 hour mode, but zero on read.
ret += hour / 10;
ret <<= 4;
ret += hour % 10;
ret <<= 4;
int minute = t.tm_min;
ret += minute / 10;
ret <<= 4;
ret += minute % 10;
ret <<= 4;
int second = t.tm_sec;
ret += second / 10;
ret <<= 4;
ret += second % 10;
ret <<= 4;
int centisecond = 0;
ret += centisecond / 10;
ret <<= 4;
ret += centisecond % 10;
nsc->pattern = ret;
}
/*
* this is a protothread, so remember no locals that will survive a yield()
*/
static bool
_mii_nsc_access(
struct mii_bank_t *bank,
void *param,
uint16_t addr,
uint8_t * byte,
bool write)
{
mii_nsc_t * nsc = param;
// printf("%s PC:%04x access %s addr %04x byte %02x write %d\n",
// __func__, nsc->mii->cpu.PC,
// bank->name, addr, *byte, write);
int rd = (addr & 0x4);
int bit = addr & 1;
bool res = false;
pt_start(nsc->state);
do {
if (rd) {
nsc->pattern = 0;
} else {
nsc->pattern = (nsc->pattern >> 1) | ((uint64_t)bit << 63);
int match = nsc->pattern == nsc_pattern;
if (match) {
// printf("pattern %016lx %s\n", nsc->pattern, match ? "match" : "");
break;
}
}
pt_yield(nsc->state);
} while (1);
nsc->bitcount = 0;
pt_yield(nsc->state);
nsc->mode = rd;
// printf("%s in %s mode\n", __func__, rd ? "read" : "write");
if (nsc->mode) {
_mii_nsc_maketime(nsc);
// printf("time %016lx\n", nsc->pattern);
}
do {
if (nsc->mode) {
*byte = (nsc->pattern >> nsc->bitcount) & 1;
} else {
nsc->pattern |= ((uint64_t)bit << nsc->bitcount);
}
res = true; // don't call ROM handler
pt_yield(nsc->state);
nsc->bitcount++;
} while (nsc->bitcount < 64 && nsc->mode == rd);
// printf("%s done\n", __func__);
pt_end(nsc->state);
return res;
}
static int
_mii_nsc_probe(
mii_t *mii,
uint32_t flags)
{
printf("%s %s\n", __func__, flags & MII_INIT_NSC ? "enabled" : "disabled");
if (!(flags & MII_INIT_NSC))
return 0;
mii_nsc_t * nsc = calloc(1, sizeof(*nsc));
nsc->mii = mii;
// This worked fine with NS.CLOCK.SYSTEM but...
// mii_bank_install_access_cb(&mii->bank[MII_BANK_CARD_ROM],
// _mii_nsc_access, nsc, 0xc1, 0);
/* ... A2Desktop requires the NSC to be on the main rom, the source
* claims it probe the slots, but in fact, it doesnt */
mii_bank_install_access_cb(&mii->bank[MII_BANK_ROM],
_mii_nsc_access, nsc, 0xc3, 0);
return 1;
}
static mii_slot_drv_t _driver = {
.name = "nsc",
.desc = "No Slot Clock",
.enable_flag = MII_INIT_NSC,
.probe = _mii_nsc_probe,
};
MI_DRIVER_REGISTER(_driver);

342
src/drivers/mii_smartport.c Normal file
View File

@ -0,0 +1,342 @@
/*
* mii_smartport.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*
* This is shamelessly inspired from:
* https:github.com/ct6502/apple2ts/blob/main/src/emulator/harddrivedata.ts
* http://www.1000bit.it/support/manuali/apple/technotes/smpt/tn.smpt.1.html
*/
#define _GNU_SOURCE // for asprintf
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include "mii.h"
#include "mii_bank.h"
#include "mii_dd.h"
#include "mii_slot.h"
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#define INCBIN_PREFIX mii_
#include "incbin.h"
INCBIN(smartport_rom, "test/asm/mii_smartport_driver.bin");
extern const unsigned char mii_smartport_rom_data[];
#define MII_SM_DRIVE_COUNT 2
typedef struct mii_card_sm_t {
struct mii_card_sm_t *next;
mii_dd_t drive[MII_SM_DRIVE_COUNT];
struct mii_slot_t *slot;
uint8_t * file;
} mii_card_sm_t;
static void
_mii_hd_callback(
mii_t *mii,
uint8_t trap)
{
int sid = ((mii->cpu.PC >> 8) & 0xf) - 1;
mii_card_sm_t *c = mii->slot[sid].drv_priv;
uint8_t command = mii_read_one(mii, 0x42);
uint8_t unit = mii_read_one(mii, 0x43);
uint16_t buffer = mii_read_word(mii, 0x44);
uint16_t blk = mii_read_word(mii, 0x46);
unit >>= 7; // last bit is the one we want, drive 0/1
switch (command) {
case 0: { // get status
if (!c->drive[unit].file) {
mii->cpu.X = mii->cpu.Y = 0;
mii->cpu.P.C = 1;
} else {
int nblocks = (c->drive[unit].file->size + 511) / 512;
mii->cpu.X = nblocks & 0xff;
mii->cpu.Y = nblocks >> 8;
mii->cpu.P.C = 0;
}
} break;
case 1: {// read block
if (!c->drive[unit].file) {
mii->cpu.P.C = 1;
break;
}
if (blk >= c->drive[unit].file->size / 512) {
mii->cpu.P.C = 1;
break;
}
mii_bank_t * bank = &mii->bank[mii->mem[buffer >> 8].write];
mii->cpu.P.C = mii_dd_read(
&c->drive[unit], bank, buffer, blk, 1) != 0;
} break;
case 2: {// write block
if (!c->drive[unit].file) {
mii->cpu.P.C = 1;
break;
}
if (blk >= c->drive[unit].file->size / 512) {
mii->cpu.P.C = 1;
break;
}
mii_bank_t * bank = &mii->bank[mii->mem[buffer >> 8].read];
mii->cpu.P.C = mii_dd_write(
&c->drive[unit], bank, buffer, blk, 1) != 0;
} break;
default: {
printf("%s cmd %02x unit %02x buffer %04x blk %04x\n", __func__,
command, unit, buffer, blk);
printf("*** %s: unhandled command %02x\n", __func__, command);
mii->cpu.P.C = 1;
}
}
}
static void
_mii_sm_callback(
mii_t *mii,
uint8_t trap)
{
printf("%s\n", __func__);
int sid = ((mii->cpu.PC >> 8) & 0xf) - 1;
mii_card_sm_t *c = mii->slot[sid].drv_priv;
uint16_t sp = 0x100 + mii->cpu.S + 1;
uint16_t call_addr = mii_read_word(mii, sp);
uint8_t spCommand = mii_read_one(mii, call_addr + 1);
uint16_t spParams = mii_read_word(mii, call_addr + 2);
call_addr += 3;
mii_write_word(mii, sp, call_addr);
uint8_t spPCount = mii_read_one(mii, spParams + 0);
uint8_t spUnit = mii_read_one(mii, spParams + 1);
uint16_t spBuffer = mii_read_word(mii, spParams + 2);
printf("%s cmd %02x params %04x pcount %d unit %02x buffer %04x\n", __func__,
spCommand, spParams, spPCount, spUnit, spBuffer);
switch (spCommand) {
case 0: { // get status
if (spPCount != 3) {
mii->cpu.P.C = 1;
break;
}
uint8_t status = mii_read_one(mii, spParams + 4);
printf("%s: unit %d status %02x \n", __func__, spUnit, status);
uint8_t st = 0x80 | 0x40 | 0x20;
uint32_t bsize = 0;
if (spUnit) spUnit--;
if (spUnit < MII_SM_DRIVE_COUNT && c->drive[spUnit].file) {
st |= 0x10;
bsize = (c->drive[spUnit].file->size + 511) / 512;
}
if (status == 0) {
mii->cpu.P.C = 0;
/* Apple IIc reference says this ought to be a status byte,
* but practice and A2Desktop says it ought to be a drive
* count, so here goes... */
// mii_write_one(mii, spBuffer++, st);
mii_write_one(mii, spBuffer++, MII_SM_DRIVE_COUNT);
mii_write_one(mii, spBuffer++, bsize);
mii_write_one(mii, spBuffer++, bsize >> 8);
mii_write_one(mii, spBuffer++, bsize >> 16);
} else if (status == 3 && spUnit < MII_SM_DRIVE_COUNT) {
mii->cpu.P.C = 0;
mii_write_one(mii, spBuffer++, st);
mii_write_one(mii, spBuffer++, bsize);
mii_write_one(mii, spBuffer++, bsize >> 8);
mii_write_one(mii, spBuffer++, bsize >> 16);
char dname[17] = "\x8MII HD 0 ";
dname[8] = '0' + spUnit;
for (int i = 0; i < 17; i++)
mii_write_one(mii, spBuffer++, dname[i]);
mii_write_one(mii, spBuffer++, 0x02); // Profile
mii_write_one(mii, spBuffer++, 0x00); // Profile
mii_write_one(mii, spBuffer++, 0x01); // Version
mii_write_one(mii, spBuffer++, 0x13);
} else {
printf("%s: unit %d bad status %d\n",
__func__, spUnit, status);
mii->cpu.P.C = 1;
}
} break;
case 1: { // read
if (spPCount != 3) {
printf("%s: unit %d bad pcount %d\n",
__func__, spUnit, spPCount);
mii->cpu.P.C = 1;
break;
}
if (spUnit >= MII_SM_DRIVE_COUNT) {
printf("%s: unit %d out of range\n",
__func__, spUnit);
mii->cpu.P.C = 1;
break;
}
uint32_t blk = mii_read_word(mii, spParams + 3) |
(mii_read_one(mii, spParams + 4) << 16) |
(mii_read_one(mii, spParams + 5) << 24);
printf("%s read block %x\n", __func__, blk);
if (!c->drive[spUnit].file) {
mii->cpu.P.C = 1;
break;
}
if (blk >= c->drive[spUnit].file->size / 512) {
printf("%s: block %d out of range\n",
__func__, blk);
mii->cpu.P.C = 1;
break;
}
mii_bank_t * bank = &mii->bank[mii->mem[spBuffer >> 8].write];
mii->cpu.P.C = mii_dd_read(
&c->drive[spUnit], bank, spBuffer, blk, 1) != 0;
// mii->cpu.P.C = 0;
} break;
case 2: { // write
if (spPCount != 3) {
printf("%s: unit %d bad pcount %d\n",
__func__, spUnit, spPCount);
mii->cpu.P.C = 1;
break;
}
if (spUnit >= MII_SM_DRIVE_COUNT) {
printf("%s: unit %d out of range\n",
__func__, spUnit);
mii->cpu.P.C = 1;
break;
}
uint32_t blk = mii_read_word(mii, spParams + 3) |
(mii_read_one(mii, spParams + 4) << 16) |
(mii_read_one(mii, spParams + 5) << 24);
printf("%s write block %x\n", __func__, blk);
if (!c->drive[spUnit].file) {
mii->cpu.P.C = 1;
break;
}
if (blk >= c->drive[spUnit].file->size / 512) {
printf("%s: block %d out of range\n",
__func__, blk);
mii->cpu.P.C = 1;
break;
}
mii_bank_t * bank = &mii->bank[mii->mem[spBuffer >> 8].read];
mii->cpu.P.C = mii_dd_write(
&c->drive[spUnit], bank, spBuffer, blk, 1) != 0;
} break;
}
}
static int
_mii_sm_init(
mii_t * mii,
struct mii_slot_t *slot )
{
mii_card_sm_t *c = calloc(1, sizeof(*c));
c->slot = slot;
slot->drv_priv = c;
printf("%s loading in slot %d\n", __func__, slot->id);
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr, mii_smartport_rom_data, 256);
uint8_t trap_hd = mii_register_trap(mii, _mii_hd_callback);
uint8_t trap_sm = mii_register_trap(mii, _mii_sm_callback);
printf("%s: traps %02x %02x\n", __func__, trap_hd, trap_sm);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr + 0xd2, &trap_hd, 1);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr + 0xe2, &trap_sm, 1);
for (int i = 0; i < MII_SM_DRIVE_COUNT; i++) {
mii_dd_t *dd = &c->drive[i];
dd->slot_id = slot->id + 1;
dd->drive = i + 1;
dd->slot = slot;
asprintf((char **)&dd->name, "SmartPort S:%d D:%d",
dd->slot_id, dd->drive);
}
mii_dd_register_drives(&mii->dd, c->drive, MII_SM_DRIVE_COUNT);
return 0;
}
static int
_mii_sm_command(
mii_t * mii,
struct mii_slot_t *slot,
uint8_t cmd,
void * param)
{
mii_card_sm_t *c = slot->drv_priv;
switch (cmd) {
case MII_SLOT_DRIVE_COUNT:
if (param)
*(int *)param = MII_SM_DRIVE_COUNT;
break;
case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + MII_SM_DRIVE_COUNT - 1:
if (param) {
int drive = cmd - MII_SLOT_DRIVE_LOAD;
const char *filename = param;
mii_dd_file_t *file = NULL;
if (filename) {
file = mii_dd_file_load(&mii->dd, filename, 0);
if (!file)
return -1;
}
mii_dd_drive_load(&c->drive[drive], file);
return 0;
}
break;
}
return 0;
}
static uint8_t
_mii_sm_access(
mii_t * mii, struct mii_slot_t *slot,
uint16_t addr, uint8_t byte, bool write)
{
#if 0
mii_card_sm_t *c = slot->drv_priv;
printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
mii->cpu.PC, addr, byte, write);
int psw = addr & 0x0F;
if (write) {
switch (psw) {
case 0:
// c->latch = (c->latch & 0xff00) | byte;
break;
case 1:
// c->latch = (c->latch & 0x00ff) | (byte << 8);
break;
}
} else {
// return c->file[(c->latch << 4) + psw];
}
#endif
return 0;
}
static mii_slot_drv_t _driver = {
.name = "smartport",
.desc = "SmartPort card",
.init = _mii_sm_init,
.access = _mii_sm_access,
.command = _mii_sm_command,
};
MI_DRIVER_REGISTER(_driver);

View File

@ -0,0 +1,79 @@
/*
* mii_titan_iie.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mii.h"
#include "mii_bank.h"
/*
* This is a mini driver for the Titan Accelerator IIe, not very common,
* but it's a nice card, and it's a good example of a driver that needs
* to use a softswitch ovverride to work.
* Also, I own one of these, and none of the other fancy ones, so this one
* gets the love.
*/
static bool
_mii_titan_access(
struct mii_bank_t *bank,
void *param,
uint16_t addr,
uint8_t * byte,
bool write)
{
mii_t *mii = param;
bool res = false;
mii_bank_t *main = &mii->bank[MII_BANK_MAIN];
if (write) {
printf("titan: write %02x to %04x\n", *byte, addr);
switch (*byte) {
case 5:
mii->speed = 3.58;
mii_bank_poke(main, 0xc086, *byte);
break;
case 1:
mii_bank_poke(main, 0xc086, *byte);
mii->speed = 1;
break;
case 0xa: // supposed to lock it too...
mii_bank_poke(main, 0xc086, *byte);
mii->speed = 1;
break;
default:
printf("titan: unknown speed %02x\n", *byte);
break;
}
}
return res;
}
static int
_mii_titan_probe(
mii_t *mii,
uint32_t flags)
{
printf("%s %s\n", __func__, flags & MII_INIT_TITAN ? "enabled" : "disabled");
if (!(flags & MII_INIT_TITAN))
return 0;
// this override a read-only soft switch, but we only handle writes
// so it's fine
mii_set_sw_override(mii, 0xc086, _mii_titan_access, mii);
mii->speed = 3.58;
return 1;
}
static mii_slot_drv_t _driver = {
.name = "titan",
.desc = "Titan Accelerator IIe",
.enable_flag = MII_INIT_TITAN,
.probe = _mii_titan_probe,
};
MI_DRIVER_REGISTER(_driver);

472
src/format/dsk.c Normal file
View File

@ -0,0 +1,472 @@
// format/dsk.c
//
// Copyright (c) 2023 Micah John Cowan.
// This code is licensed under the MIT license.
// See the accompanying LICENSE file for details.
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/mman.h>
#include <assert.h>
#include "mii_disk_format.h"
#define NIBBLE_SECTOR_SIZE 416
#define NIBBLE_TRACK_SIZE 6656
#define DSK_SECTOR_SIZE 256
#define MAX_SECTORS 16
#define VOLUME_NUMBER 254
#define DSK_TRACK_SIZE (DSK_SECTOR_SIZE * MAX_SECTORS)
#define byte uint8_t
struct dskprivdat {
const char *path;
byte *realbuf;
byte *buf;
const byte *secmap;
int bytenum;
uint64_t dirty_tracks;
};
static const struct dskprivdat datinit = { 0 };
static const size_t nib_disksz = 232960;
static const size_t dsk_disksz = 143360;
// DOS 3.3 Physical sector order (index is physical sector,
// value is DOS sector)
const byte DO[] = {
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4,
0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF
};
// ProDOS Physical sector order (index is physical sector,
// value is ProDOS sector).
const byte PO[] = {
0x0, 0x8, 0x1, 0x9, 0x2, 0xa, 0x3, 0xb,
0x4, 0xc, 0x5, 0xd, 0x6, 0xe, 0x7, 0xf
};
const byte TRANS62[] = {
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
static const int DETRANS62[] = {
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 0x00, 0x01,
-1, -1, 0x02, 0x03, -1, 0x04, 0x05, 0x06,
-1, -1, -1, -1, -1, -1, 0x07, 0x08,
-1, -1, -1, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
-1, -1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
-1, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, 0x1B, -1, 0x1C, 0x1D, 0x1E,
-1, -1, -1, 0x1F, -1, -1, 0x20, 0x21,
-1, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
-1, -1, -1, -1, -1, 0x29, 0x2A, 0x2B,
-1, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
-1, -1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
-1, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
};
static void realign_tracks(DiskFormatDesc *desc)
{
/*
When we unpack a .dsk into nibblized form, it's
automatically aligned neatly within our track-storage
boundaries within the disk buffer, because that's how
we laid 'em out.
When a sector is written or updated, it will
tend to stay aligned, because the program had to
find the existing sector to write to, to keep things
in order.
However, in the event of reformatting, there are no
guarantees about the track being aligned conveniently.
The formatting program can start at any old position
and just start writing, because no existing data is going
to be preserved.
To deal with this, we could either:
1) Be prepared to loop back around our track's buffer,
mid-sector
2) Re-align the track at our convenience, to start
somewhere that we know for sure can't be the middle of
sector data.
I've opted for option (2). It would be a bad option if
we're reading and writing a .nib file, because we'd be
unnecessarily altering the file's structure (in cases
where it had NOT been reformatted), but it's perfectly
fine when we'll be discarding the nibblized format anyway.
We handle this by seeking forward to the first sector-field
start boundary (D5 AA 96) that we can find, and make that
the new "start" of our track. It doesn't matter if it's a
false-start that doesn't start a real sector-field, because one
thing we know for *sure* is that first D5 can't be found
in the middle of a legitimate header or data field.
*/
struct dskprivdat *dat = desc->privdat;
byte *buf = dat->buf;
byte *secbuf = malloc(NIBBLE_TRACK_SIZE);
for (int t=0; t != NUM_TRACKS; ++t) {
byte *tstart = buf + (t * NIBBLE_TRACK_SIZE);
byte *tend = buf + ((t + 1) * NIBBLE_TRACK_SIZE);
byte *talign;
for (talign = tstart; talign <= (tend - 3); ++talign) {
if (talign[0] == 0xD5 && talign[1] == 0xAA && talign[2] == 0x96) {
if (talign == tstart) {
// Nothing to do, already aligned.
} else {
size_t rollsz = talign - tstart;
memcpy(secbuf, tstart, rollsz);
memmove(tstart, talign, tend-talign);
memcpy(tend - rollsz, secbuf, rollsz);
}
break; // handle next track
}
}
}
free(secbuf);
}
static void implodeDo(DiskFormatDesc *desc)
{
struct dskprivdat *dat = desc->privdat;
realign_tracks(desc);
const byte *rd = dat->buf; // nibble buf
const byte *end = dat->buf + nib_disksz;
bool warned = false;
for (;;) {
// Scan forward for a sector header
const int sector_hdr_sz = 11; // counts prologue, but not epilogue
for (;;) {
// This is the only place we're "allowed" to end processing.
if (rd >= (end - sector_hdr_sz)) goto done;
if (rd[0] == 0xD5 && rd[1] == 0xAA && rd[2] == 0x96) break;
++rd;
}
header:
rd += 3;
int v = ((rd[0] << 1) | 0x1) & rd[1];
rd += 2;
int t = ((rd[0] << 1) | 0x1) & rd[1];
rd += 2;
int s = ((rd[0] << 1) | 0x1) & rd[1];
rd += 2;
int checkSum = ((rd[0] << 1) | 0x1) & rd[1];
rd += 2;
if (checkSum != (v ^ t ^ s)) {
WARN("Sector header checksum failed, t=%d s=%d"
" at nibblized byte %zu.\n", t, s, rd - dat->buf);
WARN("Probable disk corruption for %s\n", dat->path);
}
byte truet = (rd - dat->buf)/NIBBLE_TRACK_SIZE;
if (t != truet) {
WARN("Sector header lying about track number"
" at nibblized pos %zu\n", (size_t)(rd - dat->buf));
WARN(" (says %d but we're on track %d). Skipping sector.\n",
(int)t, (int)truet);
continue;
}
if (s >= MAX_SECTORS) {
WARN("Sector header sec# too high (%d; max is %d).\n",
(int)s, (int)MAX_SECTORS);
WARN(" Skipping sector.\n");
continue;
}
const int data_field_sz = 0x15A; //counts prologue, not epilogue
for (;;) {
if (rd >= (end - 0x15A)) goto bail;
if (rd[0] == 0xD5 && rd[1] == 0xAA) {
if (rd[2] == 0x96) goto header;
if (rd[2] == 0xAD) break;
}
++rd;
}
rd += 3;
// Translate sector data field
{
int s_ = dat->secmap[s];
byte *data = dat->realbuf + (t * DSK_TRACK_SIZE)
+ (s_ * DSK_SECTOR_SIZE);
byte data2[0x56];
byte last = 0;
int val;
if (0) {
WARN("Translating track %d, phys sec %d (image sec %d)\n",
t, s, s_);
WARN(" starting at dsk pos %zu\n", (size_t)(data - dat->realbuf));
}
for (int j = 0x55; j >= 0; --j) {
val = DETRANS62[*rd - 0x80];
if (val == -1 && !warned) {
warned = true;
WARN("Untranslatable nibble at (nibblized) pos %zu,"
" disk %s.\n", (size_t)(rd - dat->buf), dat->path);
if (rd <= end - 4) {
WARN("%02X %02X %02X %02X [%02X] %02X %02X %02X\n",
rd[-4], rd[-3], rd[-2], rd[-1],
rd[0], rd[1], rd[2], rd[3]);
}
WARN("CORRUPT DISK SAVE TO %s\n", dat->path);
}
val ^= last;
data2[j] = val;
last = val;
++rd;
}
for (int j = 0; j < 0x100; ++j) {
val = DETRANS62[*rd - 0x80];
if (val == -1 && !warned) {
warned = true;
WARN("Untranslatable nibble at t=%d, phys s=%d,"
" disk %s.\n", t, s, dat->path);
WARN("CORRUPT DISK SAVE TO %s\n", dat->path);
}
val ^= last;
data[j] = val;
last = val;
++rd;
}
int checkSum = DETRANS62[*rd++ - 0x80];
if (checkSum != -1) checkSum ^= last;
if (checkSum != 0) {
WARN("Bad sector data checksum at t=%d, phys s=%d,"
" disk %s.\n", t, s, dat->path);
}
for (int k = 0, j = 0x55; k < 0x100; ++k) {
data[k] <<= 1;
if ((data2[j] & 0x1) != 0) {
data[k] |= 0x01;
}
data2[j] >>= 1;
data[k] <<= 1;
if ((data2[j] & 0x01) != 0) {
data[k] |= 0x01;
}
data2[j] >>= 0x01;
if (--j < 0) j = 0x55;
}
}
}
bail:
WARN("Error translating to dsk: ended mid-sector!\n");
WARN("Probable disk corruption for %s\n", dat->path);
done:
return;
}
static void spin(DiskFormatDesc *desc, bool b)
{
struct dskprivdat *dat = desc->privdat;
if (!b && dat->dirty_tracks != 0) {
implodeDo(desc);
// For now, sync the entire disk
errno = 0;
int err = msync(dat->realbuf, dsk_disksz, MS_SYNC);
if (err < 0) {
DIE(1,"Couldn't sync to disk file %s: %s\n",
dat->path, strerror(errno));
}
dat->dirty_tracks = 0;
}
}
static byte read_byte(DiskFormatDesc *desc)
{
struct dskprivdat *dat = desc->privdat;
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
byte val = dat->buf[pos];
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
return val;
}
static void write_byte(DiskFormatDesc *desc, byte val)
{
struct dskprivdat *dat = desc->privdat;
if ((val & 0x80) == 0) {
// D2DBG("dodged write $%02X", val);
return; // must have high bit
}
dat->dirty_tracks |= 1 << (desc->halftrack/2);
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
//D2DBG("write byte $%02X at pos $%04zX", (unsigned int)val, pos);
dat->buf[pos] = val;
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
}
static void eject(DiskFormatDesc *desc)
{
// free dat->path and dat, and unmap disk image
struct dskprivdat *dat = desc->privdat;
(void) munmap(dat->buf, dsk_disksz);
free((void*)dat->path);
free(dat);
}
// This function is derived from Scullin Steel Co.'s apple2js code
// https://github.com/whscullin/apple2js/blob/e280c3d/js/formats/format_utils.ts#L140
static void explodeSector(byte vol, byte track, byte sector,
byte **nibSec, const byte *data)
{
byte *wr = *nibSec;
unsigned int gap;
// Gap 1/3 (40/0x28 bytes)
if (sector == 0) // Gap 1
gap = 0x80;
else { // Gap 3
gap = track == 0? 0x28 : 0x26;
}
for (uint8_t i = 0; i != gap; ++i) {
*wr++ = 0xFF;
}
// Address Field
const byte checksum = vol ^ track ^ sector;
*wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0x96; // Address Prolog D5 AA 96
*wr++ = (vol >> 1) | 0xAA; *wr++ = vol | 0xAA;
*wr++ = (track >> 1) | 0xAA; *wr++ = track | 0xAA;
*wr++ = (sector >> 1) | 0xAA; *wr++ = sector | 0xAA;
*wr++ = (checksum >> 1) | 0xAA; *wr++ = checksum | 0xAA;
*wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB
// Gap 2 (5 bytes)
for (int i = 0; i != 5; ++i) {
*wr++ = 0xFF;
}
// Data Field
*wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0xAD; // Data Prolog D5 AA AD
byte *nibbles = wr;
const unsigned ptr2 = 0;
const unsigned ptr6 = 0x56;
for (int i = 0; i != 0x156; ++i) {
nibbles[i] = 0;
}
int i2 = 0x55;
for (int i6 = 0x101; i6 >= 0; --i6) {
byte val6 = data[i6 % 0x100];
byte val2 = nibbles[ptr2 + i2];
val2 = (val2 << 1) | (val6 & 1);
val6 >>= 1;
val2 = (val2 << 1) | (val6 & 1);
val6 >>= 1;
nibbles[ptr6 + i6] = val6;
nibbles[ptr2 + i2] = val2;
if (--i2 < 0)
i2 = 0x55;
}
byte last = 0;
for (int i = 0; i != 0x156; ++i) {
const byte val = nibbles[i];
nibbles[i] = TRANS62[last ^ val];
last = val;
}
wr += 0x156; // advance write-pointer
*wr++ = TRANS62[last];
*wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB
// Gap 3
*wr++ = 0xFF;
*nibSec = wr;
}
static void explodeDsk(byte *nibbleBuf, byte *dskBuf, const byte *secmap)
{
for (int t = 0; t < NUM_TRACKS; ++t) {
byte *writePtr = nibbleBuf;
for (int phys_sector = 0; phys_sector < MAX_SECTORS; ++phys_sector) {
const byte dos_sector = secmap[phys_sector];
const size_t off = ((MAX_SECTORS * t + dos_sector)
* DSK_SECTOR_SIZE);
explodeSector(VOLUME_NUMBER, t, phys_sector,
&writePtr, &dskBuf[off]);
}
assert(writePtr - nibbleBuf <= NIBBLE_TRACK_SIZE);
for (; writePtr != (nibbleBuf + NIBBLE_TRACK_SIZE); ++writePtr) {
*writePtr = 0xFF;
}
nibbleBuf += NIBBLE_TRACK_SIZE;
}
}
DiskFormatDesc dsk_insert(const char *path, byte *buf, size_t sz)
{
if (sz != dsk_disksz) {
DIE(0,"Wrong disk image size for %s:\n", path);
DIE(1," Expected %zu, got %zu.\n", dsk_disksz, sz);
}
struct dskprivdat *dat = malloc(sizeof *dat);
*dat = datinit;
dat->realbuf = buf;
dat->path = strdup(path);
dat->buf = calloc(1, nib_disksz);
const char *ext = rindex(path, '.');
ext = ext ? ext+1 : "";
if (!strcasecmp(ext, "PO")) {
INFO("Opening %s as PO.\n", dat->path);
dat->secmap = PO;
} else {
INFO("Opening %s as DO.\n", dat->path);
dat->secmap = DO;
}
explodeDsk(dat->buf, dat->realbuf, dat->secmap);
return (DiskFormatDesc){
.privdat = dat,
.spin = spin,
.read_byte = read_byte,
.write_byte = write_byte,
.eject = eject,
};
}

36
src/format/empty.c Normal file
View File

@ -0,0 +1,36 @@
// format/empty.c
//
// Copyright (c) 2023 Micah John Cowan.
// This code is licensed under the MIT license.
// See the accompanying LICENSE file for details.
#include "mii_disk_format.h"
#define byte uint8_t
void spin(DiskFormatDesc *d, bool b)
{
}
byte read_byte(DiskFormatDesc *d)
{
// A real disk can never send a low byte.
// But an empty disk must never send a legitimate byte.
return 0x00;
}
void write_byte(DiskFormatDesc *d, byte b)
{
}
void eject(DiskFormatDesc *d)
{
}
DiskFormatDesc empty_disk_desc = {
.spin = spin,
.read_byte = read_byte,
.write_byte = write_byte,
.eject = eject,
};

399
src/format/mii_dd.c Normal file
View File

@ -0,0 +1,399 @@
/*
* mii_dd.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "mii_bank.h"
#include "mii_dd.h"
#include "md5.h"
#ifndef FCC
#define FCC(_a,_b,_c,_d) (((_a)<<24)|((_b)<<16)|((_c)<<8)|(_d))
#endif
int
mii_dd_overlay_load(
mii_dd_t * dd );
int
mii_dd_overlay_prepare(
mii_dd_t * dd );
void
mii_dd_system_init(
mii_dd_system_t *dd )
{
dd->drive = NULL;
dd->file = NULL;
}
void
mii_dd_system_dispose(
mii_dd_system_t *dd )
{
while (dd->file)
mii_dd_file_dispose(dd, dd->file);
dd->file = NULL;
dd->drive = NULL;
}
void
mii_dd_register_drives(
mii_dd_system_t *dd,
mii_dd_t * drives,
uint8_t count )
{
for (int i = 0; i < count; i++) {
mii_dd_t *d = &drives[i];
d->dd = dd;
d->next = dd->drive;
dd->drive = d;
}
}
void
mii_dd_file_dispose(
mii_dd_system_t *dd,
mii_dd_file_t *file )
{
// remove it from dd's file queue
if (dd->file == file)
dd->file = file->next;
else {
mii_dd_file_t *f = dd->file;
while (f) {
if (f->next == file) {
f->next = file->next;
break;
}
f = f->next;
}
}
if (file->dd) {
file->dd->file = NULL;
file->dd = NULL;
}
if (file->fd >= 0) {
close(file->fd);
file->fd = -1;
file->map = NULL;
}
if (file->map) {
free(file->map);
file->map = NULL;
}
if (file->pathname) {
free(file->pathname);
file->pathname = NULL;
}
free(file);
}
int
mii_dd_drive_load(
mii_dd_t *dd,
mii_dd_file_t *file )
{
if (dd->file == file)
return 0;
if (dd->file) {
printf("%s: %s unloading %s\n", __func__,
dd->name,
dd->file->pathname);
mii_dd_file_dispose(dd->dd, dd->file);
dd->file = NULL;
}
if (!file)
return 0;
dd->file = file;
printf("%s: %s loading %s\n", __func__,
dd->name, file->pathname);
if (mii_dd_overlay_load(dd) < 0) {
printf("%s: No overlay to load, we're fine for now\n", __func__);
// no overlay.. what to do?
}
return 0;
}
mii_dd_file_t *
mii_dd_file_load(
mii_dd_system_t *dd,
const char *pathname,
uint16_t flags)
{
if (!flags)
flags = O_RDONLY;
int err;
int fd = open(pathname, flags);
if (fd < 0) {
perror(pathname);
return NULL;
}
struct stat st;
err = fstat(fd, &st);
if (err < 0) {
perror(pathname);
goto bail;
}
int protect = PROT_READ;
int mflags = MAP_PRIVATE;
if (flags & (O_RDWR | O_WRONLY)) {
protect |= PROT_WRITE;
mflags = MAP_SHARED;
}
uint8_t *buf = mmap(NULL, st.st_size, protect, mflags, fd, 0);
if (buf == NULL || buf == MAP_FAILED) {
perror(pathname);
// err = errno;
goto bail;
}
mii_dd_file_t * res = calloc(1, sizeof(*res));
res->pathname = strdup(pathname);
res->fd = fd;
res->map = buf;
res->start = buf;
res->size = st.st_size;
res->dd = NULL;
res->next = dd->file;
dd->file = res;
char *suffix = strrchr(pathname, '.');
if (suffix && !strcasecmp(suffix, ".2mg")) {
res->format = MII_DD_FILE_2MG;
res->map += 64;
}
return res;
bail:
close(fd);
return NULL;
}
mii_dd_file_t *
mii_dd_file_in_ram(
mii_dd_system_t *dd,
const char *pathname,
uint32_t size,
uint16_t flags)
{
mii_dd_file_t * res = calloc(1, sizeof(*res));
res->pathname = strdup(pathname);
res->fd = -1;
res->map = calloc(1, size);
res->start = res->map;
res->size = size;
res->dd = NULL;
res->next = dd->file;
dd->file = res;
res->format = MII_DD_FILE_RAM;
return res;
}
int
mii_dd_overlay_load(
mii_dd_t * dd )
{
if (dd->overlay.file)
return 0;
if (!dd->file)
return -1;
char *filename = NULL;
char *suffix = strrchr(dd->file->pathname, '.');
if (suffix) {
asprintf(&filename, "%.*s.miov", (int)(suffix - dd->file->pathname), dd->file->pathname);
} else {
asprintf(&filename, "%s.miov", dd->file->pathname);
}
int fd = open(filename, O_RDWR, 0666);
if (fd == -1) {
fprintf(stderr, "%s: overlay %s: %s\n", __func__,
filename, strerror(errno));
free(filename);
return -1;
}
mii_dd_file_t * file = mii_dd_file_load(dd->dd, filename, O_RDWR);
close(fd);
if (!file)
return -1;
mii_dd_overlay_header_t * h = (mii_dd_overlay_header_t *)file->start;
if (h->magic != FCC('M','I','O','V')) {
fprintf(stderr, "Overlay file %s has invalid magic\n", filename);
mii_dd_file_dispose(dd->dd, file);
return -1;
}
if (h->version != 1) {
fprintf(stderr, "Overlay file %s has invalid version\n", filename);
mii_dd_file_dispose(dd->dd, file);
return -1;
}
if (h->size != dd->file->size / 512) {
fprintf(stderr, "Overlay file %s has invalid size\n", filename);
mii_dd_file_dispose(dd->dd, file);
return -1;
}
MD5_CTX d5 = {};
MD5_Init(&d5);
MD5_Update(&d5, dd->file->start, dd->file->size);
uint8_t md5[16];
MD5_Final(md5, &d5);
if (memcmp(md5, h->src_md5, 16)) {
fprintf(stderr, "Overlay file %s has mismatched HASH!\n", filename);
mii_dd_file_dispose(dd->dd, file);
return -1;
}
uint32_t bitmap_size = (h->size + 63) / 64;
dd->overlay.file = file;
dd->overlay.file->map += sizeof(*h) + bitmap_size;
dd->overlay.header = (mii_dd_overlay_header_t *)dd->overlay.file->start;
dd->overlay.bitmap = (uint64_t*)(dd->overlay.file->start + sizeof(*h));
dd->overlay.blocks = dd->overlay.file->map;
return 0;
}
int
mii_dd_overlay_prepare(
mii_dd_t * dd )
{
if (dd->overlay.file)
return 0;
if (!dd->file)
return -1;
printf("%s: %s Preparing Overlay file\n", __func__, dd->name);
uint32_t src_blocks = dd->file->size / 512;
uint32_t bitmap_size = (src_blocks + 63) / 64;
uint32_t blocks_size = src_blocks * 512;
uint32_t size = sizeof(mii_dd_overlay_header_t) + bitmap_size + blocks_size;
char *filename = NULL;
char *suffix = strrchr(dd->file->pathname, '.');
if (suffix) {
asprintf(&filename, "%.*s.miov", (int)(suffix - dd->file->pathname), dd->file->pathname);
} else {
asprintf(&filename, "%s.miov", dd->file->pathname);
}
int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd == -1) {
fprintf(stderr, "%s: Failed to create overlay file %s: %s\n", __func__,
filename, strerror(errno));
fprintf(stderr, "%s: Allocating a RAM one, lost on quit!\n", __func__);
dd->overlay.file = mii_dd_file_in_ram(dd->dd, filename, size, O_RDWR);
} else {
ftruncate(fd, size);
dd->overlay.file = mii_dd_file_load(dd->dd, filename, O_RDWR);
}
if (fd != -1)
close(fd);
free(filename);
mii_dd_overlay_header_t h = {
.magic = FCC('M','I','O','V'),
.version = 1,
.size = src_blocks,
};
// hash the whole of the file, including header
MD5_CTX d5 = {};
MD5_Init(&d5);
MD5_Update(&d5, dd->file->start, dd->file->size);
MD5_Final(h.src_md5, &d5);
*((mii_dd_overlay_header_t *)dd->overlay.file->start) = h;
dd->overlay.file->map += sizeof(h) + bitmap_size;
dd->overlay.header = (mii_dd_overlay_header_t *)dd->overlay.file->start;
dd->overlay.bitmap = (uint64_t*)(dd->overlay.file->start + sizeof(h));
dd->overlay.blocks = dd->overlay.file->map;
return 0;
}
int
mii_dd_read(
mii_dd_t * dd,
struct mii_bank_t *bank,
uint16_t addr,
uint32_t blk,
uint16_t blockcount)
{
if (!dd || !dd->file || !dd->file->map)
return -1;
// printf("%s: %s read %d blocks at %d\n",
// __func__, dd->name, blockcount, blk);
if (dd->overlay.file) {
uint64_t *bitmap = dd->overlay.bitmap;
for (int i = 0; i < blockcount; i++) {
uint32_t b = blk + i;
// printf(" overlay block %4d : %016llx\n", b,
// (unsigned long long)be64toh(bitmap[b/64]));
if (b >= dd->overlay.header->size)
break;
if (be64toh(bitmap[b / 64]) & (1ULL << (63 - (b & 63)))) {
// printf("%s: reading %4d from overlay\n", __func__, b);
mii_bank_write( bank,
addr + (i * 512),
dd->overlay.blocks + (b * 512),
512);
} else {
mii_bank_write( bank,
addr + (i * 512),
dd->file->map + (b * 512),
512);
}
}
} else {
mii_bank_write(
bank,
addr, dd->file->map + blk * 512,
blockcount * 512);
}
return 0;
}
int
mii_dd_write(
mii_dd_t * dd,
struct mii_bank_t *bank,
uint16_t addr,
uint32_t blk,
uint16_t blockcount)
{
if (!dd || !dd->file || !dd->file->map)
return -1;
// printf("%s: %s write %d blocks at %d\n",
// __func__, dd->name, blockcount, blk);
mii_dd_overlay_prepare(dd);
if (dd->overlay.file) {
uint64_t *bitmap = dd->overlay.bitmap;
for (int i = 0; i < blockcount; i++) {
uint32_t b = blk + i;
if (b >= dd->overlay.header->size)
break;
bitmap[b / 64] = htobe64(
be64toh(bitmap[b/64]) | (1ULL << (63 - (b & 63))));
// printf("%s: writing %d to overlay map: %016llx\n", __func__, b,
// (unsigned long long)be64toh(bitmap[b/64]));
}
mii_bank_read(
bank,
addr, dd->overlay.blocks + blk * 512,
blockcount * 512);
} else {
mii_bank_read(
bank,
addr, dd->file->map + blk * 512,
blockcount * 512);
}
return 0;
}

124
src/format/mii_dd.h Normal file
View File

@ -0,0 +1,124 @@
/*
* mii_dd.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
struct mii_dd_t;
enum {
MII_DD_FILE_OVERLAY = 1,
MII_DD_FILE_RAM,
MII_DD_FILE_2MG = 5,
};
// a disk image file (or chunck of ram, if ramdisk is used)
typedef struct mii_dd_file_t {
struct mii_dd_file_t *next;
char * pathname;
uint8_t format;
uint8_t * start; // start of the file
uint8_t * map; // start of the blocks
int fd; // if fd >= 0, map is mmaped, otherwise it's malloced
uint32_t size;
struct mii_dd_t * dd;
} mii_dd_file_t;
/*
* Overlays are made to provide what looks like read/write on files, but
* without commiting any of the changes to the real primary file, instead
* alld the changed blocks are kept into a sparse file and reloaded when
* the 'real' block is read.
* That way you can keep your disk images fresh and clean, while having
* multiple version of them if you like.
*/
typedef union mii_dd_overlay_header_t {
struct {
uint32_t magic; // 'MIOV'
uint32_t version; // 1 for now
uint32_t flags; // unused for now
uint32_t size; // size in blocks of original file
uint8_t src_md5[16]; // md5 of the SOURCE disk
};
uint32_t raw[16];
} mii_dd_overlay_header_t;
typedef struct mii_dd_overlay_t {
mii_dd_overlay_header_t *header; // points to the file mapped in memory
uint64_t * bitmap; // usage bitmap
uint8_t * blocks; // raw block data
mii_dd_file_t *file; // overlay file mapping
} mii_dd_overlay_t;
struct mii_slot_t;
struct mii_dd_system_t;
// a disk drive, with a slot, a drive number, and a file
typedef struct mii_dd_t {
struct mii_dd_t *next;
struct mii_dd_system_t *dd;
const char * name; // ie "Disk ][ D:2"
uint8_t slot_id : 4, drive : 4;
struct mii_slot_t *slot;
unsigned int ro : 1, wp : 1, can_eject : 1;
mii_dd_file_t * file;
mii_dd_overlay_t overlay;
} mii_dd_t;
typedef struct mii_dd_system_t {
mii_dd_t * drive; // list of all drives on all slots
mii_dd_file_t * file; // list of all open files (inc overlays)
} mii_dd_system_t;
/*
* register drives with the system -- these are not allocated, they are
* statically defined in the driver code in their own structures
*/
void
mii_dd_register_drives(
mii_dd_system_t *dd,
mii_dd_t * drives,
uint8_t count );
int
mii_dd_drive_load(
mii_dd_t *dd,
mii_dd_file_t *file );
/*
* unmap, close and dispose of 'file', clear it from the drive, if any
*/
void
mii_dd_file_dispose(
mii_dd_system_t *dd,
mii_dd_file_t *file );
mii_dd_file_t *
mii_dd_file_load(
mii_dd_system_t *dd,
const char *filename,
uint16_t flags);
struct mii_bank_t;
// read blocks from blk into bank's address 'addr'
int
mii_dd_read(
mii_dd_t * dd,
struct mii_bank_t *bank,
uint16_t addr,
uint32_t blk,
uint16_t blockcount);
int
mii_dd_write(
mii_dd_t * dd,
struct mii_bank_t *bank,
uint16_t addr,
uint32_t blk,
uint16_t blockcount);

View File

@ -0,0 +1,95 @@
/*
* mii_disk_format.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <sys/stat.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include "mii_disk_format.h"
#define byte uint8_t
static const size_t nib_disksz = 232960;
static const size_t dsk_disksz = 143360;
extern DiskFormatDesc nib_insert(const char*, byte *, size_t);
extern DiskFormatDesc dsk_insert(const char *, byte *, size_t);
extern DiskFormatDesc empty_disk_desc;
int mmapfile(const char *fname, byte **buf, size_t *sz, int flags);
DiskFormatDesc disk_format_load(const char *path)
{
if (path == NULL) {
return empty_disk_desc;
}
byte *buf;
size_t sz;
int err = mmapfile(path, &buf, &sz, O_RDWR);
if (buf == NULL) {
DIE(1,"Couldn't load/mmap disk %s: %s\n",
path, strerror(err));
}
printf("%s loaded %s dz = %d\n", __func__, path, (int)sz);
if (sz == nib_disksz) {
return nib_insert(path, buf, sz);
} else if (sz == dsk_disksz) {
return dsk_insert(path, buf, sz);
} else {
DIE(2,"Unrecognized disk format for %s.\n", path);
}
}
int mmapfile(const char *fname, byte **buf, size_t *sz, int flags)
{
int err;
int fd;
*buf = NULL;
errno = 0;
fd = open(fname, flags);
if (fd < 0) {
return errno;
}
struct stat st;
errno = 0;
err = fstat(fd, &st);
if (err < 0) {
goto bail;
}
errno = 0;
int protect = PROT_READ;
int mflags = MAP_PRIVATE;
if (flags & O_RDWR || flags & O_WRONLY) {
protect |= PROT_WRITE;
mflags = MAP_SHARED;
}
*buf = mmap(NULL, st.st_size, protect, mflags, fd, 0);
if (*buf == NULL) {
err = errno;
goto bail;
}
close(fd); // safe to close now.
*sz = st.st_size;
return 0;
bail:
close(fd);
return err;
}

View File

@ -0,0 +1,35 @@
/*
* mii_disk_format.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
/********** FORMATS **********/
#define NUM_TRACKS 35
#define SECTOR_SIZE 256
typedef struct DiskFormatDesc DiskFormatDesc;
typedef struct DiskFormatDesc {
void *privdat;
bool writeprot;
unsigned int halftrack;
void (*spin)(DiskFormatDesc *, bool);
uint8_t (*read_byte)(DiskFormatDesc *);
void (*write_byte)(DiskFormatDesc *, uint8_t);
void (*eject)(DiskFormatDesc *);
} DiskFormatDesc;
DiskFormatDesc disk_format_load(const char *path);
#define WARN(...) fprintf(stderr, __VA_ARGS__)
#define INFO(...) fprintf(stderr, __VA_ARGS__)
#define DIE(code, ...) do { \
WARN(__VA_ARGS__); \
if (code) exit(code); \
} while(0)

100
src/format/nib.c Normal file
View File

@ -0,0 +1,100 @@
// format/nib.c
//
// Copyright (c) 2023 Micah John Cowan.
// This code is licensed under the MIT license.
// See the accompanying LICENSE file for details.
#include "mii_disk_format.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#define byte uint8_t
#define NIBBLE_TRACK_SIZE 6656
#define NIBBLE_SECTOR_SIZE 416
#define MAX_SECTORS 16
struct nibprivdat {
const char *path;
byte *buf;
int bytenum;
uint64_t dirty_tracks;
};
static const struct nibprivdat datinit = { 0 };
static const size_t nib_disksz = 232960;
static void spin(DiskFormatDesc *desc, bool b)
{
struct nibprivdat *dat = desc->privdat;
if (!b && dat->dirty_tracks != 0) {
// For now, sync the entire disk
errno = 0;
int err = msync(dat->buf, nib_disksz, MS_SYNC);
if (err < 0) {
DIE(1,"Couldn't sync to disk file %s: %s\n",
dat->path, strerror(errno));
}
dat->dirty_tracks = 0;
}
}
static byte read_byte(DiskFormatDesc *desc)
{
struct nibprivdat *dat = desc->privdat;
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
byte val = dat->buf[pos];
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
return val;
}
static void write_byte(DiskFormatDesc *desc, byte val)
{
struct nibprivdat *dat = desc->privdat;
if ((val & 0x80) == 0) {
// D2DBG("dodged write $%02X", val);
return; // must have high bit
}
dat->dirty_tracks |= 1 << (desc->halftrack/2);
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
//D2DBG("write byte $%02X at pos $%04zX", (unsigned int)val, pos);
dat->buf[pos] = val;
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
}
static void eject(DiskFormatDesc *desc)
{
// free dat->path and dat, and unmap disk image
struct nibprivdat *dat = desc->privdat;
(void) munmap(dat->buf, nib_disksz);
free((void*)dat->path);
free(dat);
}
DiskFormatDesc nib_insert(const char *path, byte *buf, size_t sz)
{
if (sz != nib_disksz) {
DIE(0,"Wrong disk image size for %s:\n", path);
DIE(1," Expected %zu, got %zu.\n", nib_disksz, sz);
}
struct nibprivdat *dat = malloc(sizeof *dat);
*dat = datinit;
dat->buf = buf;
dat->path = strdup(path);
return (DiskFormatDesc){
.privdat = dat,
.spin = spin,
.read_byte = read_byte,
.write_byte = write_byte,
.eject = eject,
};
}

714
src/mii.c Normal file
View File

@ -0,0 +1,714 @@
/*
* mii.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mii_rom_iiee.h"
#include "mii.h"
#include "mii_bank.h"
#include "mii_video.h"
#include "mii_sw.h"
#include "mii_65c02.h"
#include "minipt.h"
mii_slot_drv_t * mii_slot_drv_list = NULL;
static const mii_bank_t _mii_banks_init[MII_BANK_COUNT] = {
[MII_BANK_MAIN] = {
.name = "MAIN",
.base = 0x0000,
.size = 0xd0, // 208 pages, 48KB
},
[MII_BANK_BSR] = {
.name = "BSR",
.base = 0xd000,
.size = 64,
},
[MII_BANK_BSR_P2] = {
.name = "BSR P2",
.base = 0xd000,
.size = 16,
},
[MII_BANK_AUX] = {
.name = "AUX",
.base = 0x0000,
.size = 0xd0, // 208 pages, 48KB
},
[MII_BANK_AUX_BSR] = {
.name = "AUX BSR",
.base = 0xd000,
.size = 64,
},
[MII_BANK_AUX_BSR_P2] = {
.name = "AUX BSR P2",
.base = 0xd000,
.size = 16,
},
[MII_BANK_ROM] = {
.name = "ROM",
.base = 0xc000,
.size = 0x40, // 64 pages, 16KB
.ro = 1,
},
[MII_BANK_CARD_ROM] = {
.name = "CARD ROM",
.base = 0xc100,
.size = 15,
.ro = 1,
},
};
#include "mii_65c02_ops.h"
#include "mii_65c02_disasm.h"
void
mii_dump_trace_state(
mii_t *mii)
{
mii_cpu_t * cpu = &mii->cpu;
mii_cpu_state_t s = mii->cpu_state;
printf("PC:%04X A:%02X X:%02X Y:%02X S:%02x #%d %c AD:%04X D:%02x %c ",
cpu->PC, cpu->A, cpu->X, cpu->Y, cpu->S, cpu->cycle,
s.sync ? 'I' : ' ', s.addr, s.data, s.w ? 'W' : 'R');
// display the S flags
static const char *s_flags = "CZIDBRVN";
for (int i = 0; i < 8; i++)
printf("%c", cpu->P.P[7-i] ? s_flags[7-i] : tolower(s_flags[7-i]));
if (s.sync) {
uint8_t op[16];
for (int i = 0; i < 4; i++) {
mii_mem_access(mii, mii->cpu.PC + i, op + i, false, false);
}
mii_op_t d = mii_cpu_op[op[0]];
printf(" ");
char dis[32];
mii_cpu_disasm_one(op, cpu->PC, dis, sizeof(dis),
MII_DUMP_DIS_DUMP_HEX);
printf(": %s", dis);
if (d.desc.branch) {
if (cpu->P.P[d.desc.s_bit] == d.desc.s_bit_value)
printf(" ; taken");
}
printf("\n");
} else
printf("\n");
}
void
mii_dump_run_trace(
mii_t *mii)
{
// walk all the previous PC values in mii->trace, and display a line
// of disassebly for all of them
for (int li = 0; li < MII_PC_LOG_SIZE; li++) {
int idx = (mii->trace.idx + li) & (MII_PC_LOG_SIZE - 1);
uint16_t pc = mii->trace.log[idx];
uint8_t op[16];
for (int i = 0; i < 4; i++)
mii_mem_access(mii, pc + i, op + i, false, false);
// mii_op_t d = mii_cpu_op[op[0]];
char dis[64];
mii_cpu_disasm_one(op, pc, dis, sizeof(dis),
MII_DUMP_DIS_PC | MII_DUMP_DIS_DUMP_HEX);
printf("%s\n", dis);
}
}
#define _SAME 0xf
static inline void
mii_page_set(
mii_t * mii,
uint8_t read,
uint8_t write,
uint8_t bank,
uint8_t end )
{
for (int i = bank; i <= end; i++) {
if (read != _SAME)
mii->mem[i].read = read;
if (write != _SAME)
mii->mem[i].write = write;
}
}
static inline uint8_t
mii_sw(
mii_t *mii,
uint16_t sw)
{
return mii_bank_peek(&mii->bank[MII_BANK_MAIN], sw);
}
static void
mii_page_table_update(
mii_t *mii)
{
if (!mii->mem_dirty)
return;
mii->mem_dirty = 0;
int altzp = mii_sw(mii, SWALTPZ);
int page2 = mii_sw(mii, SWPAGE2);
int store80 = mii_sw(mii, SW80STORE);
int hires = mii_sw(mii, SWHIRES);
int ramrd = mii_sw(mii, SWRAMRD);
int ramwrt = mii_sw(mii, SWRAMWRT);
int intcxrom = mii_sw(mii, SWINTCXROM);
int slotc3rom = mii_sw(mii, SWSLOTC3ROM);
if (mii->trace_cpu)
printf("%04x: page table update altzp:%02x page2:%02x store80:%02x hires:%02x ramrd:%02x ramwrt:%02x intcxrom:%02x slotc3rom:%02x\n",
mii->cpu.PC,
altzp, page2, store80, hires, ramrd, ramwrt, intcxrom, slotc3rom);
// clean slate
mii_page_set(mii, MII_BANK_MAIN, MII_BANK_MAIN, 0x00, 0xc0);
mii_page_set(mii, MII_BANK_ROM, MII_BANK_ROM, 0xc1, 0xff);
if (altzp)
mii_page_set(mii, MII_BANK_AUX, MII_BANK_AUX, 0x00, 0x01);
mii_page_set(mii,
ramrd ? MII_BANK_AUX : MII_BANK_MAIN,
ramwrt ? MII_BANK_AUX : MII_BANK_MAIN, 0x02, 0xbf);
if (store80) {
mii_page_set(mii,
page2 ? MII_BANK_AUX : MII_BANK_MAIN,
page2 ? MII_BANK_AUX : MII_BANK_MAIN, 0x04, 0x07);
if (hires)
mii_page_set(mii,
page2 ? MII_BANK_AUX : MII_BANK_MAIN,
page2 ? MII_BANK_AUX : MII_BANK_MAIN, 0x20, 0x3f);
}
if (!intcxrom)
mii_page_set(mii, MII_BANK_CARD_ROM, _SAME, 0xc1, 0xc7);
mii_page_set(mii,
slotc3rom ? MII_BANK_CARD_ROM : MII_BANK_ROM, _SAME, 0xc3, 0xc3);
mii_page_set(mii,
mii->bsr_mode.read ?
altzp ? MII_BANK_AUX_BSR : MII_BANK_BSR :
MII_BANK_ROM,
mii->bsr_mode.write ?
altzp ? MII_BANK_AUX_BSR : MII_BANK_BSR :
MII_BANK_ROM,
0xd0, 0xff);
// BSR P2
mii_page_set(mii,
mii->bsr_mode.read ?
(altzp ? MII_BANK_AUX_BSR : MII_BANK_BSR) +
mii->bsr_mode.page2 : MII_BANK_ROM,
mii->bsr_mode.write ?
(altzp ? MII_BANK_AUX_BSR : MII_BANK_BSR) +
mii->bsr_mode.page2 : MII_BANK_ROM,
0xd0, 0xdf);
}
void
mii_set_sw_override(
mii_t *mii,
uint16_t sw_addr,
mii_bank_access_cb cb,
void *param)
{
if (!mii->soft_switches_override)
mii->soft_switches_override = calloc(256,
sizeof(*mii->soft_switches_override));
sw_addr &= 0xff;
mii->soft_switches_override[sw_addr].cb = cb;
mii->soft_switches_override[sw_addr].param = param;
}
static bool
mii_access_soft_switches(
mii_t *mii,
uint16_t addr,
uint8_t * byte,
bool write)
{
if (!(addr >= 0xc000 && addr <= 0xc0ff) || addr == 0xcfff)
return false;
bool res = false;
uint8_t on = 0;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
/*
* This allows driver (titan accelerator etc) to have their own
* soft switches, and override/supplement any default ones.
*/
if (mii->soft_switches_override && mii->soft_switches_override[addr & 0xff].cb) {
res = mii->soft_switches_override[addr & 0xff].cb(
main, mii->soft_switches_override[addr & 0xff].param,
addr, byte, write);
if (res)
return res;
}
switch (addr) {
case 0xc090 ... 0xc0ff: {
res = true;
int slot = ((addr >> 4) & 7) - 1;
#if 0
printf("SLOT %d addr %04x write %d %02x drv %s\n",
slot, addr, write, *byte,
mii->slot[slot].drv ? mii->slot[slot].drv->name : "none");
#endif
if (mii->slot[slot].drv) {
on = mii->slot[slot].drv->access(mii,
&mii->slot[slot], addr, *byte, write);
if (!write)
*byte = on;
}
} break;
case 0xc080 ... 0xc08f: {
res = true;
uint8_t mode = addr & 0x0f;
static const int write_modes[4] = { 0, 1, 0, 1, };
static const int read_modes[4] = { 1, 0, 0, 1, };
uint8_t rd = read_modes[mode & 3];
uint8_t wr = write_modes[mode & 3];
mii->bsr_mode.write = wr;
mii->bsr_mode.read = rd;
mii->bsr_mode.page2 = mode & 0x08 ? 0 : 1;
mii->mem_dirty = 1;
if (mii->trace_cpu)
printf("%04x: BSR mode addr %04x:%02x read:%s write:%s %s altzp:%02x\n",
mii->cpu.PC, addr,
mode,
rd ? "BSR" : "ROM",
wr ? "BSR" : "ROM",
mii->bsr_mode.page2 ? "page2" : "page1",
mii_sw(mii, SWALTPZ));
} break;
case 0xcfff:
res = true;
mii->mem_dirty = 1;
printf("%s TODO reset SLOT roms\n", __func__);
break;
case SWPAGE2OFF:
case SWPAGE2ON:
res = true;
mii_bank_poke(main, SWPAGE2, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWHIRESOFF:
case SWHIRESON:
res = true;
mii_bank_poke(main, SWHIRES, (addr & 1) << 7);
mii->mem_dirty = 1;
// printf("HIRES %s\n", (addr & 1) ? "ON" : "OFF");
break;
case SWSPEAKER:
res = true;
mii_speaker_click(&mii->speaker);
break;
case 0xc068:
res = true;
// IIgs register, read by prodos tho
break;
}
if (res && !mii->mem_dirty)
return res;
if (write) {
switch (addr) {
case SW80STOREOFF:
case SW80STOREON:
res = true;
mii_bank_poke(main, SW80STORE, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWRAMRDOFF:
case SWRAMRDON:
res = true;
mii_bank_poke(main, SWRAMRD, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWRAMWRTOFF:
case SWRAMWRTON:
res = true;
mii_bank_poke(main, SWRAMWRT, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWALTPZOFF:
case SWALTPZON:
res = true;
mii_bank_poke(main, SWALTPZ, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWINTCXROMOFF:
case SWINTCXROMON:
res = true;
mii_bank_poke(main, SWINTCXROM, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWSLOTC3ROMOFF:
case SWSLOTC3ROMON:
res = true;
mii_bank_poke(main, SWSLOTC3ROM, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
}
} else {
switch (addr) {
case SWBSRBANK2:
*byte = mii->bsr_mode.page2 ? 0x80 : 0;
res = true;
break;
case SWBSRREADRAM:
*byte = mii->bsr_mode.read ? 0x80 : 0;
res = true;
break;
case SWRAMRD:
case SWRAMWRT:
case SW80STORE:
case SWINTCXROM:
case SWALTPZ:
case SWSLOTC3ROM:
res = true;
*byte = mii_bank_peek(main, addr);
break;
case 0xc020: // toggle TAPE output ?!?!
case 0xc064: // Analog Input 0 (paddle 0)
case 0xc065: // Analog Input 1 (paddle 1)
case 0xc079: // Analog Input Reset
res = true;
break;
case 0xc068:
res = true;
// IIgs register, read by prodos tho
break;
}
}
if (!res) {
// printf("%s addr %04x write %d %02x\n", __func__, addr, write, *byte);
// mii->state = MII_STOPPED;
}
mii_page_table_update(mii);
return res;
}
static bool
mii_access_keyboard(
mii_t *mii,
uint16_t addr,
uint8_t * byte,
bool write)
{
bool res = false;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
switch (addr) {
case SWKBD:
if (!write) {
/* If fifo not empty, peek at the next key to process, it already
* has the 0x80 bit on -- otherwise, return 0 */
res = true;
#if 1
*byte = mii_bank_peek(main, SWKBD);
#else
if (mii_key_fifo_isempty(&mii->keys))
*byte = 0;
else
*byte = mii_key_fifo_read_at(&mii->keys, 0).key;
#endif
}
break;
case SWAKD:
res = true;
#if 1
{
uint8_t r = mii_bank_peek(main, SWAKD);
if (!write)
*byte = r;
r &= 0x7f;
mii_bank_poke(main, SWAKD, r);
mii_bank_poke(main, SWKBD, r);
}
#else
// if (write) {
/* clear latch, and replace it immediately with the new key
* if there's one in the FIFO */
if (!mii_key_fifo_isempty(&mii->keys)) {
mii_key_t k = mii_key_fifo_read(&mii->keys);
mii_bank_poke(main, SWAKD, k.key);
} else
mii_bank_poke(main, SWAKD,
mii_bank_peek(main, SWAKD) & ~0x80);
// } else
if (!write)
*byte = mii_bank_peek(main, SWAKD);
#endif
break;
case 0xc061 ... 0xc063: // Push Button 0, 1, 2 (Apple Keys)
res = true;
if (!write)
*byte = mii_bank_peek(main, addr);
break;
}
return res;
}
void
mii_keypress(
mii_t *mii,
uint8_t key)
{
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
key |= 0x80;
mii_bank_poke(main, SWAKD, key);
mii_bank_poke(main, SWKBD, key);
#if 0
mii_key_t k = {
.key = key | 0x80,
};
if (!mii_key_fifo_isfull(&mii->keys))
mii_key_fifo_write(&mii->keys, k);
else {
printf("%s key fifo full\n", __func__);
}
#endif
}
void
mii_init(
mii_t *mii )
{
memset(mii, 0, sizeof(*mii));
mii->speed = 1.0;
for (int i = 0; i < MII_BANK_COUNT; i++)
mii->bank[i] = _mii_banks_init[i];
mii->bank[MII_BANK_ROM].mem = (uint8_t*)&iie_enhanced_rom_bin[0];
mii->cpu.trap = MII_TRAP;
mii_reset(mii, true);
mii_speaker_init(mii, &mii->speaker);
mii->cpu_state = mii_cpu_init(&mii->cpu);
for (int i = 0; i < 7; i++)
mii->slot[i].id = i;
}
void
mii_prepare(
mii_t *mii,
uint32_t flags )
{
printf("%s driver table\n", __func__);
mii_slot_drv_t * drv = mii_slot_drv_list;
while (drv) {
printf("%s driver: %s\n", __func__, drv->name);
if (drv->probe && drv->probe(mii, flags)) {
printf("%s %s probe done\n", __func__, drv->name);
}
drv = drv->next;
}
}
void
mii_reset(
mii_t *mii,
bool cold)
{
// printf("%s cold %d\n", __func__, cold);
mii->cpu_state.reset = 1;
mii->bsr_mode.write = 1;
mii->bsr_mode.read = 0;
mii->bsr_mode.page2 = 1;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_poke(main, SWSLOTC3ROM, 0);
mii_bank_poke(main, SWRAMRD, 0);
mii_bank_poke(main, SWRAMWRT, 0);
mii_bank_poke(main, SWALTPZ, 0);
mii_bank_poke(main, SW80STORE, 0);
mii_bank_poke(main, SW80COL, 0);
mii->mem_dirty = 1;
if (cold) {
/* these HAS to be reset in that state somehow */
mii_bank_poke(main, SWINTCXROM, 0);
uint8_t z[2] = {0x55,0x55};
mii_bank_write(main, 0x3f2, z, 2);
}
mii->mem_dirty = 1;
mii_page_table_update(mii);
for (int i = 0; i < 7; i++) {
if (mii->slot[i].drv && mii->slot[i].drv->reset)
mii->slot[i].drv->reset(mii, &mii->slot[i]);
}
}
void
mii_mem_access(
mii_t *mii,
uint16_t addr,
uint8_t * d,
bool wr,
bool do_sw)
{
if (!do_sw && addr >= 0xc000 && addr <= 0xc0ff)
return;
uint8_t done =
mii_access_keyboard(mii, addr, d, wr) ||
mii_access_video(mii, addr, d, wr) ||
mii_access_soft_switches(mii, addr, d, wr);
if (!done) {
uint8_t page = addr >> 8;
if (wr) {
uint8_t m = mii->mem[page].write;
mii_bank_t * b = &mii->bank[m];
if (b->ro) {
// printf("%s write to RO bank %s %04x:%02x\n",
// __func__, b->name, addr, *d);
} else
mii_bank_write(b, addr, d, 1);
} else {
uint8_t m = mii->mem[page].read;
mii_bank_t * b = &mii->bank[m];
*d = mii_bank_peek(b, addr);
}
}
}
static void
_mii_handle_trap(
mii_t *mii)
{
// printf("%s TRAP hit PC: %04x\n", __func__, mii->cpu.PC);
mii->cpu_state.sync = 1;
mii->cpu_state.trap = 0;
mii->cpu.state = NULL;
uint8_t trap = mii_read_one(mii, mii->cpu.PC);
mii->cpu.PC += 1;
// printf("%s TRAP %02x return PC %04x\n", __func__, trap, mii->cpu.PC);
if (mii->trap.map & (1 << trap)) {
if (mii->trap.trap[trap].cb)
mii->trap.trap[trap].cb(mii, trap);
} else {
printf("%s TRAP %02x not handled\n", __func__, trap);
mii->state = MII_STOPPED;
}
// mii->state = MII_STOPPED;
}
uint8_t
mii_register_trap(
mii_t *mii,
mii_trap_handler_cb cb)
{
if (mii->trap.map == 0xffff) {
printf("%s no more traps!!\n", __func__);
return 0xff;
}
for (int i = 0; i < (int)sizeof(mii->trap.map) * 8; i++) {
if (!(mii->trap.map & (1 << i))) {
mii->trap.map |= 1 << i;
mii->trap.trap[i].cb = cb;
return i;
}
}
return 0xff;
}
void
mii_run(
mii_t *mii)
{
/* this runs all cycles for one instruction */
do {
if (mii->trace_cpu)
mii_dump_trace_state(mii);
mii->cpu_state = mii_cpu_run(&mii->cpu, mii->cpu_state);
mii_video_run(mii);
mii_speaker_run(&mii->speaker);
// extract 16-bit address from pin mask
const uint16_t addr = mii->cpu_state.addr;
const uint8_t data = mii->cpu_state.data;
int wr = mii->cpu_state.w;
uint8_t d = data;
if (mii->debug.bp_map) {
for (int i = 0; i < (int)sizeof(mii->debug.bp_map) * 8; i++) {
if (!(mii->debug.bp_map & (1 << i)))
continue;
if (addr >= mii->debug.bp[i].addr &&
addr < mii->debug.bp[i].addr + mii->debug.bp[i].size) {
if (((mii->debug.bp[i].kind & MII_BP_R) && !wr) ||
((mii->debug.bp[i].kind & MII_BP_W) && wr)) {
if (1 || !mii->debug.bp[i].silent) {
printf("BREAKPOINT %d at %04x PC:%04x\n",
i, addr, mii->cpu.PC);
mii_dump_run_trace(mii);
mii_dump_trace_state(mii);
mii->state = MII_STOPPED;
}
}
if (!(mii->debug.bp[i].kind & MII_BP_STICKY))
mii->debug.bp_map &= ~(1 << i);
mii->debug.bp[i].kind |= MII_BP_HIT;
}
}
}
mii_mem_access(mii, addr, &d, wr, true);
if (!wr)
mii->cpu_state.data = d;
if (mii->cpu_state.trap) {
_mii_handle_trap(mii);
}
} while (!(mii->cpu_state.sync));
mii->cycles += mii->cpu.cycle;
// log PC for the running disassembler display
mii->trace.log[mii->trace.idx] = mii->cpu.PC;
mii->trace.idx = (mii->trace.idx + 1) & (MII_PC_LOG_SIZE - 1);
for (int i = 0; i < 7; i++) {
if (mii->slot[i].drv && mii->slot[i].drv->run)
mii->slot[i].drv->run(mii, &mii->slot[i]);
}
}
//! Read one byte from and addres, using the current memory mapping
uint8_t
mii_read_one(
mii_t *mii,
uint16_t addr)
{
uint8_t d = 0;
mii_mem_access(mii, addr, &d, 0, false);
return d;
}
//! Read a word from addr, using current memory mapping (little endian)
uint16_t
mii_read_word(
mii_t *mii,
uint16_t addr)
{
uint8_t d = 0;
uint16_t res = 0;
mii_mem_access(mii, addr, &d, 0, false);
res = d;
mii_mem_access(mii, addr + 1, &d, 0, false);
res |= d << 8;
return res;
}
/* same accessors, for write
*/
void
mii_write_one(
mii_t *mii,
uint16_t addr,
uint8_t d)
{
mii_mem_access(mii, addr, &d, 1, false);
}
void
mii_write_word(
mii_t *mii,
uint16_t addr,
uint16_t w)
{
uint8_t d = w;
mii_mem_access(mii, addr, &d, 1, false);
d = w >> 8;
mii_mem_access(mii, addr + 1, &d, 1, false);
}

274
src/mii.h Normal file
View File

@ -0,0 +1,274 @@
/*
* mii.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "mii_65c02.h"
#include "fifo_declare.h"
#include "mii_dd.h"
#include "mii_bank.h"
#include "mii_slot.h"
#include "mii_video.h"
#include "mii_speaker.h"
#include "mii_mouse.h"
enum {
MII_BANK_MAIN = 0, // main 48K address space
MII_BANK_BSR, // 0xd000 - 0xffff bank switched RAM 16KB
MII_BANK_BSR_P2, // 0xd000 - 0xe000 bank switched RAM aux 4KB
MII_BANK_AUX, // aux 48K address space (80 cols card)
MII_BANK_AUX_BSR, // 0xd000 - 0xffff bank switched RAM aux 16KB
MII_BANK_AUX_BSR_P2, // 0xd000 - 0xe000 bank switched RAM aux 4KB (aux bank)
MII_BANK_ROM, // 0xc000 - 0xffff 16K ROM
MII_BANK_CARD_ROM, // 0xc100 - 0xcfff Card ROM access
MII_BANK_COUNT,
};
typedef void (*mii_trap_handler_cb)(
mii_t * mii,
uint8_t trap);
typedef struct mii_trap_t {
uint16_t map;
struct {
mii_trap_handler_cb cb;
} trap[16];
} mii_trap_t;
// state of the emulator
enum {
MII_RUNNING = 0, // default
MII_STOPPED,
MII_STEP,
};
enum {
MII_BP_PC = (1 << 0), // breakpoint on PC
MII_BP_W = (1 << 1), // breakpoint on write
MII_BP_R = (1 << 2), // breakpoint on read
MII_BP_HIT = (1 << 3), // breakpoint was hit
MII_BP_SILENT = (1 << 4), // don't dump state (used for the 'next' command)
MII_BP_STICKY = (1 << 7), // breakpoint is sticky (rearms itself)
};
#define MII_PC_LOG_SIZE 16
/*
* this keeps track of the last few PC values, for the debugger
*/
typedef struct mii_trace_t {
uint16_t log[MII_PC_LOG_SIZE];
uint8_t idx;
// when in MII_STEP, do not fall back to MII_STOPPED until these
// run out
uint32_t step_inst;
} mii_trace_t;
/*
* principal emulator state, for a faceless emulation
*/
typedef struct mii_t {
unsigned int state;
__uint128_t cycles;
/* this is the video frame/VBL rate vs 60hz, default to 1.0 */
float speed;
float speed_current; // calculated speed
mii_cpu_t cpu;
mii_cpu_state_t cpu_state;
/*
* bank index for each memory page number, this is recalculated
* everytime a soft switch is triggered
*/
struct {
uint8_t read : 4, write : 4;
} mem[256];
int mem_dirty; // recalculate mem[] on next access
struct {
int write, read, page2;
} bsr_mode;
mii_trace_t trace;
int trace_cpu;
mii_trap_t trap;
/*
* Used for debugging only
*/
struct {
uint16_t bp_map;
struct {
uint32_t kind : 8,
addr : 16,
size : 8,
silent : 1;
} bp[16];
} debug;
mii_bank_t bank[MII_BANK_COUNT];
// the page c000 can have individual callbacks to override/supplement
// existing default behaviour. This is currently used for the titan
// accelerator 'card'
mii_bank_access_t * soft_switches_override;
mii_slot_t slot[7];
mii_video_t video;
mii_speaker_t speaker;
mii_mouse_t mouse;
mii_dd_system_t dd;
} mii_t;
enum {
MII_INIT_NSC = (1 << 0), // Install no slot clock
MII_INIT_TITAN = (1 << 1), // Install Titan 'card'
MII_INIT_DEFAULT = MII_INIT_NSC,
};
/*
* Call this first, to initialize the emulator state
* This doesn't initializes any driver.
*/
void
mii_init(
mii_t *mii );
/*
* Call this to prepare the emulator, instantiate and install drivers
* etc. Presumably after you have used mii_argv_parse or loaded a config
* file to set up the drivers.
* flags is a combination of MII_INIT_*
*/
void
mii_prepare(
mii_t *mii,
uint32_t flags );
/*
* Parses arguments until in finds one that isn't for mii, and returns
* the index of that argument in *index.
* Return value is 0 if there's an argument that wasn't handled or 1
* if all the arguments were parsed (and *index == argc)
* mii parameter is the state AFTER mii_init() has been called.
*
* ioFlags is a combination of MII_INIT_*, it will be updated with the
* flags found in the arguments. Pass this to mii_prepare()
*/
int
mii_argv_parse(
mii_t *mii,
int argc,
const char *argv[],
int *index,
uint32_t *ioFlags );
/*
* Locate driver_name, and attempt to register it with slot_id slot.
* Returns 0 on success, -1 on failure
*/
int
mii_slot_drv_register(
mii_t *mii,
uint8_t slot_id,
const char *driver_name);
/* returns the driver registered for slot slot_id (or NULL) */
mii_slot_drv_t *
mii_slot_drv_get(
mii_t *mii,
uint8_t slot_id);
/*
* Reset the emulator, cold reset if cold is true, otherwise warm reset
*/
void
mii_reset(
mii_t *mii,
bool cold);
/* Execute one instruction, respecting breakpoints, runs the video code,
* Check for traps and other associated debug stuff.
*/
void
mii_run(
mii_t *mii);
// this one is thread safe, there's a FIFO behind it.
void
mii_keypress(
mii_t *mii,
uint8_t key);
/* read a byte as the processor would (ex softwitches!), this will
* respect the status of slot ROMs, language card, 80 columns etc */
uint8_t
mii_read_one(
mii_t *mii,
uint16_t addr);
/* write a byte as the processor would (ex softwitches!), this will
* respect the status of slot ROMs, language card, 80 columns etc */
void
mii_write_one(
mii_t *mii,
uint16_t addr,
uint8_t d);
/* read a word as the processor would (ex softwitches!), this will
* respect the status of slot ROMs, language card, 80 columns etc */
uint16_t
mii_read_word(
mii_t *mii,
uint16_t addr);
/* write a word as the processor would (ex softwitches!), this will
* respect the status of slot ROMs, language card, 80 columns etc */
void
mii_write_word(
mii_t *mii,
uint16_t addr,
uint16_t w);
/* lower level call to access memory -- this one can trigger softswitches
* if specified. Otherwise behaves as the previous ones, one byte at a time
*/
void
mii_mem_access(
mii_t *mii,
uint16_t addr,
uint8_t * byte,
bool write,
bool do_sw);
/* register a callback to call when a specific soft switches is hit,
* this allows overriding/supplementing/tracing access to sw.
*/
void
mii_set_sw_override(
mii_t *mii,
uint16_t sw_addr,
mii_bank_access_cb cb,
void *param);
void
mii_dump_trace_state(
mii_t *mii);
void
mii_dump_run_trace(
mii_t *mii);
extern mii_slot_drv_t * mii_slot_drv_list;
#define MI_DRIVER_REGISTER(_mii_driver)\
__attribute__((constructor,unused)) \
static void _mii_register_##_mii_driver() { \
_mii_driver.next = mii_slot_drv_list; \
mii_slot_drv_list = &_mii_driver; \
}
#define MII_TRAP 0xdbfb
/*
* Request a trap ID for the given callback. Calling code is responsible
* for setting up the trap using the 2 magic NOPs in sequence. See mii_smartport.c
* for an example.
*/
uint8_t
mii_register_trap(
mii_t *mii,
mii_trap_handler_cb cb);

581
src/mii_65c02.c Normal file
View File

@ -0,0 +1,581 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "minipt.h"
#include "mii_65c02.h"
#define MII_CPU_65C02_IMPL
#include "mii_65c02_ops.h"
mii_cpu_state_t
mii_cpu_init(
mii_cpu_t *cpu )
{
mii_cpu_state_t s = {
.addr = 0,
.reset = 1,
};
cpu->state = NULL;
return s;
}
#define _FETCH(_val) { \
s.addr = _val; s.w = 0; cpu->cycle++; \
pt_yield(cpu->state); \
} while (0);
#define _STORE(_addr, _val) { \
s.addr = _addr; s.data = _val; s.w = 1; cpu->cycle++; \
pt_yield(cpu->state); \
} while (0);
#define _SET_P(_byte) { \
for (int _pi = 0; _pi < 8; _pi++) \
cpu->P.P[_pi] = _pi == B_B || _pi == B_X || \
((_byte) & (1 << _pi)); \
}
#define _NZC(_val) { \
uint16_t v = (_val); \
cpu->P.N = !!(v & 0x80); \
cpu->P.Z = (v & 0xff) == 0; \
cpu->P.C = !!(v & 0xff00); \
} while (0)
#define _NZ(_val) { \
uint16_t v = (_val); \
cpu->P.N = !!(v & 0x80); \
cpu->P.Z = (v & 0xff) == 0; \
} while (0)
#define _C(_val) { \
cpu->P.C = !!(_val); \
} while (0)
mii_cpu_state_t
mii_cpu_run(
mii_cpu_t *cpu,
mii_cpu_state_t s)
{
mii_op_desc_t d = mii_cpu_op[cpu->IR].desc;
pt_start(cpu->state);
next_instruction:
if (s.reset) {
s.reset = 0;
_FETCH(0xfffc); cpu->_P = s.data;
_FETCH(0xfffd); cpu->_P |= s.data << 8;
cpu->PC = cpu->_P;
cpu->S = 0xFF;
_SET_P(0);
}
if (s.irq) {
s.irq = 0;
cpu->P.P[B_B] = cpu->IRQ == 2;
cpu->IRQ = 1;
cpu->_D = cpu->PC + 1;
_STORE(0x0100 | cpu->S--, cpu->_D >> 8);
_STORE(0x0100 | cpu->S--, cpu->_D & 0xff);
uint8_t p = 0;
for (int i = 0; i < 8; i++)
p |= (i == B_X || cpu->P.P[i]) << i;
_STORE(0x0100 | cpu->S--, p);
cpu->P.P[B_I] = 1;
}
if (cpu->IRQ) {
cpu->IRQ = 0;
_FETCH(0xfffe); cpu->_P = s.data;
_FETCH(0xffff); cpu->_P |= s.data << 8;
cpu->PC = cpu->_P;
}
s.sync = 1;
// we dont' reset the cycle here, that way calling code has a way of knowing
// how many cycles were used by the previous instruction
_FETCH(cpu->PC);
s.sync = 0;
cpu->cycle = 0;
cpu->PC++;
cpu->IR = s.data;
d = mii_cpu_op[cpu->IR].desc;
cpu->ir_log = (cpu->ir_log << 8) | cpu->IR;
s.trap = cpu->trap && (cpu->ir_log & 0xffff) == cpu->trap;
if (s.trap)
cpu->ir_log = 0;
switch (d.mode) {
case IMM:
_FETCH(cpu->PC++); cpu->_D = s.data;
break;
case BRANCH: // BEQ/BNE etc
case ZP_REL: // $(xx)
_FETCH(cpu->PC++); cpu->_P = s.data;
break;
case ZP_X: // $xx,X
_FETCH(cpu->PC++); cpu->_P = (s.data + cpu->X) & 0xff;
break;
case ZP_Y: // $xx,Y
_FETCH(cpu->PC++); cpu->_P = (s.data + cpu->Y) & 0xff;
break;
case ABS: { // $xxxx
_FETCH(cpu->PC++); cpu->_P = s.data;
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
} break;
case ABS_X: { // $xxxx,X
_FETCH(cpu->PC++); cpu->_P = s.data;
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
cpu->_P += cpu->X;
if ((cpu->_P & 0xff00) != (s.data << 8))
cpu->cycle++;
} break;
case ABS_Y: { // $xxxx,Y
_FETCH(cpu->PC++); cpu->_P = s.data;
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
cpu->_P += cpu->Y;
if ((cpu->_P & 0xff00) != (s.data << 8))
cpu->cycle++;
} break;
case IND_X: { // ($xx,X)
_FETCH(cpu->PC++); cpu->_D = s.data;
cpu->_D += cpu->X;
_FETCH(cpu->_D & 0xff); cpu->_P = s.data;
cpu->_D++;
_FETCH(cpu->_D & 0xff); cpu->_P |= s.data << 8;
} break;
case IND_Y: { // ($xx),Y
_FETCH(cpu->PC++); cpu->_D = s.data;
_FETCH(cpu->_D); cpu->_P = s.data;
_FETCH((cpu->_D + 1) & 0xff);
cpu->_P |= s.data << 8;
cpu->_P += cpu->Y;
} break;
case IND: { // ($xxxx)
_FETCH(cpu->PC++); cpu->_D = s.data;
_FETCH(cpu->PC++); cpu->_D |= s.data << 8;
_FETCH(cpu->_D); cpu->_P = s.data;
_FETCH(cpu->_D + 1); cpu->_P |= s.data << 8;
} break;
case IND_Z: { // ($xx)
_FETCH(cpu->PC++); cpu->_D = s.data;
_FETCH(cpu->_D); cpu->_P = s.data;
_FETCH(cpu->_D + 1); cpu->_P |= s.data << 8;
} break;
case IND_AX: { // ($xxxx,X)
_FETCH(cpu->PC++); cpu->_D = s.data;
_FETCH(cpu->PC++); cpu->_D |= s.data << 8;
cpu->_D += cpu->X;
if ((cpu->_D & 0xff00) != (s.data << 8))
cpu->cycle++;
_FETCH(cpu->_D); cpu->_P = s.data;
_FETCH(cpu->_D + 1); cpu->_P |= s.data << 8;
} break;
}
if (d.r) {
_FETCH(cpu->_P);
cpu->_D = s.data;
}
switch (cpu->IR) {
case 0x69: case 0x65: case 0x75: case 0x6D: case 0x7D:
case 0x79: case 0x61: case 0x71: case 0x72:
{ // ADC
// Handle adding in BCD with bit D
if (cpu->P.D) {
uint8_t lo = (cpu->A & 0x0f) + (cpu->_D & 0x0f) + !!cpu->P.C;
if (lo > 9) lo += 6;
uint8_t hi = (cpu->A >> 4) + (cpu->_D >> 4) + (lo > 0x0f);
cpu->P.Z = ((uint8_t)(cpu->A + cpu->_D + cpu->P.C)) == 0;
// that is 6502 behaviour
// cpu->P.N = !!(hi & 0xf8);
cpu->P.V = !!((!((cpu->A ^ cpu->_D) & 0x80) &&
((cpu->A ^ (hi << 4))) & 0x80));
if (hi > 9) hi += 6;
cpu->P.C = hi > 15;
cpu->A = (hi << 4) | (lo & 0x0f);
// THAT is 65c02 behaviour
cpu->P.N = !!(cpu->A & 0x80);
} else {
uint16_t sum = cpu->A + cpu->_D + !!cpu->P.C;
cpu->P.V = cpu->P.C = 0;
if (~(cpu->A ^ cpu->_D) & (cpu->A ^ sum) & 0x80)
cpu->P.V = 1;
_NZC(sum);
cpu->A = sum;
}
} break;
case 0x29: case 0x25: case 0x35: case 0x2D: case 0x3D:
case 0x39: case 0x21: case 0x31: case 0x32:
{ // AND
cpu->A &= cpu->_D;
_NZ(cpu->A);
} break;
case 0x0A:
{ // ASL
cpu->P.C = !!(cpu->A & 0x80);
cpu->A <<= 1;
_NZ(cpu->A);
} break;
case 0x06: case 0x16: case 0x0E: case 0x1E:
{ // ASL
cpu->P.C = !!(cpu->_D & 0x80);
cpu->_D <<= 1;
_NZ(cpu->_D);
} break;
case 0x0f: case 0x1f: case 0x2f: case 0x3f: case 0x4f:
case 0x5f: case 0x6f: case 0x7f: case 0x8f: case 0x9f:
case 0xaf: case 0xbf: case 0xcf: case 0xdf: case 0xef:
case 0xff:
{ // BBR/BBS
_FETCH(cpu->PC++); // relative branch
if (((cpu->_D >> d.s_bit) & 1) == d.s_bit_value) {
cpu->_P = cpu->PC + (int8_t)cpu->_P;
if ((cpu->_P & 0xff00) != (cpu->PC & 0xff00))
cpu->cycle++;
cpu->PC = cpu->_P;
cpu->cycle++;
}
} break;
case 0x90: case 0xB0: case 0xF0: case 0x30:
case 0xD0: case 0x10: case 0x50: case 0x70:
{ // BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS
if (d.s_bit_value == cpu->P.P[d.s_bit]) {
cpu->_P = cpu->PC + (int8_t)cpu->_P;
if ((cpu->_P & 0xff00) != (cpu->PC & 0xff00))
cpu->cycle++;
cpu->PC = cpu->_P;
cpu->cycle++;
}
} break;
case 0x80:
{ // BRA
cpu->PC = cpu->PC + (int8_t)cpu->_P; cpu->cycle++;
} break;
case 0x89:
{ // BIT immediate -- does not change N & V!
cpu->P.Z = !(cpu->A & cpu->_D);
} break;
case 0x24: case 0x2C: case 0x34: case 0x3C:
{ // BIT
cpu->P.Z = !(cpu->A & cpu->_D);
cpu->P.N = !!(cpu->_D & 0x80);
cpu->P.V = !!(cpu->_D & 0x40);
} break;
case 0x00:
{ // BRK
s.irq = 1;
cpu->IRQ = 2; // IRQ interrupt
// cpu->P.P[B_D] = 0; // 65c02 clears this
} break;
case 0x18: case 0xD8: case 0x58: case 0xB8:
{ // CLC, CLD, CLI, CLV
cpu->P.P[d.s_bit] = 0;
} break;
case 0xC9: case 0xC5: case 0xD5: case 0xCD: case 0xDD:
case 0xD9: case 0xC1: case 0xD1: case 0xD2:
{ // CMP
cpu->P.C = !!(cpu->A >= cpu->_D);
uint8_t d = cpu->A - cpu->_D;
_NZ(d);
} break;
case 0xE0: case 0xE4: case 0xEC:
{ // CPX
cpu->P.C = !!(cpu->X >= cpu->_D);
uint8_t d = cpu->X - cpu->_D;
_NZ(d);
} break;
case 0xC0: case 0xC4: case 0xCC:
{ // CPY
cpu->P.C = !!(cpu->Y >= cpu->_D);
uint8_t d = cpu->Y - cpu->_D;
_NZ(d);
} break;
case 0x3A:
{ // DEC
_NZ(--cpu->A);
} break;
case 0xC6: case 0xD6: case 0xCE: case 0xDE:
{ // DEC
_NZ(--cpu->_D);
} break;
case 0xCA:
{ // DEX
_NZ(--cpu->X);
} break;
case 0x88:
{ // DEY
_NZ(--cpu->Y);
} break;
case 0x49: case 0x45: case 0x55: case 0x4D: case 0x5D:
case 0x59: case 0x41: case 0x51: case 0x52:
{ // EOR
cpu->A ^= cpu->_D;
_NZ(cpu->A);
} break;
case 0x1A:
{ // INC
_NZ(++cpu->A);
} break;
case 0xE6: case 0xF6: case 0xEE: case 0xFE:
{ // INC
_NZ(++cpu->_D);
} break;
case 0xE8:
{ // INX
_NZ(++cpu->X);
} break;
case 0xC8:
{ // INY
_NZ(++cpu->Y);
} break;
case 0x4C: case 0x6C: case 0x7C:
{ // JMP
cpu->PC = cpu->_P;
} break;
case 0x20:
{ // JSR
cpu->_D = cpu->PC - 1;
_STORE(0x0100 | cpu->S--, cpu->_D >> 8);
_STORE(0x0100 | cpu->S--, cpu->_D & 0xff);
cpu->PC = cpu->_P;
} break;
case 0xA9: case 0xA5: case 0xB5: case 0xAD: case 0xBD:
case 0xB9: case 0xA1: case 0xB1: case 0xB2:
{ // LDA
cpu->A = cpu->_D;
_NZ(cpu->A);
} break;
case 0xA2: case 0xA6: case 0xB6: case 0xAE: case 0xBE:
{ // LDX
cpu->X = cpu->_D;
_NZ(cpu->X);
} break;
case 0xA0: case 0xA4: case 0xB4: case 0xAC: case 0xBC:
{ // LDY
cpu->Y = cpu->_D;
_NZ(cpu->Y);
} break;
case 0x4A:
{ // LSR
cpu->P.C = !!(cpu->A & 0x01);
cpu->A >>= 1;
_NZ(cpu->A);
} break;
case 0x46: case 0x56: case 0x4E: case 0x5E:
{ // LSR
cpu->P.C = !!(cpu->_D & 0x01);
cpu->_D >>= 1;
_NZ(cpu->_D);
} break;
case 0xEA:
{ // NOP
} break;
case 0x09: case 0x05: case 0x15: case 0x0D: case 0x1D:
case 0x19: case 0x01: case 0x11: case 0x12:
{ // ORA
cpu->A |= cpu->_D;
_NZ(cpu->A);
} break;
case 0x48:
{ // PHA
_STORE(0x0100 | cpu->S--, cpu->A); cpu->cycle++;
} break;
case 0x08:
{ // PHP
uint8_t p = 0;
for (int i = 0; i < 8; i++)
p |= (i == B_X || cpu->P.P[i]) << i;
p |= (1 << B_B);
_STORE(0x0100 | cpu->S--, p); cpu->cycle++;
} break;
case 0xDA:
{ // PHX
_STORE(0x0100 | cpu->S--, cpu->X);cpu->cycle++;
} break;
case 0x5A:
{ // PHY
_STORE(0x0100 | cpu->S--, cpu->Y);cpu->cycle++;
} break;
case 0x68:
{ // PLA
_FETCH(0x0100 | ++cpu->S);cpu->cycle++;
cpu->A = s.data;cpu->cycle++;
_NZ(cpu->A);
} break;
case 0x28:
{ // PLP
_FETCH(0x0100 | ++cpu->S);cpu->cycle++;
_SET_P(s.data);cpu->cycle++;
} break;
case 0xFA:
{ // PLX
_FETCH(0x0100 | ++cpu->S);
cpu->X = s.data;
_NZ(cpu->X);
} break;
case 0x7A:
{ // PLY
_FETCH(0x0100 | ++cpu->S);
cpu->Y = s.data;
_NZ(cpu->Y);
} break;
case 0x2A:
{ // ROL
uint8_t c = cpu->P.C;
cpu->P.C = !!(cpu->A & 0x80);
cpu->A <<= 1;
cpu->A |= c;
_NZ(cpu->A);
} break;
case 0x26: case 0x36: case 0x2E: case 0x3E:
{ // ROL
uint8_t c = cpu->P.C;
cpu->P.C = !!(cpu->_D & 0x80);
cpu->_D <<= 1;
cpu->_D |= c;
_NZ(cpu->_D);
} break;
case 0x6A:
{ // ROR
uint8_t c = cpu->P.C;
cpu->P.C = !!(cpu->A & 0x01);
cpu->A >>= 1;
cpu->A |= c << 7;
_NZ(cpu->A);
} break;
case 0x66: case 0x76: case 0x6E: case 0x7E:
{ // ROR
uint8_t c = cpu->P.C;
cpu->P.C = !!(cpu->_D & 0x01);
cpu->_D >>= 1;
cpu->_D |= c << 7;
_NZ(cpu->_D);
} break;
case 0x40:
{ // RTI
_FETCH(0x0100 | ((++cpu->S) & 0xff));
for (int i = 0; i < 8; i++)
cpu->P.P[i] = i == B_B || (s.data & (1 << i));
cpu->P._R = 1;
_FETCH(0x0100 | ((++cpu->S) & 0xff));
cpu->_P = s.data;
_FETCH(0x0100 | ((++cpu->S) & 0xff));
cpu->_P = (s.data << 8) | (cpu->_P );
cpu->PC = cpu->_P;
} break;
case 0x60:
{ // RTS
_FETCH(0x0100 | ((++cpu->S) & 0xff));cpu->cycle++;
cpu->_P = s.data;
_FETCH(0x0100 | ((++cpu->S) & 0xff));cpu->cycle++;
cpu->_P |= s.data << 8;
cpu->PC = cpu->_P + 1; cpu->cycle++;
} break;
case 0xE9: case 0xE5: case 0xF5: case 0xED: case 0xFD:
case 0xF9: case 0xE1: case 0xF1: case 0xF2:
{ // SBC
// Handle subbing in BCD with bit D
if (cpu->P.D) {
uint8_t lo = (cpu->A & 0x0f) - (cpu->_D & 0x0f) - !cpu->P.C;
if (lo & 0x10) lo -= 6;
uint8_t hi = (cpu->A >> 4) - (cpu->_D >> 4) - (lo & 0x10);
if (hi & 0x10) hi -= 6;
cpu->P.Z = ((uint8_t)(cpu->A - cpu->_D - !cpu->P.C)) == 0;
cpu->P.N = !!(hi & 0x8);
cpu->P.V = !!(((cpu->A ^ cpu->_D) &
((cpu->A ^ (hi << 4))) & 0x80));
cpu->P.C = !(hi & 0x10);
cpu->A = (hi << 4) | (lo & 0x0f);
} else {
cpu->_D = (~cpu->_D) & 0xff;
uint16_t sum = cpu->A + cpu->_D + !!cpu->P.C;
cpu->P.V = cpu->P.C = 0;
if (~(cpu->A ^ cpu->_D) & (cpu->A ^ sum) & 0x80)
cpu->P.V = 1;
_NZC(sum);
cpu->A = sum;
}
} break;
case 0x38: case 0xF8: case 0x78:
{ // SEC, SED, SEI
cpu->P.P[d.s_bit] = 1;
} break;
case 0x85: case 0x95: case 0x8D: case 0x9D:
case 0x99: case 0x81: case 0x91: case 0x92:
{ // STA
cpu->_D = cpu->A;
} break;
case 0x86: case 0x96: case 0x8E:
{ // STX
cpu->_D = cpu->X;
} break;
case 0x84: case 0x94: case 0x8C:
{ // STY
cpu->_D = cpu->Y;
} break;
case 0x64: case 0x74: case 0x9C: case 0x9E:
{ // STZ
cpu->_D = 0;
} break;
case 0x14: case 0x1c:
{ // TRB
cpu->P.Z = !(cpu->A & cpu->_D);
cpu->_D &= ~cpu->A;
} break;
case 0x04: case 0x0c:
{ // TSB
cpu->P.Z = !(cpu->A & cpu->_D);
cpu->_D |= cpu->A;
} break;
case 0xAA:
{ // TAX
cpu->X = cpu->A;
_NZ(cpu->X);
} break;
case 0xA8:
{ // TAY
cpu->Y = cpu->A;
_NZ(cpu->Y);
} break;
case 0xBA:
{ // TSX
cpu->X = cpu->S;
_NZ(cpu->X);
} break;
case 0x8A:
{ // TXA
cpu->A = cpu->X;
_NZ(cpu->A);
} break;
case 0x9A:
{ // TXS
cpu->S = cpu->X;
} break;
case 0x98:
{ // TYA
cpu->A = cpu->Y;
_NZ(cpu->A);
} break;
case 0x07: case 0x17: case 0x27: case 0x37: case 0x47:
case 0x57: case 0x67: case 0x77: case 0x87: case 0x97:
case 0xA7: case 0xB7: case 0xC7: case 0xD7: case 0xE7:
case 0xF7:
{ // RMB/SMB
cpu->_D = (cpu->_D & ~(1 << d.s_bit)) | (d.s_bit_value << d.s_bit);
} break;
/* Apparently these NOPs use 3 bytes, according to the tests */
case 0x5c: case 0xdc: case 0xfc:
_FETCH(cpu->PC++);
/* Apparently these NOPs use 2 bytes, according to the tests */
case 0x02: case 0x22: case 0x42: case 0x62: case 0x82:
case 0xC2: case 0xE2: case 0x44: case 0x54: case 0xD4:
case 0xF4:
_FETCH(cpu->PC++); // consume that byte
break;
case 0xdb: case 0xfb:
// trap NOPs
break;
default:
printf("%04x %02x UNKNOWN INSTRUCTION\n", cpu->PC, cpu->IR);
// exit(1);
break;
}
if (d.w) {
_STORE(cpu->_P, cpu->_D);
}
goto next_instruction;
pt_end(cpu->state);
return s;
}

88
src/mii_65c02.h Normal file
View File

@ -0,0 +1,88 @@
#pragma once
#include <stdint.h>
/*
* State structure used to 'talk' to the CPU emulator.
* It works like this:
* mii_cpu_state_t s = { .reset = 1 };
* do {
* s = mii_cpu_run(cpu, s);
* if (s.w)
* write_memory(s.addr, s.data);
* else
* s.data = read_memory(s.addr);
* } while (1);
* s.sync will be 1 when we are fetching a new instruction
* s.reset will be 1 when the CPU is in reset (fetching vector)
* will turn to zero when done
* You check then the cpu->cycle to know what cycle you're in the current
* instruction.
*
* If you want to 'jump' to a new PC, you need to
* s.addr = new_pc;
* s.sync = 1;
* this will stop the current instruction and start fetching at new_pc
*
*/
typedef union mii_cpu_state_t {
struct {
uint32_t addr : 16,
data : 8,
w : 1,
sync : 1,
reset : 1,
irq : 1,
nmi : 1,
trap : 1;
};
uint32_t raw;
} mii_cpu_state_t;
/* CPU state machine */
typedef struct mii_cpu_t {
uint8_t A, X, Y, S;
/* internal 16bit registers for fetch etc
* _D is the 'data' register, used for 16bit operations
* _P is the 'pointer' register, used for 16bit addressing
* This is not set hard in stone, but typically addressing modes that
* are 'indirect' and load from memory will set _P and read in _D
* so the opcode doesn't have to handle s.data at all */
uint16_t _D, _P;
/* My experience with simavr shows that maintaining a 8 bits bitfield
* for a status register is a lot slower than having discrete flags
* and 'constructing' the matching 8 biots register when needed */
union {
struct {
uint8_t C, Z, I, D, B, _R, V, N;
};
uint8_t P[8];
} P;
uint16_t PC;
uint8_t IR;
uint8_t IRQ; // IRQ (0) or NMI (1) or BRK (2)
uint8_t cycle; // for current instruction
/* State of the CPU state machine */
void * state;
/* sequence of instruction that will trigger a trap flag.
* this is used to trigger 'call backs' to the main code
* typically use a pair of NOPs sequence that is unlikely to exist in
* real code. */
uint16_t trap;
// last 4 instructions, as a shift register, used for traps or debug
uint32_t ir_log;
/* Debug only; the callback is called every cycles, with the current
* state of the cpu. */
uint8_t * ram; // DEBUG
} mii_cpu_t;
mii_cpu_state_t
mii_cpu_init(
mii_cpu_t *cpu );
mii_cpu_state_t
mii_cpu_run(
mii_cpu_t *cpu,
mii_cpu_state_t s);

503
src/mii_65c02_asm.c Normal file
View File

@ -0,0 +1,503 @@
/*
* mii_65c02_asm.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mii_65c02.h"
#include "mii_65c02_ops.h"
#include "mii_65c02_asm.h"
static char *
_mii_extract_token(
char *position,
char *dst,
int dst_len)
{
if (!position)
return NULL;
while (*position == ' ' || *position == '\t')
position++;
char *kw = strsep(&position, " \t");
if (kw)
strncpy(dst, kw, dst_len);
return position;
}
static char *
_mii_extract_name(
char *src,
char *dst,
int dst_len)
{
char *end = src;
while (isalnum(*end) || *end == '_')
*dst++ = *end++;
*dst = 0;
return end;
}
static char *
_mii_extract_value_or_name(
mii_cpu_asm_line_t *l,
char *src)
{
static const char * const hex = "0123456789abcdef";
char *end = src;
l->op_value = 0;
if (*end == '<') {
l->op_low = 1;
end++;
} else if (*end == '>') {
l->op_high = 1;
end++;
}
if (*end == '$') {
end++;
while (isxdigit(*end))
l->op_value = (l->op_value << 4) +
(strchr(hex, tolower(*end++)) - hex);
} else if (isdigit(*end)) {
while (isdigit(*end))
l->op_value = (l->op_value << 4) +
(strchr(hex, tolower(*end++)) - hex);
} else {
end = _mii_extract_name(end, l->op_name, sizeof(l->op_name));
l->label_resolved = 0;
}
return end;
}
static int
_mii_resolve_symbol(
mii_cpu_asm_program_t *p,
mii_cpu_asm_line_t *l,
int optional)
{
if (!l->op_name[0] || l->label_resolved)
return 1;
mii_cpu_asm_line_t *l2 = p->sym;
while (l2) {
if (!strcasecmp(l->op_name, l2->label)) {
l->op_value = l2->op_value;
l->label_resolved = 1;
goto found;
}
l2 = l2->sym_next;
}
if (!optional)
printf("%s symbol %s not found\n", __func__, l->op_name);
return 0;
found:
if (l->op_low)
l->op_value &= 0xff;
else if (l->op_high)
l->op_value >>= 8;
return 1;
}
void
mii_cpu_asm_load(
mii_cpu_asm_program_t *p,
const char *prog)
{
const char *current = prog;
int line_count = 0;
do {
const char *lend = strchr(current, '\n');
int len = lend ? (int)(lend - current) : (int)strlen(current);
mii_cpu_asm_line_t *l = calloc(1, sizeof(mii_cpu_asm_line_t) + len + 1);
l->line_index = line_count++;
sprintf(l->line, "%.*s", len, current);
if (p->prog_tail) {
p->prog_tail->next = l;
p->prog_tail = l;
} else {
p->prog = p->prog_tail = l;
}
char *dup = strdup(l->line);
char * comment = strrchr(dup, ';');
if (comment)
*comment = 0;
if (!*dup) {
goto next_line;
}
char * position = dup;
if (position[0] != ' ' && position[0] != '\t') {
char *start = position;
char *kw = strsep(&position, " \t=");
if (kw) {
// strip spaces
char *ke = kw + strlen(kw);
while (ke > start && (ke[-1] <= ' ' || ke[-1] == ':'))
*--ke = 0;
strncpy(l->label, kw, sizeof(l->label)-1);
}
}
position = _mii_extract_token(position, l->mnemonic, sizeof(l->mnemonic));
if (!strcmp(l->mnemonic, ".db") || !strcmp(l->mnemonic, "byte")) {
/* remaining of the line is comma separated hex values OR symbols
* that can be resolved immediately */
char *kw = position;
while (*kw == ' ' || *kw == '\t') kw++;
char *cur = kw;
while ((cur = strsep(&kw, ",")) != NULL) {
while (*cur == ' ' || *cur == '\t') cur++;
char *ke = cur + strlen(cur);
while (ke > cur && (ke[-1] <= ' ' || ke[-1] == ':'))
*--ke = 0;
if (!*cur)
break;
_mii_extract_value_or_name(l, cur);
if (_mii_resolve_symbol(p, l, 0) == 0)
printf("ERROR -- sorry code symbols don't work, just EQUs\n");
else
l->opcodes[l->opcode_count++] = l->op_value;
}
goto next_line;
}
position = _mii_extract_token(position, l->operand, sizeof(l->operand));
if (l->operand[0] == '.' || l->operand[0] == '=') {
// a directive that has been not aligned on the first line, re-adjust
snprintf(l->label, sizeof(l->label), "%s", l->mnemonic);
snprintf(l->mnemonic, sizeof(l->mnemonic), "%s", l->operand);
position = _mii_extract_token(position, l->operand, sizeof(l->operand));
}
if (l->mnemonic[0] == '=' ||
!strcasecmp(l->mnemonic, "equ") ||
!strcasecmp(l->mnemonic, ".equ")) {
l->symbol = 1;
if (p->sym_tail)
p->sym_tail->sym_next = l;
else
p->sym = l;
_mii_extract_value_or_name(l, l->operand);
if (_mii_resolve_symbol(p, l, 0) == 0)
printf("ERROR -- sorry code symbols don't work, just EQUs\n");
}
// printf(">L:%s M:%s O:%s\n", l->label, l->mnemonic, l->operand);
next_line:
free(dup);
current = lend ? lend+1 : NULL;
} while (current);
}
static int
mii_cpu_opcode_has_mode(
const char *mnemonic,
int mode)
{
for (int i = 0; i < 256; i++) {
if (!strncasecmp(mnemonic, mii_cpu_op[i].name, 4)) {
if (mode == mii_cpu_op[i].desc.mode) {
return i;
}
}
}
return -1;
}
static int
_mii_cpu_asm_parse_operand(
mii_cpu_asm_program_t *p,
mii_cpu_asm_line_t *l)
{
l->mode = IMPLIED;
if (!l->operand[0])
return 0;
char sep = 0; // (parenthesis)
char *src = l->operand;
switch (*src) {
case '#':
l->mode = IMM;
src = _mii_extract_value_or_name(l, src + 1);
break;
case '$':
l->mode = ABS;
src = _mii_extract_value_or_name(l, src);
break;
case '(':
l->mode = IND;
sep = ')';
src = _mii_extract_value_or_name(l, src + 1);
break;
default: {
l->mode = ABS;
src = _mii_extract_value_or_name(l, src);
_mii_resolve_symbol(p, l, 1);
} break;
}
while (*src == ' ' || *src == '\t') src++;
switch (*src) {
case ',':
src++;
while (*src == ' ' || *src == '\t') src++;
switch (tolower(*src)) {
case 'x':
l->mode = ABS_X;
if (sep) {
// special case for JMP ($xxxx,x)
if (!strcasecmp(l->mnemonic, "JMP"))
l->mode = IND_AX;
else
l->mode = IND_X;
break;
}
break;
case 'y':
l->mode = ABS_Y;
break;
default:
l->mode = ABS;
break;
}
break;
case ')':
src++;
switch (*src) {
case ',':
src++;
while (*src == ' ' || *src == '\t') src++;
switch (tolower(*src)) {
case 'x':
l->mode = IND_X;
break;
default:
l->mode = IND_Y;
break;
}
break;
default:
l->mode = IND;
break;
}
break;
}
switch (l->mode) {
case ABS: {
if (l->op_value < 0x100) {
int newo = mii_cpu_opcode_has_mode(l->mnemonic, ZP_REL);
if (newo != -1) {
l->mode = ZP_REL;
l->opcodes[0] = mii_cpu_op[newo].desc.op;
l->opcode_count = mii_cpu_op[newo].desc.pc;
}
}
} break;
case ABS_X: {
if (l->op_value < 0x100) {
int newo = mii_cpu_opcode_has_mode(l->mnemonic, ZP_X);
if (newo != -1) {
l->mode = ZP_X;
l->opcodes[0] = mii_cpu_op[newo].desc.op;
l->opcode_count = mii_cpu_op[newo].desc.pc;
}
}
} break;
case ABS_Y: {
if (l->op_value < 0x100) {
int newo = mii_cpu_opcode_has_mode(l->mnemonic, ZP_Y);
if (newo != -1) {
l->mode = ZP_Y;
l->opcodes[0] = mii_cpu_op[newo].desc.op;
l->opcode_count = mii_cpu_op[newo].desc.pc;
}
}
} break;
case IND: if (1) {
if (l->op_value < 0x100) {
// printf("Testing if IND opcode %s can be IND_Z operand %s (%04x)\n",
// l->mnemonic, l->operand, l->op_value);
int newo = mii_cpu_opcode_has_mode(l->mnemonic, IND_Z);
if (newo != -1) {
// printf("YES replace %02x with %02x\n",
// l->opcodes[0], newo);
l->mode = IND_Z;
l->opcodes[0] = mii_cpu_op[newo].desc.op;
l->opcode_count = mii_cpu_op[newo].desc.pc;
}
}
} break;
}
return 0;
}
int
mii_cpu_asm_assemble(
mii_cpu_asm_program_t *p )
{
mii_cpu_asm_line_t *l = p->prog;
int error = 0;
// fix symbols
l = p->sym;
while (l) {
_mii_extract_value_or_name(l, l->operand);
// printf("SYM %s = %04x\n", l->label, l->op_value);
l = l->sym_next;
}
l = p->prog;
while (l) {
if (!l->mnemonic[0] || l->symbol) {
l = l->next;
continue;
}
l->mode = IMPLIED;
_mii_cpu_asm_parse_operand(p, l);
if (!strcasecmp(l->mnemonic, ".org")) {
if (p->verbose)
printf("%s origin set to $%04x\n", __func__, l->op_value);
if (l->mode == ABS) {
if (!p->org)
p->org = l->op_value;
l->addr_set = 1;
l->addr = l->op_value;
}
l = l->next;
continue;
} else if (!strcasecmp(l->mnemonic, ".verbose")) {
p->verbose = 1;
l = l->next;
continue;
} else if (l->mnemonic[0] == '.') {
l = l->next;
continue;
}
int found = -1;
for (int i = 0; i < 256; i++) {
if (!strncasecmp(l->mnemonic, mii_cpu_op[i].name, 4)) {
if (mii_cpu_op[i].desc.branch) {
l->mode = mii_cpu_op[i].desc.mode;
found = i;
break;
} else if (l->mode == mii_cpu_op[i].desc.mode) {
found = i;
break;
}
}
}
if (found == -1) {
printf("ERROR: %d: %s %s %s\n",
l->line_index, l->label, l->mnemonic, l->operand);
printf(" Missing opcode for %s %d\n", l->mnemonic, l->mode);
error = 1;
break;
}
// printf("FOUND %02x for '%s' '%s' '%s' name:'%s'\n",
// found, l->label, l->mnemonic, l->operand, l->op_name);
l->opcodes[0] = mii_cpu_op[found].desc.op;
l->opcode_count = mii_cpu_op[found].desc.pc;
l = l->next;
}
if (error)
return error;
uint16_t pc = p->org;
/*
* We (should) know all instruction size by now, so lets sets
* the addresses for all the lines
*/
l = p->prog;
while (l) {
if (l->addr_set)
pc = l->addr;
else
l->addr = pc;
if (l->mnemonic[0] != '.') {
// this sets the ones we know about, and clears the ones we dont
for (int i = 1; i < l->opcode_count; i++)
l->opcodes[i] = l->op_value >> (8 * (i - 1));
}
pc += l->opcode_count;
l = l->next;
}
/*
* Now resolve all the operand labels
*/
l = p->prog;
while (l) {
if (l->op_name[0] && !l->label_resolved) {
mii_cpu_asm_line_t *l2 = p->prog;
while (l2) {
int32_t value = 0;
if (!strcasecmp(l->op_name, l2->label)) {
value = l2->op_value;
if (!l2->symbol)
value = l2->addr;
else
value = l2->op_value;
l->op_value = value;
l->label_resolved = 1;
if (mii_cpu_op[l->opcodes[0]].desc.branch)
l->op_value = l2->addr - l->addr - 2;
else if (l->op_low)
l->op_value &= 0xff;
else if (l->op_high)
l->op_value >>= 8;
for (int i = 1; i < l->opcode_count; i++)
l->opcodes[i] = l->op_value >> (8 * (i - 1));
break;
}
l2 = l2->next;
}
if (!l->label_resolved) {
printf("ERROR: Missing label %d: %s %s %s\n",
l->line_index, l->label, l->mnemonic, l->operand);
error = 1;
break;
}
}
l = l->next;
}
if (error)
return error;
p->output_len = p->prog_tail->addr + p->prog_tail->opcode_count - p->org;
if (p->verbose)
printf("%s program at $%04x size %d bytes\n", __func__,
p->org, p->output_len);
p->output = calloc(1, p->output_len);
l = p->prog;
while (l) {
for (int i = 0; i < l->opcode_count; i++)
p->output[l->addr - p->org + i] = l->opcodes[i];
l = l->next;
}
return error;
}
int
mii_cpu_asm(
mii_cpu_asm_program_t *p,
const char *prog)
{
mii_cpu_asm_load(p, prog);
return mii_cpu_asm_assemble(p);
}
void
mii_cpu_asm_free(
mii_cpu_asm_program_t *p)
{
mii_cpu_asm_line_t *l = p->prog;
while (l) {
mii_cpu_asm_line_t *next = l->next;
free(l);
l = next;
}
if (p->output)
free(p->output);
memset(p, 0, sizeof(*p));
}

47
src/mii_65c02_asm.h Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include <stdint.h>
typedef struct mii_cpu_asm_line_t {
struct mii_cpu_asm_line_t *next;
struct mii_cpu_asm_line_t *sym_next;
uint16_t line_index;
uint8_t symbol : 1,
label_resolved : 1,
addr_set : 1, op_low : 1, op_high : 1;
uint16_t addr; // address of the instruction (if resolved)
uint8_t mode; // mode of the instruction
uint8_t opcode_count; // number of bytes for the opcode
uint8_t opcodes[32]; // or .byte statements
char label[64];
char mnemonic[64];
char operand[64];
char op_name[64];
int op_value;
char line[]; // actual line read ends up here, untouched
} mii_cpu_asm_line_t;
typedef struct mii_cpu_asm_program_t {
uint8_t verbose;
uint16_t org; // origin, can be set before, or with .org
mii_cpu_asm_line_t *sym;
mii_cpu_asm_line_t *sym_tail;
mii_cpu_asm_line_t *prog;
mii_cpu_asm_line_t *prog_tail;
uint8_t * output;
uint16_t output_len;
} mii_cpu_asm_program_t;
int
mii_cpu_asm(
mii_cpu_asm_program_t *p,
const char *prog);
void
mii_cpu_asm_free(
mii_cpu_asm_program_t *p);

124
src/mii_65c02_disasm.c Normal file
View File

@ -0,0 +1,124 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mii_65c02.h"
#include "mii_65c02_ops.h"
#include "mii_65c02_disasm.h"
int
mii_cpu_disasm_one(
const uint8_t *prog,
uint16_t addr,
char *out,
size_t out_len,
uint16_t flags)
{
uint16_t pc = addr;
int i = 0;
uint8_t op = prog[i];
mii_op_desc_t d = mii_cpu_op[op].desc;
if (!d.pc)
d.pc = 1;
*out = 0;
int len = out_len;
if (flags & MII_DUMP_DIS_PC)
len -= snprintf(out + strlen(out), len, "%04X: ", pc + i);
if (flags & MII_DUMP_DIS_DUMP_HEX) {
char hex[32] = {0};
for (int oi = 0; oi < d.pc; oi++)
sprintf(hex + (oi * 3), "%02X ", prog[i + oi]);
len -= snprintf(out + strlen(out), len, "%-9s ", hex);
}
len -= snprintf(out + strlen(out), len, "%.4s ",
mii_cpu_op[op].name[0] ?
mii_cpu_op[op].name : "???");
i++;
switch (d.mode) {
case IMM:
len -= snprintf(out + strlen(out), len,
"#$%02X", prog[i++]);
break;
case BRANCH:
case ZP_REL:
if ((op & 0xf) == 0xf) {
uint16_t base = pc + i + 1;
uint16_t dest = base + (int8_t)prog[i++];
len -= snprintf(out + strlen(out), len,
"%d,$%04X", d.s_bit, dest);
} else if (d.branch) {
uint16_t base = pc + i + 1;
uint16_t dest = base + (int8_t)prog[i++];
len -= snprintf(out + strlen(out), len,
"$%04X", dest);
} else
len -= snprintf(out + strlen(out), len,
"$%02X", prog[i++]);
break;
case ZP_X:
len -= snprintf(out + strlen(out), len,
"$%02X,X", prog[i++]);
break;
case ZP_Y:
len -= snprintf(out + strlen(out), len,
"$%02X,Y", prog[i++]);
break;
case ABS:
len -= snprintf(out + strlen(out), len,
"$%02X%02X", prog[i + 1], prog[i]);
i += 2;
break;
case ABS_X:
len -= snprintf(out + strlen(out), len,
"$%02X%02X,X", prog[i + 1], prog[i]);
i += 2;
break;
case ABS_Y:
len -= snprintf(out + strlen(out), len,
"$%02X%02X,Y", prog[i + 1], prog[i]);
i += 2;
break;
case IND_X:
len -= snprintf(out + strlen(out), len,
"($%02X,X)", prog[i++]);
break;
case IND_AX:
len -= snprintf(out + strlen(out), len,
"($%02X%02X,X)", prog[i + 1], prog[i]);
i += 2;
break;
case IND_Y:
len -= snprintf(out + strlen(out), len,
"($%02X),Y", prog[i++]);
break;
case IND_Z:
len -= snprintf(out + strlen(out), len,
"($%02X)", prog[i++]);
break;
case IND:
len -= snprintf(out + strlen(out), len,
"($%02X%02X)", prog[i + 1], prog[i]);
i += 2;
break;
case IMPLIED:
break;
}
return d.pc;
}
void
mii_cpu_disasm(
const uint8_t *prog,
uint16_t addr,
uint16_t len)
{
uint16_t pc = addr;
int i = 0;
while (i < len) {
char out[256] = {0};
i += mii_cpu_disasm_one(prog + i, pc + i, out, sizeof(out),
MII_DUMP_DIS_PC | MII_DUMP_DIS_DUMP_HEX);
printf("%s\n", out);
}
}

24
src/mii_65c02_disasm.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <stdint.h>
enum {
MII_DUMP_DIS_PC = (1 << 0),
MII_DUMP_DIS_DUMP_HEX = (1 << 1),
};
int
mii_cpu_disasm_one(
const uint8_t *prog,
uint16_t addr,
char *out,
size_t out_len,
uint16_t flags);
void
mii_cpu_disasm(
const uint8_t *prog,
uint16_t addr,
uint16_t len);

308
src/mii_65c02_ops.h Normal file
View File

@ -0,0 +1,308 @@
#pragma once
#include <stdint.h>
typedef struct mii_op_desc_t {
uint32_t op : 8,
mode : 4,
pc : 2,
branch : 1, // relative branch
ch_pc : 1, // change PC
s_bit : 3,
s_bit_value : 1,
r : 1,
w : 1;
} mii_op_desc_t;
typedef struct mii_op_t {
char name[4];
mii_op_desc_t desc;
} mii_op_t;
enum {
B_C = 0,
B_Z,
B_I,
B_D,
B_B,
B_X,
B_V,
B_N,
};
enum {
IMPLIED, // BRK
IMM, // LDA #$01
ZP_REL, // LDA $C0 or BCC $FF
ZP_X, // LDA $C0,X
ZP_Y, // LDX $C0,Y
ABS, // LDA $1234
ABS_X, // LDA $1234,X
ABS_Y, // LDA $1234,Y
IND_X, // LDA ($FF,X)
IND_AX, // JMP ($1234,X)
IND_Y, // LDA ($FF),Y
IND, // JMP ($1234)
IND_Z, // LDA ($C0)
BRANCH, // BEQ/BNE etc
};
#define PCODE_(_name, _mode, _op, _pc, _r, _w, _br, _sb, _sv, _cpc) \
[_op] = { \
.name = #_name, \
.desc = { \
.op = _op, .mode = _mode, .pc = _pc, .r = _r, .w = _w, \
.branch = _br, .s_bit = _sb, .s_bit_value = _sv, .ch_pc = _cpc } \
},
#ifndef MII_CPU_65C02_IMPL
extern const mii_op_t mii_cpu_op[256];
#else // MII_CPU_65C02_IMPL
#define PCODE_R_(_name, _mode, _op, _pc) \
PCODE_(_name, _mode, _op, _pc, 1, 0, 0, 0, 0, 0)
#define PCODE__W(_name, _mode, _op, _pc) \
PCODE_(_name, _mode, _op, _pc, 0, 1, 0, 0, 0, 0)
#define PCODE___(_name, _mode, _op, _pc) \
PCODE_(_name, _mode, _op, _pc, 0, 0, 0, 0, 0, 0)
#define PCODE_RW(_name, _mode, _op, _pc) \
PCODE_(_name, _mode, _op, _pc, 1, 1, 0, 0, 0, 0)
#define PCODE__W(_name, _mode, _op, _pc) \
PCODE_(_name, _mode, _op, _pc, 0, 1, 0, 0, 0, 0)
#define PCODE_BR(_name, _op, _pc, _sb, _sv) \
PCODE_(_name, BRANCH, _op, _pc, 0, 0, 1, B_##_sb, _sv, 1)
#define PCODE_PC(_name, _mode, _op, _pc) \
PCODE_(_name, _mode, _op, _pc, 1, 0, 0, 0, 0, 1)
#define PCODE_RT(_name, _mode, _op, _pc) \
PCODE_(_name, _mode, _op, _pc, 0, 0, 0, 0, 0, 1)
#define PCODE_SB(_name, _mode, _op, _pc, _sb, _sv) \
PCODE_(_name, _mode, _op, _pc, 0, 0, 0, B_##_sb, _sv, 0)
// for RMBx SMBx
#define PCODE_MB(_name, _op, _sb, _sv) \
PCODE_(_name, ZP_REL, _op, 2, 1, 1, 0, _sb, _sv, 0)
// for BBRx BBSx
#define PCODE_BB(_name, _op, _sb, _sv) \
PCODE_(_name, ZP_REL, _op, 3, 1, 0, 1, _sb, _sv, 0)
const mii_op_t mii_cpu_op[256] = {
PCODE___(ADC, IMM, 0x69, 2)
PCODE_R_(ADC, ZP_REL, 0x65, 2)
PCODE_R_(ADC, ZP_X, 0x75, 2)
PCODE_R_(ADC, ABS, 0x6D, 3)
PCODE_R_(ADC, ABS_X, 0x7D, 3)
PCODE_R_(ADC, ABS_Y, 0x79, 3)
PCODE_R_(ADC, IND_X, 0x61, 2)
PCODE_R_(ADC, IND_Y, 0x71, 2)
PCODE_R_(ADC, IND_Z, 0x72, 2)
PCODE___(AND, IMM, 0x29, 2)
PCODE_R_(AND, ZP_REL, 0x25, 2)
PCODE_R_(AND, ZP_X, 0x35, 2)
PCODE_R_(AND, ABS, 0x2D, 3)
PCODE_R_(AND, ABS_X, 0x3D, 3)
PCODE_R_(AND, ABS_Y, 0x39, 3)
PCODE_R_(AND, IND_X, 0x21, 2)
PCODE_R_(AND, IND_Y, 0x31, 2)
PCODE_R_(AND, IND_Z, 0x32, 2)
PCODE___(ASL, IMPLIED, 0x0A, 1)
PCODE_RW(ASL, ZP_REL, 0x06, 2)
PCODE_RW(ASL, ZP_X, 0x16, 2)
PCODE_RW(ASL, ABS, 0x0E, 3)
PCODE_RW(ASL, ABS_X, 0x1E, 3)
PCODE_BB(BBR0, 0x0F, 0, 0)
PCODE_BB(BBR1, 0x1F, 1, 0)
PCODE_BB(BBR2, 0x2F, 2, 0)
PCODE_BB(BBR3, 0x3F, 3, 0)
PCODE_BB(BBR4, 0x4F, 4, 0)
PCODE_BB(BBR5, 0x5F, 5, 0)
PCODE_BB(BBR6, 0x6F, 6, 0)
PCODE_BB(BBR7, 0x7F, 7, 0)
PCODE_BB(BBS0, 0x8F, 0, 1)
PCODE_BB(BBS1, 0x9F, 1, 1)
PCODE_BB(BBS2, 0xAF, 2, 1)
PCODE_BB(BBS3, 0xBF, 3, 1)
PCODE_BB(BBS4, 0xCF, 4, 1)
PCODE_BB(BBS5, 0xDF, 5, 1)
PCODE_BB(BBS6, 0xEF, 6, 1)
PCODE_BB(BBS7, 0xFF, 7, 1)
PCODE_BR(BCC, 0x90, 2, C, 0)
PCODE_BR(BCS, 0xB0, 2, C, 1)
PCODE_BR(BEQ, 0xF0, 2, Z, 1)
PCODE_BR(BMI, 0x30, 2, N, 1)
PCODE_BR(BNE, 0xD0, 2, Z, 0)
PCODE_BR(BPL, 0x10, 2, N, 0)
PCODE_BR(BVC, 0x50, 2, V, 0)
PCODE_BR(BVS, 0x70, 2, V, 1)
PCODE_BR(BRA, 0x80, 2, X, 1)
PCODE_R_(BIT, ZP_REL, 0x24, 2)
PCODE_R_(BIT, ABS, 0x2C, 3)
PCODE___(BIT, IMM, 0x89, 2)
PCODE_R_(BIT, ZP_X, 0x34, 2)
PCODE_R_(BIT, ABS_X, 0x3C, 3)
PCODE_RT(BRK, IMPLIED, 0x00, 1)
PCODE_SB(CLC, IMPLIED, 0x18, 1, C, 0)
PCODE_SB(CLD, IMPLIED, 0xD8, 1, D, 0)
PCODE_SB(CLI, IMPLIED, 0x58, 1, I, 0)
PCODE_SB(CLV, IMPLIED, 0xB8, 1, V, 0)
PCODE___(CMP, IMM, 0xC9, 2)
PCODE_R_(CMP, ZP_REL, 0xC5, 2)
PCODE_R_(CMP, ZP_X, 0xD5, 2)
PCODE_R_(CMP, ABS_X, 0xDD, 3)
PCODE_R_(CMP, ABS, 0xCD, 3)
PCODE_R_(CMP, ABS_Y, 0xD9, 3)
PCODE_R_(CMP, IND_X, 0xC1, 2)
PCODE_R_(CMP, IND_Y, 0xD1, 2)
PCODE_R_(CMP, IND_Z, 0xD2, 2)
PCODE___(CPX, IMM, 0xE0, 2)
PCODE_R_(CPX, ZP_REL, 0xE4, 2)
PCODE_R_(CPX, ABS, 0xEC, 3)
PCODE___(CPY, IMM, 0xC0, 2)
PCODE_R_(CPY, ZP_REL, 0xC4, 2)
PCODE_R_(CPY, ABS, 0xCC, 3)
PCODE___(DEC, IMPLIED, 0x3A, 1)
PCODE_RW(DEC, ZP_REL, 0xC6, 2)
PCODE_RW(DEC, ZP_X, 0xD6, 2)
PCODE_RW(DEC, ABS, 0xCE, 3)
PCODE_RW(DEC, ABS_X, 0xDE, 3)
PCODE___(DEX, IMPLIED, 0xCA, 1)
PCODE___(DEY, IMPLIED, 0x88, 1)
PCODE___(EOR, IMM, 0x49, 2)
PCODE_R_(EOR, ZP_REL, 0x45, 2)
PCODE_R_(EOR, ZP_X, 0x55, 2)
PCODE_R_(EOR, ABS, 0x4D, 3)
PCODE_R_(EOR, ABS_X, 0x5D, 3)
PCODE_R_(EOR, ABS_Y, 0x59, 3)
PCODE_R_(EOR, IND_X, 0x41, 2)
PCODE_R_(EOR, IND_Y, 0x51, 2)
PCODE_R_(EOR, IND_Z, 0x52, 2)
PCODE___(INC, IMPLIED, 0x1A, 1)
PCODE_RW(INC, ZP_REL, 0xE6, 2)
PCODE_RW(INC, ZP_X, 0xF6, 2)
PCODE_RW(INC, ABS, 0xEE, 3)
PCODE_RW(INC, ABS_X, 0xFE, 3)
PCODE___(INX, IMPLIED, 0xE8, 1)
PCODE___(INY, IMPLIED, 0xC8, 1)
PCODE_PC(JMP, ABS, 0x4C, 3)
PCODE_PC(JMP, IND, 0x6C, 3)
PCODE_PC(JMP, IND_AX, 0x7C, 3)
PCODE_PC(JSR, ABS, 0x20, 3)
PCODE___(LDA, IMM, 0xA9, 2)
PCODE_R_(LDA, ZP_REL, 0xA5, 2)
PCODE_R_(LDA, ZP_X, 0xB5, 2)
PCODE_R_(LDA, ABS, 0xAD, 3)
PCODE_R_(LDA, ABS_X, 0xBD, 3)
PCODE_R_(LDA, ABS_Y, 0xB9, 3)
PCODE_R_(LDA, IND_X, 0xA1, 2)
PCODE_R_(LDA, IND_Y, 0xB1, 2)
PCODE_R_(LDA, IND_Z, 0xB2, 2)
PCODE___(LDX, IMM, 0xA2, 2)
PCODE_R_(LDX, ZP_REL, 0xA6, 2)
PCODE_R_(LDX, ZP_Y, 0xB6, 2)
PCODE_R_(LDX, ABS, 0xAE, 3)
PCODE_R_(LDX, ABS_Y, 0xBE, 3)
PCODE___(LDY, IMM, 0xA0, 2)
PCODE_R_(LDY, ZP_REL, 0xA4, 2)
PCODE_R_(LDY, ZP_X, 0xB4, 2)
PCODE_R_(LDY, ABS, 0xAC, 3)
PCODE_R_(LDY, ABS_X, 0xBC, 3)
PCODE___(LSR, IMPLIED, 0x4A, 1)
PCODE_RW(LSR, ZP_REL, 0x46, 2)
PCODE_RW(LSR, ZP_X, 0x56, 2)
PCODE_RW(LSR, ABS, 0x4E, 3)
PCODE_RW(LSR, ABS_X, 0x5E, 3)
PCODE___(NOP, IMPLIED, 0xEA, 1)
PCODE___(ORA, IMM, 0x09, 2)
PCODE_R_(ORA, ZP_REL, 0x05, 2)
PCODE_R_(ORA, ZP_X, 0x15, 2)
PCODE_R_(ORA, ABS, 0x0D, 3)
PCODE_R_(ORA, ABS_X, 0x1D, 3)
PCODE_R_(ORA, ABS_Y, 0x19, 3)
PCODE_R_(ORA, IND_X, 0x01, 2)
PCODE_R_(ORA, IND_Y, 0x11, 2)
PCODE_R_(ORA, IND_Z, 0x12, 2)
PCODE___(PHA, IMPLIED, 0x48, 1)
PCODE___(PHP, IMPLIED, 0x08, 1)
PCODE___(PHX, IMPLIED, 0xDA, 1)
PCODE___(PHY, IMPLIED, 0x5A, 1)
PCODE___(PLA, IMPLIED, 0x68, 1)
PCODE___(PLP, IMPLIED, 0x28, 1)
PCODE___(PLX, IMPLIED, 0xFA, 1)
PCODE___(PLY, IMPLIED, 0x7A, 1)
// these fill the opcode name without terminating zero
PCODE_MB(RMB0, 0x07, 0, 0)
PCODE_MB(RMB1, 0x17, 1, 0)
PCODE_MB(RMB2, 0x27, 2, 0)
PCODE_MB(RMB3, 0x37, 3, 0)
PCODE_MB(RMB4, 0x47, 4, 0)
PCODE_MB(RMB5, 0x57, 5, 0)
PCODE_MB(RMB6, 0x67, 6, 0)
PCODE_MB(RMB7, 0x77, 7, 0)
PCODE___(ROL, IMPLIED, 0x2A, 1)
PCODE_RW(ROL, ZP_REL, 0x26, 2)
PCODE_RW(ROL, ZP_X, 0x36, 2)
PCODE_RW(ROL, ABS, 0x2E, 3)
PCODE_RW(ROL, ABS_X, 0x3E, 3)
PCODE___(ROR, IMPLIED, 0x6A, 1)
PCODE_RW(ROR, ZP_REL, 0x66, 2)
PCODE_RW(ROR, ZP_X, 0x76, 2)
PCODE_RW(ROR, ABS, 0x6E, 3)
PCODE_RW(ROR, ABS_X, 0x7E, 3)
PCODE_RT(RTI, IMPLIED, 0x40, 1)
PCODE_RT(RTS, IMPLIED, 0x60, 1)
PCODE___(SBC, IMM, 0xE9, 2)
PCODE_R_(SBC, ZP_REL, 0xE5, 2)
PCODE_R_(SBC, ZP_X, 0xF5, 2)
PCODE_R_(SBC, ABS, 0xED, 3)
PCODE_R_(SBC, ABS_X, 0xFD, 3)
PCODE_R_(SBC, ABS_Y, 0xF9, 3)
PCODE_R_(SBC, IND_X, 0xE1, 2)
PCODE_R_(SBC, IND_Y, 0xF1, 2)
PCODE_R_(SBC, IND_Z, 0xF2, 2)
PCODE_SB(SEC, IMPLIED, 0x38, 1, C, 1)
PCODE_SB(SED, IMPLIED, 0xF8, 1, D, 1)
PCODE_SB(SEI, IMPLIED, 0x78, 1, I, 1)
// these fill the opcode name without terminating zero
PCODE_MB(SMB0, 0x87, 0, 1)
PCODE_MB(SMB1, 0x97, 1, 1)
PCODE_MB(SMB2, 0xa7, 2, 1)
PCODE_MB(SMB3, 0xb7, 3, 1)
PCODE_MB(SMB4, 0xc7, 4, 1)
PCODE_MB(SMB5, 0xd7, 5, 1)
PCODE_MB(SMB6, 0xe7, 6, 1)
PCODE_MB(SMB7, 0xf7, 7, 1)
PCODE__W(STA, ZP_REL, 0x85, 2)
PCODE__W(STA, ZP_X, 0x95, 2)
PCODE__W(STA, ABS, 0x8D, 3)
PCODE__W(STA, ABS_X, 0x9D, 3)
PCODE__W(STA, ABS_Y, 0x99, 3)
PCODE__W(STA, IND_X, 0x81, 2)
PCODE__W(STA, IND_Y, 0x91, 2)
PCODE__W(STA, IND_Z, 0x92, 2)
PCODE__W(STX, ZP_REL, 0x86, 2)
PCODE__W(STX, ZP_Y, 0x96, 2)
PCODE__W(STX, ABS, 0x8E, 3)
PCODE__W(STY, ZP_REL, 0x84, 2)
PCODE__W(STY, ZP_X, 0x94, 2)
PCODE__W(STY, ABS, 0x8C, 3)
PCODE__W(STZ, ZP_REL, 0x64, 2)
PCODE__W(STZ, ZP_X, 0x74, 2)
PCODE__W(STZ, ABS, 0x9C, 3)
PCODE__W(STZ, ABS_X, 0x9E, 3)
PCODE___(TAX, IMPLIED, 0xAA, 1)
PCODE___(TAY, IMPLIED, 0xA8, 1)
PCODE_RW(TRB, ZP_REL, 0x14, 2)
PCODE_RW(TRB, ABS, 0x1c, 3)
PCODE___(TSX, IMPLIED, 0xBA, 1)
PCODE_RW(TSB, ZP_REL, 0x04, 2)
PCODE_RW(TSB, ABS, 0x0c, 3)
PCODE___(TXA, IMPLIED, 0x8A, 1)
PCODE___(TXS, IMPLIED, 0x9A, 1)
PCODE___(TYA, IMPLIED, 0x98, 1)
};
#endif // MII_CPU_65C02_IMPL

199
src/mii_argv.c Normal file
View File

@ -0,0 +1,199 @@
/*
* mii_argv.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mii.h"
extern mii_slot_drv_t * mii_slot_drv_list;
static void
_mii_usage(
const char *progname)
{
printf("Usage: %s [options]\n", progname);
printf("Options:\n");
printf(" -h, --help\tThis help\n");
printf(" -v, --verbose\tVerbose output\n");
printf(" -m, --mute\tMute the speaker\n");
printf(" -vol, --volume <volume>\tSet speaker volume (0.0 to 10.0)\n");
printf(" -speed, --speed <speed>\tSet the CPU speed in MHz\n");
printf(" -s, --slot <slot>:<driver>\tSpecify a slot and driver\n");
printf("\t\tSlot id is 1..7\n");
printf(" -d, --drive <slot>:<drive>:<filename>\tLoad a drive\n");
printf("\t\tSlot id is 1..7, drive is 1..2\n");
printf("\t\tAlternate syntax: <slot>:<drive> <filename>\n");
printf(" -L, --list-drivers\tList available drivers, exit\n");
printf(" -def, --default\tUse a set of default cards:\n");
printf("\t\tSlot 4: mouse\n");
printf("\t\tSlot 6: disk2\n");
printf("\t\tSlot 7: smartport\n");
printf(" -nsc[=0|1]\tEnable/Disable No Slot Clock:\n");
printf("\t\t0: disable\n");
printf("\t\t1: enable [Enabled by default]\n");
printf(" -titan[=0|1]\tEnable/Disable Titan Accelerator IIe:\n");
printf("\t\t0: disable [default]\n");
printf("\t\t1: enable [Start at 3.58MHz]\n");
}
int
mii_argv_parse(
mii_t *mii,
int argc,
const char *argv[],
int *index,
uint32_t *ioFlags)
{
if (*index == 0)
*index += 1;
for (int i = *index; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) {
_mii_usage(argv[0]);
exit(0);
} else if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) {
// mii->verbose++;
// continue;
} else if ((!strcmp(arg, "-s") || !strcmp(arg, "--slot")) && i < argc-1) {
// for mat for slot is 1..8:<name> where name is the driver name
const char *p = argv[++i];
int slot = 0;
char *drv = NULL;
if (sscanf(p, "%d:%ms", &slot, &drv) != 2) {
printf("mii: invalid slot specification %s\n", p);
return 1;
}
if (slot < 1 || slot > 8) {
printf("mii: invalid slot number %d\n", slot);
return 1;
}
if (drv == NULL) {
printf("mii: missing driver name for slot %d\n", slot);
return 1;
}
if (mii_slot_drv_register(mii, slot, drv) < 0) {
printf("mii: failed to register driver %s for slot %d\n", drv, slot);
return 1;
}
} else if ((!strcmp(arg, "-d") || !strcmp(arg, "--drive")) && i < argc-1) {
// drive takes 2 following arguments, the <slot>:<drive> and a filename
const char *p = argv[++i];
int slot = 0;
int drive = 0;
const char *filename = NULL;
int got = sscanf(p, "%d:%d:%ms", &slot, &drive, &filename);
if (got == 2) {
if (i < argc-1) {
filename = argv[++i];
got = 3;
} else {
printf("mii: missing filename for drive %d:%d\n", slot, drive);
return 1;
}
} else if (got != 3) {
printf("mii: invalid drive specification %s\n", p);
return 1;
}
if (slot < 1 || slot > 8) {
printf("mii: invalid slot number %d\n", slot);
return 1;
}
if (drive < 1 || drive > 2) {
printf("mii: invalid drive number %d\n", drive);
return 1;
}
if (filename == NULL) {
printf("mii: missing filename for drive %d:%d\n", slot, drive);
return 1;
}
mii_slot_command(mii, slot,
MII_SLOT_DRIVE_LOAD + drive - 1,
(void*)filename);
} else if (!strcmp(arg, "-def") || !strcmp(arg, "--default")) {
mii_slot_drv_register(mii, 4, "mouse");
mii_slot_drv_register(mii, 6, "disk2");
mii_slot_drv_register(mii, 7, "smartport");
} else if (!strcmp(arg, "-L") || !strcmp(arg, "--list-drivers")) {
mii_slot_drv_t * drv = mii_slot_drv_list;
printf("mii: available drivers:\n");
while (drv) {
printf("%10.10s - %s\n", drv->name, drv->desc);
drv = drv->next;
}
exit(0);
} else if (!strcmp(arg, "-m") || !strcmp(arg, "--mute")) {
mii->speaker.muted = true;
} else if (!strcmp(arg, "-vol") || !strcmp(arg, "--volume")) {
if (i < argc-1) {
float vol = atof(argv[++i]);
if (vol < 0) vol = 0;
else if (vol > 10) vol = 10;
mii_speaker_volume(&mii->speaker, vol);
} else {
printf("mii: missing volume value\n");
return 1;
}
} else if (!strcmp(arg, "-speed") || !strcmp(arg, "--speed")) {
if (i < argc-1) {
mii->speed = atof(argv[++i]);
if (mii->speed < 0.0)
mii->speed = 1.0;
} else {
printf("mii: missing speed value\n");
return 1;
}
} else {
if (argv[i][0] == '-') {
char dup[128];
snprintf(dup, sizeof(dup), "%s", argv[i] + 1);
char *equal = dup;
char *name = strsep(&equal, "=");
int enable = 1;
if (equal && *equal) {
if (!strcmp(equal, "0"))
enable = 0;
else if (!strcmp(equal, "1"))
enable = 1;
else {
printf("mii: invalid flag %s\n", argv[i]);
return 1;
}
}
// printf("%s lookup driver %s to %s\n", __func__,
// name, enable ? "enable" : "disable");
mii_slot_drv_t * drv = mii_slot_drv_list;
int done = 0;
while (drv) {
printf("%10.10s - %s\n", drv->name, drv->desc);
if (drv->enable_flag) {
if (!strcmp(name, drv->name)) {
*ioFlags = (*ioFlags & ~drv->enable_flag) |
(enable ? drv->enable_flag : 0);
printf("mii: %s %s\n", name, enable ? "enabled" : "disabled");
break;
}
}
drv = drv->next;
}
if (!done && equal) {
printf("mii: no driver found %s\n", argv[i]);
return 1;
}
continue;
}
printf("mii: unknown argument %s\n", argv[i]);
return 1;
}
}
*index = argc;
return 1;
}

105
src/mii_bank.c Normal file
View File

@ -0,0 +1,105 @@
/*
* mii_bank.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mii.h"
#include "mii_bank.h"
void
mii_bank_write(
mii_bank_t *bank,
uint16_t addr,
const uint8_t *data,
uint16_t len)
{
if ((addr < bank->base) ||
((addr + len) > (uint32_t)(bank->base + (bank->size * 256)))) {
printf("%s %s INVALID write addr %04x len %d %04x:%04x\n",
__func__, bank->name, addr, (int)len,
bank->base, bank->base + (bank->size * 256));
return;
}
uint8_t page_index = (addr - bank->base) >> 8;
if (bank->access && bank->access[page_index].cb) {
if (bank->access[page_index].cb(bank, bank->access[page_index].param,
addr, (uint8_t *)data, true))
return;
}
if (!bank->mem) {
bank->alloc = 1;
bank->mem = calloc(1, bank->size * 256);
}
addr -= bank->base;
for (uint16_t i = 0; i < len; i++, addr++) {
bank->mem[addr] = data[i];
}
}
void
mii_bank_read(
mii_bank_t *bank,
uint16_t addr,
uint8_t *data,
uint16_t len)
{
if (addr < bank->base ||
(addr + len) > (uint32_t)(bank->base + (bank->size * 256))) {
printf("%s %s INVALID read addr %04x len %d %04x-%04x\n",
__func__, bank->name, addr, (int)len,
bank->base, bank->base + (bank->size * 256));
return;
}
uint8_t page_index = (addr - bank->base) >> 8;
if (bank->access && bank->access[page_index].cb) {
if (bank->access[page_index].cb(bank, bank->access[page_index].param,
addr, data, false))
return;
}
if (!bank->mem) {
bank->alloc = 1;
bank->mem = calloc(1, bank->size * 256);
}
addr -= bank->base;
for (uint16_t i = 0; i < len; i++, addr++) {
data[i] = bank->mem[addr];
}
}
void
mii_bank_install_access_cb(
mii_bank_t *bank,
mii_bank_access_cb cb,
void *param,
uint8_t page,
uint8_t end)
{
if (!end)
end = page;
if ((page << 8) < bank->base || (end << 8) > (bank->base + bank->size * 256)) {
printf("%s %s INVALID install access cb %p param %p page %02x-%02x\n",
__func__, bank->name, cb, param, page, end);
return;
}
page -= bank->base >> 8;
end -= bank->base >> 8;
if (!bank->access) {
bank->access = calloc(1, bank->size * sizeof(bank->access[0]));
}
printf("%s %s install access cb page %02x:%02x\n",
__func__, bank->name, page, end);
for (int i = page; i <= end; i++) {
bank->access[i].cb = cb;
bank->access[i].param = param;
}
}

81
src/mii_bank.h Normal file
View File

@ -0,0 +1,81 @@
/*
* mii_bank.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
struct mii_bank_t;
typedef bool (*mii_bank_access_cb)(
struct mii_bank_t *bank,
void *param,
uint16_t addr,
uint8_t * byte,
bool write);
typedef struct mii_bank_access_t {
mii_bank_access_cb cb;
void *param;
} mii_bank_access_t;
typedef struct mii_bank_t {
uint64_t base : 16, // base address
size : 9, // in pages
alloc : 1, // been calloced()
ro : 1; // read only
char * name;
mii_bank_access_t * access;
uint8_t *mem;
} mii_bank_t;
void
mii_bank_write(
mii_bank_t *bank,
uint16_t addr,
const uint8_t *data,
uint16_t len);
void
mii_bank_read(
mii_bank_t *bank,
uint16_t addr,
uint8_t *data,
uint16_t len);
/* return the number of pages dirty (written into since last time) between
* addr1 and addr2 (inclusive) */
uint8_t
mii_bank_is_dirty(
mii_bank_t *bank,
uint16_t addr1,
uint16_t addr2);
void
mii_bank_install_access_cb(
mii_bank_t *bank,
mii_bank_access_cb cb,
void *param,
uint8_t page,
uint8_t end);
static inline void
mii_bank_poke(
mii_bank_t *bank,
uint16_t addr,
const uint8_t data)
{
mii_bank_write(bank, addr, &data, 1);
}
static inline uint8_t
mii_bank_peek(
mii_bank_t *bank,
uint16_t addr)
{
uint8_t res = 0;
mii_bank_read(bank, addr, &res, 1);
return res;
}

86
src/mii_slot.c Normal file
View File

@ -0,0 +1,86 @@
/*
* mii_slot.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mii.h"
mii_slot_drv_t *
mii_slot_drv_find(
mii_t *mii,
const char * name)
{
mii_slot_drv_t * drv = mii_slot_drv_list;
while (drv) {
if (!strcmp(drv->name, name))
return drv;
drv = drv->next;
}
return NULL;
}
int
mii_slot_drv_register(
mii_t *mii,
uint8_t slot_id,
const char *driver_name)
{
if (!mii || !driver_name) {
printf("%s invalid args\n", __func__);
return -1;
}
if (slot_id < 1 || slot_id > 7) {
printf("%s invalid slot id %d\n", __func__, slot_id);
return -1;
}
if (mii->slot[slot_id - 1].drv) {
printf("%s slot %d already has a driver (%s)\n",
__func__, slot_id, mii->slot[slot_id - 1].drv->name);
return -1;
}
mii_slot_drv_t * drv = mii_slot_drv_find(mii, driver_name);
if (!drv) {
printf("%s driver %s not found\n", __func__, driver_name);
return -1;
}
if (drv->init) {
if (drv->init(mii, &mii->slot[slot_id - 1]) != 0) {
printf("%s driver %s init failed\n", __func__, driver_name);
return -1;
}
}
mii->slot[slot_id - 1].drv = drv;
return 0;
}
mii_slot_drv_t *
mii_slot_drv_get(
mii_t *mii,
uint8_t slot_id)
{
if (!mii || slot_id < 1 || slot_id > 7)
return NULL;
return (mii_slot_drv_t *)mii->slot[slot_id - 1].drv;
}
int
mii_slot_command(
mii_t *mii,
uint8_t slot_id,
uint8_t cmd,
void * param)
{
mii_slot_drv_t * drv = mii_slot_drv_get(mii, slot_id);
if (!drv)
return -1;
if (drv->command)
return drv->command(mii, &mii->slot[slot_id - 1], cmd, param);
return -1;
}

74
src/mii_slot.h Normal file
View File

@ -0,0 +1,74 @@
/*
* mii_slot.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
typedef struct mii_slot_drv_t mii_slot_drv_t;
typedef struct mii_slot_t {
uint8_t id;
void * drv_priv; // for driver use
const mii_slot_drv_t * drv;
} mii_slot_t;
typedef struct mii_t mii_t;
/*
* That won't handle secondary ROM banks, we'll do that if we ever need it
*/
typedef struct mii_slot_drv_t {
struct mii_slot_drv_t *next; // in global driver queue
uint32_t enable_flag; // if there is a MII_INIT_xxx flag
const char * name;
const char * desc;
int (*probe)(mii_t * mii, uint32_t flags);
int (*init)(mii_t * mii, struct mii_slot_t *slot);
void (*reset)(mii_t * mii, struct mii_slot_t *slot); /* optional */
void (*run)(mii_t * mii, struct mii_slot_t *slot); /* optional */
uint8_t (*access)(
mii_t * mii,
struct mii_slot_t *slot,
uint16_t addr, uint8_t data, bool write);
// arbitrary command for load/save etc /* optional */
int (*command)(
mii_t * mii,
struct mii_slot_t *slot,
uint8_t cmd,
void * param);
} mii_slot_drv_t;
// get driver installed in slot_id
mii_slot_drv_t *
mii_slot_drv_get(
mii_t *mii,
uint8_t slot_id);
// install driver 'driver_name' in slot_id slot
int
mii_slot_drv_register(
mii_t *mii,
uint8_t slot_id,
const char *driver_name);
// find a driver 'name' in the global list
mii_slot_drv_t *
mii_slot_drv_find(
mii_t *mii,
const char * name);
enum {
MII_SLOT_DRIVE_COUNT = 0x01,
MII_SLOT_DRIVE_LOAD = 0x20, // + drive index 0...n
};
// send a command to a slot/driver. Return >=0 if ok, -1 if error
int
mii_slot_command(
mii_t *mii,
uint8_t slot_id,
uint8_t cmd,
void * param);

212
src/mii_speaker.c Normal file
View File

@ -0,0 +1,212 @@
/*
* mii_speaker.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mii.h"
#include "mii_speaker.h"
// one frame of audio per frame of video?
#define MII_SPEAKER_FRAME_SIZE (MII_SPEAKER_FREQ / 60)
// TODO Make some sort of driver for audio and move alsa code there
#ifdef HAS_ALSA
#include <alsa/asoundlib.h>
#define PCM_DEVICE "default"
static int
_alsa_init(
mii_speaker_t *s)
{
int pcm;
unsigned int rate = 44100, channels = 1;
snd_pcm_t *alsa;
snd_pcm_hw_params_t *params;
snd_pcm_uframes_t frames;
/* Open the PCM device in playback mode */
if ((pcm = snd_pcm_open(&alsa, PCM_DEVICE,
SND_PCM_STREAM_PLAYBACK, 0)) < 0)
printf("ERROR: Can't open \"%s\" PCM device. %s\n",
PCM_DEVICE, snd_strerror(pcm));
/* Allocate parameters object and fill it with default values*/
snd_pcm_hw_params_alloca(&params);
snd_pcm_hw_params_any(alsa, params);
/* Set parameters */
if ((pcm = snd_pcm_hw_params_set_access(alsa, params,
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
printf("ERROR: Can't set interleaved mode. %s\n",
snd_strerror(pcm));
if ((pcm = snd_pcm_hw_params_set_format(alsa, params,
SND_PCM_FORMAT_S16_LE)) < 0)
printf("ERROR: Can't set format. %s\n",
snd_strerror(pcm));
if ((pcm = snd_pcm_hw_params_set_channels(alsa, params, channels)) < 0)
printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm));
if ((pcm = snd_pcm_hw_params_set_rate_near(alsa, params, &rate, 0)) < 0)
printf("ERROR: Can't set rate. %s\n",
snd_strerror(pcm));
frames = MII_SPEAKER_FRAME_SIZE;
/* Write parameters */
if ((pcm = snd_pcm_hw_params(alsa, params)) < 0)
printf("ERROR: Can't set harware parameters. %s\n",
snd_strerror(pcm));
// printf("%s frames want %d got %ld\n",
// __func__, MII_SPEAKER_FRAME_SIZE, frames);
snd_pcm_sw_params_t *sw_params;
snd_pcm_sw_params_alloca (&sw_params);
snd_pcm_sw_params_current (alsa, sw_params);
snd_pcm_sw_params_set_start_threshold(alsa, sw_params, frames * 4);
snd_pcm_sw_params_set_avail_min(alsa, sw_params, frames*4);
snd_pcm_sw_params(alsa, sw_params);
s->fsize = frames;
s->alsa_pcm = alsa;
snd_pcm_prepare(s->alsa_pcm);
return 0;
}
#endif
// Initialize the speaker with the frame size in samples
void
mii_speaker_init(
struct mii_t * mii,
mii_speaker_t *s)
{
s->mii = mii;
s->fsize = MII_SPEAKER_FRAME_SIZE;
#ifdef HAS_ALSA
_alsa_init(s); // this can/will change fsize
#endif
s->vol_multiplier = 0.2;
s->sample = 0x8000;
s->findex = 0;
for (int i = 0; i < MII_SPEAKER_FRAME_COUNT; i++)
s->frame[i].audio = calloc(sizeof(s->frame[i].audio[0]), s->fsize);
s->frame[0].start = mii->cycles;
}
// Called when $c030 is touched, place a sample at the 'appropriate' time
void
mii_speaker_click(
mii_speaker_t *s)
{
// if CPU speed has changed, recalculate the number of cycles per sample
if (s->cpu_speed != s->mii->speed) {
s->cpu_speed = s->mii->speed;
s->clk_per_sample = ((1000000.0 /* / s->mii->speed */) /
(float)MII_SPEAKER_FREQ) + 0.5f;
printf("%s: %d cycles per sample\n", __func__, s->clk_per_sample);
}
mii_audio_frame_t *f = &s->frame[s->findex];
// if we had stopped playing for 2 frames, restart
if (f->start == 0 ||
(s->mii->cycles - f->start) > (2 * s->fsize * s->clk_per_sample)) {
// printf("Restarting playback\n");
#ifdef HAS_ALSA
snd_pcm_prepare(s->alsa_pcm);
#endif
f->start = s->mii->cycles - (s->clk_per_sample * 8);
f->fill = 0;
// add a small attack to the start of the frame to soften the beeps
// we are going to flip the sample, so we need to preemptively
// flip the attack as well
mii_audio_sample_t attack = s->sample ^ 0xffff;
for (int i = 8; i >= 1; i--)
f->audio[f->fill++] = (attack / i) * s->vol_multiplier;
s->fplay = s->findex; // restart here
}
long sample_index = (s->mii->cycles - f->start) / s->clk_per_sample;
// fill from last sample to here with the current sample
for (; f->fill < sample_index && f->fill < s->fsize; f->fill++)
f->audio[f->fill] = s->sample * s->vol_multiplier;
// if we've gone past the end of the frame, switch to the next one
if (sample_index >= s->fsize) {
sample_index = sample_index % s->fsize;
__uint128_t newstart = s->mii->cycles - (sample_index * s->clk_per_sample);
s->findex = (s->findex + 1) % MII_SPEAKER_FRAME_COUNT;
f = &s->frame[s->findex];
f->start = newstart;
f->fill = 0;
// fill from start of this frame to newly calculated sample_index
for (; f->fill < sample_index && f->fill < s->fsize; f->fill++)
f->audio[f->fill] = s->sample * s->vol_multiplier;
}
s->sample ^= 0xffff;
f->audio[sample_index] = s->sample * s->vol_multiplier;
}
int g_mii_audio_record_fd = -1;
// Check to see if there's a new frame to send, send it
void
mii_speaker_run(
mii_speaker_t *s)
{
mii_audio_frame_t *f = &s->frame[s->fplay];
// here we check if the frame we want to play is filled, and if it's
// been long enough since we started filling it to be sure we have
// enough samples to play.
// There's also the case were we stopped playing and the last frame
// wasn't complete, in which case we flush it as well
if (f->fill && ((s->mii->cycles - f->start) >
(s->fsize * s->clk_per_sample * 2))) {
s->fplaying = s->fplay;
s->fplay = (s->fplay + 1) % MII_SPEAKER_FRAME_COUNT;
f = &s->frame[s->fplaying];
if (!s->muted) {
#ifdef HAS_ALSA
int pcm;
if (g_mii_audio_record_fd != -1)
write(g_mii_audio_record_fd, f->audio,
f->fill * sizeof(s->frame[0].audio[0]));
if ((pcm = snd_pcm_writei(s->alsa_pcm,
f->audio, f->fill)) == -EPIPE) {
printf("%s Underrun.\n", __func__);
snd_pcm_recover(s->alsa_pcm, pcm, 1);
}
#endif
}
f->fill = 0;
}
}
// this is here so we dont' have to drag in libm math library.
double fastPow(double a, double b) {
union { double d; int32_t x[2]; } u = { .d = a };
u.x[1] = (int)(b * (u.x[1] - 1072632447) + 1072632447);
u.x[0] = 0;
return u.d;
}
// take the volume from 0 to 10, save it, convert it to a multiplier
void
mii_speaker_volume(
mii_speaker_t *s,
float volume)
{
if (volume < 0) volume = 0;
else if (volume > 10) volume = 10;
double mul = (fastPow(10.0, volume / 10.0) / 10.0) - 0.09;
s->vol_multiplier = mul;
s->volume = volume;
// printf("audio: speaker volume set to %.3f (%.4f)\n", volume, mul);
}

58
src/mii_speaker.h Normal file
View File

@ -0,0 +1,58 @@
/*
* mii_speaker.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#define MII_SPEAKER_FREQ (44100)
#define MII_SPEAKER_FRAME_COUNT 4
struct mii_t;
struct snd_pcm_t;
typedef int16_t mii_audio_sample_t;
typedef struct mii_audio_frame_t {
__uint128_t start;
uint16_t fill;
mii_audio_sample_t * audio;
} mii_audio_frame_t;
typedef struct mii_speaker_t {
struct mii_t * mii;
void * alsa_pcm; // alsa pcm handle
bool muted; // if true, don't play anything
float volume; // volume, 0.0 to 10.0
float vol_multiplier; // sample multiplier, 0.0 to 1.0
float cpu_speed; // CPU speed in MHz, to calculate clk_per_sample
uint8_t fplay; // frame we want to play
uint8_t fplaying; // index of the current playing frame
uint16_t fsize; // size in samples of a frame
uint8_t findex; // frame we are currently filling
uint16_t clk_per_sample; // number of cycles per sample (at current CPU speed)
mii_audio_sample_t sample; // current value for the speaker output
mii_audio_frame_t frame[MII_SPEAKER_FRAME_COUNT];
} mii_speaker_t;
// Initialize the speaker with the frame size in samples
void
mii_speaker_init(
struct mii_t * mii,
mii_speaker_t *speaker);
// Called when $c030 is touched, place a sample at the 'appropriate' time
void
mii_speaker_click(
mii_speaker_t *speaker);
// Check to see if there's a new frame to send, send it
void
mii_speaker_run(
mii_speaker_t *speaker);
// volume from 0 to 10, sets the audio sample multiplier.
void
mii_speaker_volume(
mii_speaker_t *s,
float volume);

59
src/mii_sw.h Normal file
View File

@ -0,0 +1,59 @@
#pragma once
enum {
SW80STOREOFF = 0xc000,
SW80STOREON = 0xc001,
SWALTCHARSETOFF = 0xc00e,
SWALTCHARSETON = 0xc00f,
SW80STORE = 0xc018,
SWVBL = 0xc019,
SW80COL = 0xc01f,
SWTEXT = 0xc01a,
SWMIXED = 0xc01b,
SWPAGE2 = 0xc01c,
SWHIRES = 0xc01d,
SWALTCHARSET = 0xc01e,
SW80COLOFF = 0xc00c,
SW80COLON = 0xc00d,
SWTEXTOFF = 0xc050, // (AKA LORES ON)
SWTEXTON = 0xc051,
SWMIXEDOFF = 0xc052,
SWMIXEDON = 0xc053,
SWPAGE2OFF = 0xc054,
SWPAGE2ON = 0xc055,
SWHIRESOFF = 0xc056,
SWHIRESON = 0xc057,
// this one is inverted, the ON is the even address
SWDHIRESOFF = 0xc05f, // AN3_ON
SWDHIRESON = 0xc05e, // AN3_OFF
SWAN3 = 0xc05e, // AN3 status
SWAN3_REGISTER = 0xc05f, // AN3 register for video mode
SWRDDHIRES = 0xc07f,
SWRAMRDOFF = 0xc002,
SWRAMRDON = 0xc003,
SWRAMWRTOFF = 0xc004,
SWRAMWRTON = 0xc005,
SWALTPZOFF = 0xc008,
SWALTPZON = 0xc009,
SWINTCXROMOFF = 0xc006,
SWINTCXROMON = 0xc007,
SWSLOTC3ROMOFF = 0xc00a,
SWSLOTC3ROMON = 0xc00b,
SWBSRBANK2 = 0xc011,
SWBSRREADRAM = 0xc012,
SWRAMRD = 0xc013,
SWRAMWRT = 0xc014,
SWINTCXROM = 0xc015,
SWALTPZ = 0xc016,
SWSLOTC3ROM = 0xc017,
SWSPEAKER = 0xc030,
SWKBD = 0xc000,
SWAKD = 0xc010,
};

415
src/mii_video.c Normal file
View File

@ -0,0 +1,415 @@
/*
* mii_video.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mii.h"
#include "mii_bank.h"
#include "mii_rom_iiee_video.h"
#include "mii_sw.h"
#include "minipt.h"
#define VIDEO_RESOLUTION_X 280
#define VIDEO_RESOLUTION_Y 192
#define VIDEO_BYTES_PER_LINE 40
#define SCREEN_LINE_OFFSET 0x80
#define VIDEO_SEGMENT_OFFSET 0x28
enum {
// https://rich12345.tripod.com/aiivideo/vbl.html
MII_VBL_DOWN_CYCLES = 12480,
MII_VBL_UP_CYCLES = 4550,
MII_VIDEO_H_CYCLES = 40,
MII_VIDEO_HB_CYCLES = 25,
};
/*
* Colors were lifted from
* https://comp.sys.apple2.narkive.com/lTSrj2ZI/apple-ii-colour-rgb
*/
#define HI_RGB(r,g,b) (0xff000000 | ((b) << 16) | ((g) << 8) | (r))
static const uint32_t lores_colors[] = {
[0x0] = HI_RGB(0x00, 0x00, 0x00), // black
[0x1] = HI_RGB(0xe3, 0x1e, 0x60), // magenta
[0x2] = HI_RGB(0x60, 0x4e, 0xbd), // dark blue
[0x3] = HI_RGB(0xff, 0x44, 0xfd), // purple
[0x4] = HI_RGB(0x00, 0xa3, 0x60), // dark green
[0x5] = HI_RGB(0x9c, 0x9c, 0x9c), // gray 1
[0x6] = HI_RGB(0x14, 0xcf, 0xfd), // medium blue
[0x7] = HI_RGB(0xd0, 0xc3, 0xff), // light blue
[0x8] = HI_RGB(0x60, 0x72, 0x03), // brown
[0x9] = HI_RGB(0xff, 0x6a, 0x3c), // orange
[0xa] = HI_RGB(0x9c, 0x9c, 0x9c), // gray 2
[0xb] = HI_RGB(0xff, 0xa0, 0xd0), // pink
[0xc] = HI_RGB(0x14, 0xf5, 0x3c), // light green
[0xd] = HI_RGB(0xd0, 0xdd, 0x8d), // yellow
[0xe] = HI_RGB(0x72, 0xff, 0xd0), // aqua
[0xf] = HI_RGB(0xff, 0xff, 0xff), // white
};
enum {
C_BLACK = HI_RGB(0x00, 0x00, 0x00), // black
C_PURPLE = HI_RGB(0xff, 0x44, 0xfd), // purple
C_GREEN = HI_RGB(0x14, 0xf5, 0x3c), // green
C_BLUE = HI_RGB(0x14, 0xcf, 0xfd), // blue
C_ORANGE = HI_RGB(0xff, 0x6a, 0x3c), // orange
C_WHITE = HI_RGB(0xff, 0xff, 0xff), // white
};
static const uint32_t hires_colors[] = {
C_BLACK,
C_PURPLE,
C_GREEN,
C_GREEN,
C_PURPLE,
C_BLUE,
C_ORANGE,
C_ORANGE,
C_BLUE,
C_WHITE,
};
static const uint32_t dhires_colors[] = {
[0x0] = HI_RGB(0x00, 0x00, 0x00), // black
[0x1] = HI_RGB(0xe3, 0x1e, 0x60), // magenta
[0x2] = HI_RGB(0x60, 0x72, 0x03), // brown
[0x3] = HI_RGB(0xff, 0x6a, 0x3c), // orange
[0x4] = HI_RGB(0x00, 0xa3, 0x60), // dark green
[0x5] = HI_RGB(0x9c, 0x9c, 0x9c), // gray 1
[0x6] = HI_RGB(0x14, 0xf5, 0x3c), // light green
[0x7] = HI_RGB(0xd0, 0xdd, 0x8d), // yellow
[0x8] = HI_RGB(0x60, 0x4e, 0xbd), // dark blue
[0x9] = HI_RGB(0xff, 0x44, 0xfd), // purple
[0xa] = HI_RGB(0x9c, 0x9c, 0x9c), // gray 2
[0xb] = HI_RGB(0xff, 0xa0, 0xd0), // pink
[0xc] = HI_RGB(0x14, 0xcf, 0xfd), // medium blue
[0xd] = HI_RGB(0xd0, 0xc3, 0xff), // light blue
[0xe] = HI_RGB(0x72, 0xff, 0xd0), // aqua
[0xf] = HI_RGB(0xff, 0xff, 0xff), // white
};
static const uint32_t mono[3][2] = {
{ 0xff000000, C_WHITE },
{ 0xff000000, C_GREEN },
{ 0xff000000, C_ORANGE },
};
/* this 'dims' the colors for every second line of pixels */
#define C_SCANLINE_MASK 0xffc0c0c0
static inline uint16_t
_mii_line_to_video_addr(
uint16_t addr,
uint8_t line)
{
addr += ((line & 0x07) << 10) |
(((line >> 3) & 7) << 7) |
((line >> 6) << 5) | ((line >> 6) << 3);
return addr;
}
/*
* This is the state of the video output
* All timings lifted from https://rich12345.tripod.com/aiivideo/vbl.html
*
* This is a 'protothread' basically cooperative scheduling using an
* old compiler trick. It's not a real thread, but it's a way to
* write code that looks like a thread, and is easy to read.
* The 'pt_start' macro starts the thread, and pt_yield() yields
* the thread to the main loop.
* The pt_end() macro ends the thread.
* Remeber you cannot have locals in the thread, they must be
* static or global.
* *everything* before the pt_start call is ran every time, so you can use
* that to reload some sort of state, as here, were we reload all the
* video mode softwsitches.
*/
void
mii_video_run(
mii_t *mii)
{
// no need to do anything, we're waiting cycles
if (mii->video.wait) {
if (mii->video.wait > mii->cycles)
return;
// extra cycles we waited are kept around for next delay
mii->video.wait = mii->cycles - mii->video.wait;
}
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
bool text = !!mii_bank_peek(main, SWTEXT);
bool page2 = !!mii_bank_peek(main, SWPAGE2);
bool col80 = !!mii_bank_peek(main, SW80COL);
bool store80 = !!mii_bank_peek(main, SW80STORE);
bool mixed = !!mii_bank_peek(main, SWMIXED);
bool hires = !!mii_bank_peek(main, SWHIRES);
bool dhires = !!mii_bank_peek(main, SWRDDHIRES);
pt_start(mii->video.state);
/*
We cheat and draw a whole line at a time, then 'wait' until
horizontal blanking, then wait until vertical blanking.
*/
do {
// 'clear' VBL flag. Flag is 0 during retrace
mii_bank_poke(main, SWVBL, 0x80);
if (mixed && !text) {
text = mii->video.line >= 192 - (4 * 8);
hires = 0;
}
// http://www.1000bit.it/support/manuali/apple/technotes/aiie/tn.aiie.03.html
if (hires && !text && col80 && dhires) {
if (store80)
page2 = 0;
uint8_t reg = mii_bank_peek(main, SWAN3_REGISTER);
uint16_t a = (0x2000 + (0x2000 * page2));
a = _mii_line_to_video_addr(a, mii->video.line);
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
mii_bank_t * aux = &mii->bank[MII_BANK_AUX];
if (reg == 0 || mii->video.color_mode != MII_VIDEO_COLOR) {
for (int x = 0; x < 40; x += 1) {
uint32_t ext = (mii_bank_peek(aux, a + x) & 0x7f) |
((mii_bank_peek(main, a + x) & 0x7f) << 7);
for (int bi = 0; bi < 14; bi++) {
uint8_t pixel = (ext >> bi) & 1;
uint32_t col = pixel ?
mono[mii->video.color_mode][1] :
mono[mii->video.color_mode][0];
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
}
}
} else { // color mode
int x = 0, dx = 0;
do {
uint64_t run = 0;
// get 8 bytes, so we get 8*7=56 bits for 14 pixels
for (int bx = 0; bx < 8 && x < 80; bx++, x++) {
uint8_t b = mii_bank_peek(
x & 1 ? main : aux, a + (x / 2));
run |= ((uint64_t)(b & 0x7f) << (bx * 7));
}
for (int px = 0; px < 14 && dx < 80*2; px++, dx++) {
uint8_t pixel = run & 0xf;
run >>= 4;
uint32_t col = dhires_colors[pixel];
*screen++ = col;
*screen++ = col;
*screen++ = col;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
}
} while (x < 80);
}
} else if (hires && !text) {
if (store80)
page2 = 0;
uint16_t a = (0x2000 + (0x2000 * page2));
a = _mii_line_to_video_addr(a, mii->video.line);
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
uint8_t b0 = 0;
uint8_t b1 = mii_bank_peek(main, a + 0);
for (int x = 0; x < 40; x++) {
uint8_t b2 = mii_bank_peek(main, a + x + 1);
// last 2 pixels, current 7 pixels, next 2 pixels
uint16_t run = ((b0 & 0x60) >> ( 5 )) |
((b1 & 0x7f) << ( 2 )) |
((b2 & 0x03) << ( 9 ));
int odd = (x & 1) << 1;
int offset = (b1 & 0x80) >> 5;
if (mii->video.color_mode == MII_VIDEO_COLOR) {
for (int i = 0; i < 7; i++) {
uint8_t left = (run >> (1 + i)) & 1;
uint8_t pixel = (run >> (2 + i)) & 1;
uint8_t right = (run >> (3 + i)) & 1;
int idx = 0; // black
if (pixel) {
if (left || right) {
idx = 9; // white
} else {
idx = offset + odd + (i & 1) + 1;
}
} else {
if (left && right) {
idx = offset + odd + 1 - (i & 1) + 1;
}
}
uint32_t col = hires_colors[idx];
*screen++ = col;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
}
} else {
for (int i = 0; i < 7; i++) {
uint8_t pixel = (run >> (2 + i)) & 1;
uint32_t col = pixel ?
mono[mii->video.color_mode][1] :
mono[mii->video.color_mode][0];
*screen++ = col;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
}
}
b0 = b1;
b1 = b2;
}
} else {
if (store80)
page2 = 0;
uint16_t a = (0x400 + (0x400 * page2));
int i = mii->video.line >> 3;
a += ((i & 0x07) << 7) | ((i >> 3) << 5) | ((i >> 3) << 3);
mii_bank_t * aux = &mii->bank[MII_BANK_AUX];
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
for (int x = 0; x < 40 + (40 * col80); x++) {
uint8_t c = 0;
if (col80)
c = mii_bank_peek(
x & 1 ? main : aux, a + (x >> 1));
else
c = mii_bank_peek(main, a + x);
if (text) {
const uint8_t * rom = iie_enhanced_video + (c << 3);
uint8_t bits = rom[mii->video.line & 0x07];
for (int pi = 0; pi < 7; pi++) {
uint8_t pixel = (bits >> pi) & 1;
uint32_t col = pixel ?
mono[mii->video.color_mode][0] :
mono[mii->video.color_mode][1];
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
if (!col80) {
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
}
}
} else { // lores graphics
int lo_line = mii->video.line / 4;
c = c >> ((lo_line & 1) * 4);
uint32_t pixel = lores_colors[c & 0x0f];
for (int pi = 0; pi < 7; pi++) {
*screen++ = pixel;
*l2++ = pixel & C_SCANLINE_MASK;
if (!col80) {
*screen++ = pixel;
*l2++ = pixel & C_SCANLINE_MASK;
}
}
}
}
}
mii->video.line++;
if (mii->video.line == 192) {
mii->video.line = 0;
mii->video.wait = mii->cycles - mii->video.wait +
MII_VIDEO_H_CYCLES * mii->speed;
pt_yield(mii->video.state);
mii_bank_poke(main, SWVBL, 0x00);
if (mii->video.vbl_irq)
mii->cpu_state.irq = 1;
mii->video.wait = mii->cycles - mii->video.wait +
MII_VBL_UP_CYCLES * mii->speed;
mii->video.frame_count++;
pt_yield(mii->video.state);
} else {
mii->video.wait = mii->cycles - mii->video.wait +
(MII_VIDEO_H_CYCLES + MII_VIDEO_HB_CYCLES) *
mii->speed;
pt_yield(mii->video.state);
}
} while (1);
pt_end(mii->video.state);
return;
}
bool
mii_access_video(
mii_t *mii,
uint16_t addr,
uint8_t * byte,
bool write)
{
bool res = false;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
switch (addr) {
case SWALTCHARSETOFF:
case SWALTCHARSETON:
if (!write) break;
res = true;
mii_bank_poke(main, SWALTCHARSET, (addr & 1) << 7);
break;
case SWVBL:
case SW80COL:
case SWTEXT:
case SWMIXED:
case SWPAGE2:
case SWHIRES:
case SWALTCHARSET:
case SWRDDHIRES:
res = true;
if (!write)
*byte = mii_bank_peek(main, addr);
break;
case SW80COLOFF:
case SW80COLON:
if (!write) break;
res = true;
mii_bank_poke(main, SW80COL, (addr & 1) << 7);
// printf("80COL %s\n", on ? "ON" : "OFF");
break;
case SWDHIRESOFF: // 0xc05f,
case SWDHIRESON: { // = 0xc05e,
res = true;
uint8_t an3 = !!mii_bank_peek(main, SWAN3);
bool an3_on = !!(addr & 1); // 5f is ON, 5e is OFF
uint8_t reg = mii_bank_peek(main, SWAN3_REGISTER);
if (an3_on && !an3) {
uint8_t bit = !!mii_bank_peek(main, SW80COL);
reg = ((reg << 1) | bit) & 3;
printf("VIDEO 80:%d REG now %x\n", bit, reg);
mii_bank_poke(main, SWAN3_REGISTER, reg);
}
mii_bank_poke(main, SWAN3, an3_on);
printf("DHRES IS %s mode:%d\n",
(addr & 1) ? "OFF" : "ON", reg);
mii_bank_poke(main, SWRDDHIRES, (!(addr & 1)) << 7);
} break;
case SWTEXTOFF:
case SWTEXTON:
res = true;
mii_bank_poke(main, SWTEXT, (addr & 1) << 7);
break;
case SWMIXEDOFF:
case SWMIXEDON:
res = true;
mii_bank_poke(main, SWMIXED, (addr & 1) << 7);
break;
}
return res;
}

Some files were not shown because too many files have changed in this diff Show More