├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── README.serial.md ├── dep ├── .keepme └── musashi │ ├── .keepme │ └── softfloat │ └── .keepme ├── doc └── 3B1 Memory Map.ods ├── freebee.1 ├── obj ├── .keepme └── musashi │ ├── .keepme │ └── softfloat │ └── .keepme ├── sample.freebee.toml ├── src ├── diskimd.c ├── diskimg.h ├── diskraw.c ├── fbconfig.c ├── fbconfig.h ├── i8274.c ├── i8274.h ├── keyboard.c ├── keyboard.h ├── lightbar.c ├── m68kconf.h ├── main.c ├── memory.c ├── memory.h ├── state.c ├── state.h ├── tc8250.c ├── tc8250.h ├── toml.c ├── toml.h ├── utils.h ├── version.h.in ├── wd2010.c ├── wd2010.h ├── wd279x.c └── wd279x.h └── tools ├── Makefile ├── makehdimg.1 └── makehdimg.c /.gitignore: -------------------------------------------------------------------------------- 1 | obj/*.o 2 | dep/*.d 3 | *~ 4 | .*.sw? 5 | .~lock* 6 | .buildnum 7 | 8 | # musashi build artefacts 9 | src/musashi/m68kopac.c 10 | src/musashi/m68kopdm.c 11 | src/musashi/m68kopnz.c 12 | src/musashi/m68kops.c 13 | src/musashi/m68kops.h 14 | dep/musashi/*.d 15 | dep/musashi/softfloat/*.d 16 | obj/musashi/*.o 17 | obj/musashi/softfloat/*.o 18 | obj/musashi/m68kmake 19 | 20 | # version header 21 | src/version.h 22 | 23 | # final executable binary 24 | freebee 25 | 3b1emu 26 | 27 | # tools 28 | makehdimg 29 | 30 | # ignore ROMs and techref 31 | roms 32 | TRM 33 | misc 34 | discs 35 | 36 | # config file 37 | .freebee.toml 38 | 39 | # disc images 40 | *.img 41 | 42 | # serial port pty link 43 | serial-pty 44 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/musashi"] 2 | path = src/musashi 3 | url = https://github.com/kstenerud/Musashi.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Phil's multiplatform makefile template 2 | # With auto-incrementing build number and automatic version.h generation 3 | # Version 1.9, 2010-02-15 4 | # 5 | # The latest version of this Makefile can be found at http://www.philpem.me.uk/ 6 | # 7 | # 8 | # Copyright (c) 2010 Philip Pemberton 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | # 28 | # 29 | # Instructions for use: 30 | # Run 'make init' to create the required directories 31 | # Add your source files to the 'SOURCES' list, and change the TARGET filename 32 | # Set the desired build type and platform in the BUILD_TYPE and PLATFORM 33 | # variables respectively 34 | # Set your project type (C only, or C++) in the SRC_TYPE variable 35 | # Add any libraries you need to link against to the 'LIB' list 36 | # Run 'make' 37 | # 38 | # Object files are created in the 'obj' subdirectory, from source code in the 39 | # 'src' directory. Dependency files are created in the 'dep' directory from 40 | # the same source code the object files are created from. 41 | # 42 | # Supported targets are: 43 | # all Build everything. 44 | # update-revision Increment the build number without building anything. 45 | # clean-versioninfo Delete src/version.h (will be rebuilt on the next 46 | # 'make all'). 47 | # init Initialise the build system for a new project. 48 | # WARNING: overwrites .buildnum and src/version.h.in! 49 | # cleandep Delete all dependency files. 50 | # clean Delete all dependency, intermediate and target files. 51 | # tidy Delete all dependency and intermediate files, leaving 52 | # the target file intact. 53 | # 54 | # If you want to reset the build number to zero, delete '.buildnum'. This 55 | # should be done whenever the major or minor version changes. Excluding 56 | # .buildnum from version control may also be a good idea, depending on how 57 | # you want your build numbers to work. 58 | # 59 | # The BUILD_TYPE variable contains the current build type. There are two 60 | # supported build types: 61 | # debug Debug mode - object files are compiled with debug information 62 | # and the target is left unstripped. 63 | # release Release mode - object files are not compiled with debug info, 64 | # and the target is fed through strip to remove redundant 65 | # data. 66 | # 67 | # The PLATFORM variable contains the current target platform. There are two 68 | # supported platforms: 69 | # linux GNU/Linux with GNU Compiler Collection 70 | # win32 Windows 32-bit with MinGW 71 | # 72 | # The EXTSRC variable is used to specify other files to build. It is typically 73 | # used to specify platform or build-type specific source files, e.g. 74 | # 75 | # ifeq ($(BUILD_TYPE),debug-memwatch) 76 | # CFLAGS += -g -ggdb 77 | # CPPFLAGS += -DMEMWATCH 78 | # INCPATH += ./memwatch 79 | # EXTSRC += memwatch/memwatch.c 80 | # endif 81 | # 82 | # (example taken from one of my projects that allowed the use of Memwatch to 83 | # track down memory allocation/deallocation bugs) 84 | # 85 | # 86 | # Change history: 87 | # 1.9 - Bugfix -- if CFLAGS contained a forward-slash, sed would fall over. 88 | # Also added SDL support and fixed the date/time formats. To use SDL, 89 | # set ENABLE_SDL to "yes". 90 | # 1.8 - Now supports the use of the wxWidgets GUI framework. To turn 91 | # this on, set ENABLE_WX to "yes". 92 | # 1.7 - Now creates a basic Hgignore file and directory keepers for the 93 | # dep and obj directories. 94 | # 1.6 - Added CFLAGS and CXXFLAGS to the command-lines for the dependency 95 | # building commands. This was causing issues with C99 / C++0x mode. 96 | # 1.5 - Added support for Mercurial revision (changeset ID) display 97 | # Fixed a few issues with Subversion support (svn: and version 0 would 98 | # be displayed for exported code) 99 | # 100 | 101 | #### 102 | # Build configuration 103 | #### 104 | 105 | # version information -- major.minor.extra 106 | # note that VER_EXTRA can be overridden on the command line, e.g.: 107 | # make VER_EXTRA=12345 all 108 | VER_MAJOR = 0 109 | VER_MINOR = 0 110 | VER_EXTRA ?= 111 | 112 | # build platform: win32 or linux 113 | PLATFORM ?= linux 114 | # build type: release or debug 115 | BUILD_TYPE ?= release 116 | 117 | # target executable 118 | TARGET = freebee 119 | 120 | # source files that produce object files 121 | SRC = main.c state.c memory.c wd279x.c wd2010.c keyboard.c tc8250.c diskraw.c diskimd.c i8274.c fbconfig.c toml.c 122 | SRC += musashi/m68kcpu.c musashi/m68kdasm.c musashi/m68kops.c musashi/softfloat/softfloat.c 123 | 124 | # source type - either "c" or "cpp" (C or C++) 125 | SRC_TYPE = c 126 | 127 | # additional object files that don't necessarily include source 128 | EXT_OBJ = 129 | # libraries to link in -- these will be specified as "-l" parameters, the -l 130 | # is prepended automatically 131 | LIB = m 132 | # library paths -- where to search for the above libraries 133 | LIBPATH = 134 | # include paths -- where to search for #include files (in addition to the 135 | # standard paths 136 | INCPATH = 137 | # garbage files that should be deleted on a 'make clean' or 'make tidy' 138 | GARBAGE = obj/musashi/m68kmake obj/musashi/m68kmake.exe obj/musashi/m68kmake.o 139 | 140 | # extra dependencies - files that we don't necessarily know how to build, but 141 | # that are required for building the application; e.g. object files or 142 | # libraries in sub or parent directories 143 | EXTDEP = 144 | 145 | # Extra libraries 146 | # wxWidgets: set to "yes" to enable, anything else to disable 147 | ENABLE_WX = no 148 | # wxWidgets: list of wxWidgets libraries to enable 149 | WX_LIBS = std 150 | # SDL: set to "yes" to enable, anything else to disable 151 | ENABLE_SDL = yes 152 | 153 | #### 154 | # Win32 target-specific settings 155 | #### 156 | ifeq ($(strip $(PLATFORM)),win32) 157 | # windows executables have a .exe suffix 158 | TARGET := $(addsuffix .exe,$(TARGET)) 159 | # console mode application 160 | EXT_CFLAGS = -mconsole 161 | endif 162 | 163 | 164 | #### 165 | # Tool setup 166 | #### 167 | CC = gcc 168 | CXX = g++ 169 | CFLAGS = -Wall -pedantic -std=gnu99 $(EXT_CFLAGS) 170 | CXXFLAGS= -Wall -pedantic -std=gnu++0x $(EXT_CXXFLAGS) 171 | LDFLAGS = $(EXT_LDFLAGS) 172 | RM = rm 173 | STRIP = strip 174 | 175 | ############################################################################### 176 | # You should not need to touch anything below here, unless you're adding a new 177 | # platform or build type (or changing the version string format) 178 | ############################################################################### 179 | 180 | #### 181 | # A quick sanity check on the platform type 182 | #### 183 | ifneq ($(PLATFORM),linux) 184 | ifneq ($(PLATFORM),win32) 185 | $(error Platform '$(PLATFORM)' not supported. Supported platforms are: linux, win32) 186 | endif 187 | endif 188 | 189 | #### 190 | # Version info generation 191 | #### 192 | # get the current build number 193 | VER_BUILDNUM = $(shell cat .buildnum) 194 | 195 | #### --- begin Git revision grabber --- 196 | # get the current Git ID 197 | VER_GITREV=$(shell ((git rev-parse --short HEAD) || echo "NONE") 2>/dev/null) 198 | ifneq ($(VER_GITREV),NONE) 199 | VER_VCS = git 200 | VER_VCSREV = $(VER_GITREV) 201 | else 202 | VER_VCS = none 203 | VER_VCSREV = 0 204 | endif 205 | 206 | #### --- end version grabbers --- 207 | 208 | # start creating the revision string 209 | VER_FULLSTR = $(VER_MAJOR).$(VER_MINOR).$(VER_BUILDNUM)$(VER_EXTRA) 210 | 211 | # if this is a VCS release, include the SVN revision in the version string 212 | # also create a revision string that is either "svn:12345", "hg:12345" or 213 | # blank 214 | ifneq ($(VER_VCS),none) 215 | VER_FULLSTR += ($(VER_VCS) $(VER_VCSREV)) 216 | VER_VCSSTR = $(VER_VCS):$(VER_VCSREV) 217 | else 218 | VER_VCSSTR = 219 | endif 220 | 221 | 222 | #### 223 | # Build-type specific configuration 224 | #### 225 | ifeq ($(BUILD_TYPE),debug) 226 | CFLAGS += -g -ggdb -DDEBUG 227 | CXXFLAGS += -g -ggdb -DDEBUG 228 | else 229 | ifeq ($(BUILD_TYPE),release) 230 | CFLAGS += -O2 231 | CXXFLAGS += -O2 232 | else 233 | $(error Unsupported build type: '$(BUILD_TYPE)') 234 | endif 235 | endif 236 | 237 | #### 238 | # Musashi config file 239 | #### 240 | ifeq ($(PLATFORM),linux) 241 | CFLAGS += -DMUSASHI_CNF=\"../m68kconf.h\" 242 | CXXFLAGS += -DMUSASHI_CNF=\"../m68kconf.h\" 243 | endif 244 | ifeq ($(PLATFORM),win32) 245 | CFLAGS += -DMUSASHI_CNF="../m68kconf.h" 246 | CXXFLAGS += -DMUSASHI_CNF="../m68kconf.h" 247 | endif 248 | 249 | #### 250 | # wxWidgets support 251 | #### 252 | ifeq ($(ENABLE_WX),yes) 253 | ifeq ($(BUILD_TYPE),debug) 254 | LIBLNK += `wx-config --debug --libs $(WX_LIBS)` 255 | CFLAGS += `wx-config --debug --cflags $(WX_LIBS)` 256 | CXXFLAGS += `wx-config --debug --cxxflags $(WX_LIBS)` 257 | CPPFLAGS += `wx-config --debug --cppflags $(WX_LIBS)` 258 | else 259 | ifeq ($(BUILD_TYPE),release) 260 | LIBLNK += `wx-config --libs $(WX_LIBS)` 261 | CFLAGS += `wx-config --cflags $(WX_LIBS)` 262 | CPPFLAGS += `wx-config --cppflags $(WX_LIBS)` 263 | CXXFLAGS += `wx-config --cxxflags $(WX_LIBS)` 264 | else 265 | $(error Unsupported build type: '$(BUILD_TYPE)') 266 | endif 267 | endif 268 | endif 269 | 270 | #### 271 | # SDL support 272 | #### 273 | ifeq ($(ENABLE_SDL),yes) 274 | LIBLNK += $(shell sdl2-config --libs) 275 | CFLAGS += $(shell sdl2-config --cflags) 276 | endif 277 | 278 | 279 | #### 280 | # rules 281 | #### 282 | 283 | # object files 284 | OBJ = $(addprefix obj/, $(addsuffix .o, $(basename $(SRC))) $(EXT_OBJ)) $(addsuffix .o, $(basename $(EXTSRC))) 285 | 286 | # dependency files 287 | DEPFILES = $(addprefix dep/, $(addsuffix .d, $(basename $(SRC))) $(EXT_OBJ)) $(addsuffix .d, $(basename $(EXTSRC))) 288 | 289 | # path commands 290 | LIBLNK += $(addprefix -l, $(LIB)) 291 | LIBPTH += $(addprefix -L, $(LIBPATH)) 292 | INCPTH += $(addprefix -I, $(INCPATH)) 293 | 294 | CPPFLAGS += $(INCPTH) 295 | 296 | #### 297 | # Make sure there is at least one object file to be linked in 298 | #### 299 | ifeq ($(strip $(OBJ)),) 300 | $(error Unable to build: no object or source files specified in Makefile) 301 | endif 302 | 303 | #### 304 | # targets 305 | #### 306 | .PHONY: default all update-revision versionheader clean-versioninfo init cleandep clean tidy 307 | 308 | all: update-revision 309 | @$(MAKE) versionheader 310 | $(MAKE) $(TARGET) 311 | 312 | # increment the current build number 313 | NEWBUILD=$(shell expr $(VER_BUILDNUM) + 1) 314 | update-revision: 315 | @echo $(NEWBUILD) > .buildnum 316 | 317 | versionheader: 318 | @sed -e 's/@@datetime@@/$(shell LC_ALL=C date +"%a %d-%b-%Y %T %Z")/g' \ 319 | -e 's/@@date@@/$(shell LC_ALL=C date +"%a %d-%b-%Y")/g' \ 320 | -e 's/@@time@@/$(shell LC_ALL=C date +"%T %Z")/g' \ 321 | -e 's/@@majorver@@/$(VER_MAJOR)/g' \ 322 | -e 's/@@minorver@@/$(VER_MINOR)/g' \ 323 | -e 's/@@extraver@@/$(subst \",,$(VER_EXTRA))/g' \ 324 | -e 's/@@buildnum@@/$(VER_BUILDNUM)/g' \ 325 | -e 's/@@buildtype@@/$(BUILD_TYPE)/g' \ 326 | -e 's/@@vcs@@/$(VER_VCS)/g' \ 327 | -e 's/@@vcsrev@@/$(VER_VCSREV)/g' \ 328 | -e 's/@@vcsstr@@/$(VER_VCSSTR)/g' \ 329 | -e 's/@@fullverstr@@/$(VER_FULLSTR)/g' \ 330 | < src/version.h.in > src/version.h 331 | 332 | # version.h creation stuff based on code from the Xen makefile 333 | clean-versioninfo: 334 | @if [ ! -r src/version.h -o -O src/version.h ]; then \ 335 | rm -f src/version.h; \ 336 | fi 337 | @echo 0 > .buildnum 338 | 339 | # initialise the build system for a new project 340 | init: 341 | @mkdir -p src dep obj 342 | @echo "This file is a directory-keeper. Do not delete it." > dep/.keepme 343 | @echo "This file is a directory-keeper. Do not delete it." > obj/.keepme 344 | @echo 0 > .buildnum 345 | @echo 'syntax: glob' > .hgignore 346 | @echo 'obj/*.o' >> .hgignore 347 | @echo 'dep/*.d' >> .hgignore 348 | @echo '*~' >> .hgignore 349 | @echo '.*.sw?' >> .hgignore 350 | @echo '#define VER_COMPILE_DATETIME "@@datetime@@"' > src/version.h.in 351 | @echo '#define VER_COMPILE_DATE "@@date@@"' >> src/version.h.in 352 | @echo '#define VER_COMPILE_TIME "@@time@@"' >> src/version.h.in 353 | @echo '#define VER_COMPILE_BY "@@whoami@@"' >> src/version.h.in 354 | @echo '#define VER_COMPILE_HOST "@@hostname@@"' >> src/version.h.in 355 | @echo '#define VER_COMPILER "@@compiler@@"' >> src/version.h.in 356 | @echo '#define VER_BUILD_TYPE "@@buildtype@@"' >> src/version.h.in 357 | @echo '#define VER_CFLAGS "@@cflags@@"' >> src/version.h.in 358 | @echo '' >> src/version.h.in 359 | @echo '#define VER_MAJOR @@majorver@@' >> src/version.h.in 360 | @echo '#define VER_MINOR @@minorver@@' >> src/version.h.in 361 | @echo '#define VER_BUILDNUM @@buildnum@@' >> src/version.h.in 362 | @echo '#define VER_EXTRA "@@extraver@@"' >> src/version.h.in 363 | @echo '#define VER_VCSREV "@@vcsstr@@"' >> src/version.h.in 364 | @echo '' >> src/version.h.in 365 | @echo '#define VER_FULLSTR "@@fullverstr@@"' >> src/version.h.in 366 | @echo '' >> src/version.h.in 367 | @echo Build system initialised 368 | 369 | # remove the dependency files 370 | cleandep: 371 | -rm $(DEPFILES) 372 | 373 | # remove the dependency files and any target or intermediate build files 374 | clean: cleandep clean-versioninfo 375 | -rm $(OBJ) $(TARGET) $(GARBAGE) 376 | 377 | # remove any dependency or intermediate build files 378 | tidy: cleandep clean-versioninfo 379 | -rm $(OBJ) $(GARBAGE) 380 | 381 | ################################# 382 | 383 | $(TARGET): $(OBJ) $(EXTDEP) 384 | ifeq ($(SRC_TYPE),c) 385 | $(CC) $(CXXFLAGS) $(LDFLAGS) $(OBJ) $(LIBPTH) $(LIBLNK) -o $@ 386 | else 387 | $(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJ) $(LIBPTH) $(LIBLNK) -o $@ 388 | endif 389 | ifeq ($(BUILD_TYPE),release) 390 | $(STRIP) $(TARGET) 391 | endif 392 | 393 | ### 394 | # extra rules 395 | # example: 396 | #src/parser.c: src/parser.h 397 | 398 | 399 | #### 400 | ## musashi build rules 401 | # 68k CPU builder 402 | obj/musashi/m68kmake: obj/musashi/m68kmake.o 403 | $(CC) $(CFLAGS) $(CPPFLAGS) obj/musashi/m68kmake.o -o $@ 404 | # 68k CPU sources 405 | src/musashi/m68kops.h src/musashi/m68kops.c: obj/musashi/m68kmake src/musashi/m68k_in.c 406 | ./obj/musashi/m68kmake src/musashi src/musashi/m68k_in.c 407 | 408 | #### 409 | # make object files from C source files 410 | obj/%.o: src/%.c 411 | $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ 412 | 413 | ## 414 | # make object files from C++ source files 415 | obj/%.o: src/%.cc 416 | $(CXX) -c $(CXXFLAGS) $(CPPFLAGS) $< -o $@ 417 | 418 | obj/%.o: src/%.cpp 419 | $(CXX) -c $(CXXFLAGS) $(CPPFLAGS) $< -o $@ 420 | 421 | ### 422 | # make C files from yacc/bison source 423 | src/%.h src/%.c: src/%.y 424 | $(YACC) $(YFLAGS) -d $< 425 | mv -f y.tab.c $*.c 426 | mv -f y.tab.h $*.h 427 | 428 | ### 429 | # make C files from lex/flex source 430 | src/%.c: src/%.l 431 | $(LEX) $(LFLAGS) -o$@ $< 432 | 433 | ### 434 | # make dependencies for our source files 435 | dep/%.d: src/%.c 436 | $(CC) -MM $(CFLAGS) $(CPPFLAGS) $< > $@.$$$$; \ 437 | sed 's,\($*\)\.o[ :]*,obj/\1.o $@ : ,g' < $@.$$$$ > $@; \ 438 | rm -f $@.$$$$ 439 | 440 | dep/%.d: src/%.cpp 441 | $(CXX) -MM $(CXXFLAGS) $(CPPFLAGS) $< > $@.$$$$; \ 442 | sed 's,\($*\)\.o[ :]*,obj/\1.o $@ : ,g' < $@.$$$$ > $@; \ 443 | rm -f $@.$$$$ 444 | 445 | dep/%.d: src/%.cc 446 | $(CXX) -MM $(CXXFLAGS) $(CPPFLAGS) $< > $@.$$$$; \ 447 | sed 's,\($*\)\.o[ :]*,obj/\1.o $@ : ,g' < $@.$$$$ > $@; \ 448 | rm -f $@.$$$$ 449 | 450 | #### 451 | # pull in the dependency files, but only for 'make $(TARGET)' 452 | #### 453 | 454 | ifeq ($(MAKECMDGOALS),$(TARGET)) 455 | -include $(DEPFILES) 456 | endif 457 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # freebee 2 | FreeBee - AT&T 3B1 emulator 3 | 4 | FreeBee is an emulator for the AT&T 3B1. It's a work-in-progress, but currently works well enough to boot the operating system and to compile programs with the standard C compiler. 5 | 6 | 7 | ## Maintained by 8 | 9 | Phil Pemberton -- 10 | 11 | ## Limitations 12 | 13 | ### Mac OS X support 14 | 15 | There have historically been a few instability issues on the Mac OS X platform, related to the SDL libraries. I've not heard much about them. 16 | 17 | Unfortunately I don't have a recent Mac, so I won't be able to reproduce or test issues which affect OS X versions later than High Sierra. 18 | Bugs will likely be left open until someone with a more recent system is able to look into them (unless you open a pull request with a fix!) 19 | 20 | If you have an issue on OSX and have a Windows or Linux system, please try to reproduce the issue on there as it will be easier for me to test. 21 | Please list all the platforms you've tested on (and the result) in the issue. 22 | 23 | In summary: all support is on a best-effort basis, but **I cannot guarantee that bugs reported solely on Mac OS X will be fixed as I don't have the equipment to test with.** 24 | 25 | 26 | ### Memory mapper emulation inaccuracy 27 | 28 | There is a workaround in the memory mapping emulation, which allows supervisor-mode writes to low memory. If this is disabled, the kernel will fail to boot 29 | with a `PAGEIN` or `PAGEOUT` panic. 30 | 31 | If anyone can figure this out, 32 | 33 | 34 | ## Things which are emulated... 35 | 36 | * Revision P5.1 motherboard with 68010 processor, WD2010 hard drive controller and P5.1 upgrade. 37 | * 720x348 pixel monochrome bitmapped graphics. 38 | * 4MB RAM (2MB on the motherboard, 2MB on expansion cards). 39 | * This is the maximum allowed by the memory mapper. 40 | * Keyboard and mouse. 41 | * WD2010 MFM Winchester hard disk controller. 42 | * Two separate drives. 43 | * Maximum 1400 cylinders (limited by the UNIX OS, see [the UNIX PC FAQ, section 5.6](http://www.unixpc.org/FAQ)). 44 | * Heads fixed at 8. 45 | * Sectors per track fixed at 17. 46 | * Fixed 512 bytes per sector. 47 | * Those numbers are the default configuration; see below for more information. 48 | * WD2797 floppy disk controller. 49 | * Double-sided, 512 bytes per sector, 10 sectors per track, any number of tracks. 50 | * Realtime clock. 51 | * Reading the RTC reads the date and time from the host. 52 | * Year is fixed at 1987 due to Y2K issues in the UNIX PC Kernel. 53 | * Serial port. 54 | * Linux only: file 'serial-pty' is symlinked to PTY that can be used to access tty000 55 | * Usage instructions: [README.serial.md](README.serial.md) 56 | 57 | 58 | 59 | ## Things which aren't emulated fully (or at all) 60 | 61 | * Printer port 62 | * Modem 63 | * You will get errors that '/dev/ph0 cannot be opened' and that there was a problem with the modem. Ignore these. 64 | 65 | 66 | # Build instructions 67 | 68 | - Install the `libsdl2-dev` package 69 | - Clone a copy of Freebee (remember to check out the submodules too) 70 | - `git clone --recurse-submodules https://github.com/philpem/freebee` 71 | - Build Freebee (run 'make') 72 | 73 | 74 | # Running Freebee 75 | 76 | ## Initial Setup 77 | - Download the 3B1 ROMs from Bitsavers: [link](http://bitsavers.org/pdf/att/3b1/firmware/3b1_roms.zip) 78 | - Unzip the ROMs ZIP file and put the ROMs in a directory called `roms`: 79 | * Rename `14C 72-00616.bin` to `14c.bin` 80 | * Rename `15C 72-00617.bin` to `15c.bin` 81 | 82 | ## Option 1: Use an existing drive image 83 | - Arnold Robbins created a drive image installed with all sorts of tools: [here](https://www.skeeve.com/3b1/) 84 | - David Gesswein created a drive image for the VCF Museum: [here](http://www.pdp8online.com/3b1/demos.shtml) 85 | - Uncompress either of these images in the Freebee directory and rename the image to `hd.img`, or create a `.freebee.toml` file pointing to the image. (See the CONFIGURATION section of the man page.) 86 | 87 | ## Option 2: Do a fresh install 88 | - Download the 3B1 Foundation disk set from Bitsavers: [here](http://bitsavers.org/bits/ATT/unixPC/system_software_3.51/) 89 | * The disk images on unixpc.org don't work: the boot track is missing. 90 | * Use the replacement version of the `08_Foundation_Set_Ver_3.51.IMD` image which is available [here](https://www.skeeve.com/3b1/os-install/index.html). 91 | - Create a hard drive image file: 92 | * Use the `makehdimg` program supplied in the `tools` directory to create an initial `hd.img` file with the number of cylinders, heads and sectors per track that you want. Limits: 1400 cylinders, 16 heads, 17 sectors per track. 93 | * When using the diagnostics disk to initialize the hard disk, select "Other" and supply the correct values that correspond to the numbers used with `makehdimg`. 94 | * Alternatively, you can use `dd if=/dev/zero of=hd.img bs=512 count=$(expr 17 \* 8 \* 1024)` to create a disk matching the compiled-in defaults. Initialize the disk using the "Miniscribe 64MB" (CHS 1024:8:17, 512 bytes per sector) choice. 95 | * The second hard drive file is optional. If present, it should be called `hd2.img`. You can copy an existing `hd.img` to `hd2.img` as a quick way to get a disk with a filesystem already on it. When Unix is up and running, use `mount /dev/fp012 /mnt` to mount the second drive. You may want to run `fsck` on it first, just to be safe. 96 | - You can also use the ICUS Enhanced Diagnostics disk. A bootable copy is 97 | available [here](https://www.skeeve.com/3b1/enhanced-diag/index.html). 98 | Uncompress it before using. 99 | - Install the operating system: 100 | * Follow the instructions in the [3B1 Software Installation Guide](http://bitsavers.org/pdf/att/3b1/999-801-025IS_ATT_UNIX_PC_System_Software_Installation_Guide_1987.pdf) to install UNIX. 101 | * Copy `01_Diagnostic_Disk_Ver_3.51.IMD` to `floppy.img` in the Freebee directory. 102 | * If you wish to increase the swap space size, do so with the diagnostics 103 | disk before installing the OS. See these [instructions](https://groups.google.com/g/comp.sys.att/c/8XLILI3K8-Y/m/VxVMJNdt9NQJ). 104 | * To change disks: 105 | * Press F11 to release the disk image. 106 | * Copy the next disk image to `floppy.img` in the Freebee directory. 107 | * Press F11 to load the disk image. 108 | * Instead of `08_Foundation_Set_Ver_3.51.IMD` use `08_Foundation_Set_Ver_3.51_no_phinit.IMD` from [here](https://www.skeeve.com/3b1/os-install/index.html). 109 | This will allow the emulated Unix PC to come all the way up to 110 | a login prompt after the installation. 111 | 112 | ## Importing files 113 | - Files can be imported using the 3b1 `msdos` command which allows reading a 360k MS-DOS floppy image. 114 | * Use dosbox to copy files to a DOS disk image named `floppy.img`. This image must be in the same directory as the Freebee executable (or path specified in the .freebee.toml config file). 115 | * If the floppy.img file wasn't present on boot or was updated, hit F11 to load/unload the floppy image. 116 | * Run `msdos` from the 3b1 command prompt, grab the mouse cursor with F10 if you haven't already, then COPY files to the hard drive. 117 | - Another option is to use the s4tools [here](https://github.com/dgesswein/s4-3b1-pc7300) which allow you to export the file system image out of the disk image and import the fs image back. In particular, there is an updated `sysv` Linux kernel module which allows mounting the fs image as a usable filesystem under Linux. 118 | 119 | ## Scaling the display 120 | 121 | You can scale the display by setting scale factors in the `.freebee.toml` file. 122 | Scale values must be greater than zero and less than or equal to 45. This 123 | facility is useful on large displays. 124 | 125 | # Keyboard commands 126 | 127 | * F10 -- Grab/Release mouse cursor 128 | * F11 -- Load/Unload floppy disk image (`floppy.img`) 129 | * Alt-F12 -- Exit 130 | 131 | # 3b1-specific key mappings 132 | 133 | * F1-F8 -- "soft keys" at bottom of screen 134 | * F9 -- SUSPD key (brings up list of windows) 135 | * Alt-Esc -- EXIT key 136 | * Alt-Backspace -- CANCL key 137 | * Page Up -- HELP key 138 | * Page Down -- PAGE key 139 | * Insert -- CMD key 140 | * Enter -- RETURN key 141 | * Alt-Enter -- ENTER key 142 | * Pause Break -- RESET BREAK key 143 | 144 | # Useful links 145 | 146 | * [AT&T 3B1 Information](http://unixpc.taronga.com) -- the "Taronga archive". 147 | * Includes the STORE, comp.sources.3b1, XINU and a very easy to read HTML version of the 3B1 FAQ. 148 | * Also includes (under "Kernel Related") tools to build an Enhanced Diagnostics disk which provides more options formatting hard drives. 149 | * [unixpc.org](http://www.unixpc.org/) 150 | * Bitsavers: [documentation and firmware (ROMs)](http://bitsavers.org/pdf/att/3b1/), [software](http://bitsavers.org/bits/ATT/unixPC/) 151 | 152 | # Other Notes 153 | 154 | * To make an MS-DOS disk under Linux (9 tracks per sector): 155 | 156 | dd if=/dev/zero of=dos.img bs=1k count=360
157 | /sbin/mkfs.fat dos.img
158 | sudo mount -o loop -t msdos dos.img /mnt
159 | ... copy files to /mnt ...
160 | sudo umount /mnt
161 | 162 | * To make a 10 track per sector disk image, just use `count=400` in the `dd` command and then format the disk under Unix with `iv` and `mkfs`. 163 | 164 | * See this part of the [FAQ](https://stason.org/TULARC/pc/3b1-faq/4-4-How-do-I-get-multiple-login-windows.html) on setting up multiple login windows. 165 | -------------------------------------------------------------------------------- /README.serial.md: -------------------------------------------------------------------------------- 1 | # Serial port usage (Linux) 2 | 3 | A pseudoterminal (pty) will be created on launch of FreeBee. The file `serial-pty` in the current directory will be symlinked to the pty (e.g. /dev/pts/?). This will be the proxy for /dev/tty000 (the built-in serial port) on the 3b1. 4 | 5 | ## Logging in to 3b1 via serial port 6 | * 3b1: Make sure getty is running on tty000 7 | * Boot FreeBee and run `setgetty 000 1` as root 8 | * Linux: Run terminal emulator pointed at our pty 9 | * Install package "minicom", "picocom", "putty", or "screen" (minicom recommended) 10 | * Then run: 11 | * Minicom: `minicom -D ./serial-pty` 12 | * To quit: Ctrl-A + Q 13 | * Picocom: `picocom --omap delbs --send-cmd "sx -vv" --receive-cmd "rx -vv" ./serial-pty` 14 | * To quit: Ctrl-A + Ctrl-Q 15 | * Putty: `putty -serial ./serial-pty` 16 | * To backspace: use Shift-Backspace (or configure to send ^H) 17 | * Screen: `screen ./serial-pty` 18 | * To quit: Ctrl-A + \\ 19 | 20 | ## Transferring files via Xmodem 21 | * Once a serial connection has been made, files can be transferred via Xmodem 22 | * If using Picocom, "rzsz" (or "lrzsz") package must be installed and "--send-cmd" and "--receive-cmd" parameters specified on cmd line (see above) 23 | * Sending a local file to 3b1 24 | * Initiate receive on 3b1 with: `umodem -rb ` 25 | * Then quickly select file to send: 26 | * Minicom: Ctrl-A + s, select xmodem 27 | * Picocom: Ctrl-A + Ctrl-S 28 | * Receiving a file from 3b1 29 | * Send file on 3b1 with: `umodem -sb ` 30 | * Then quickly receive file with: 31 | * Minicom: Ctrl-A + r, select xmodem 32 | * Picocom: Ctrl-A + Ctrl-R 33 | 34 | ## Connecting out from 3b1 to Linux machine via serial port 35 | * 3b1: Make sure getty is **NOT** running on tty000 36 | * Boot FreeBee and run `setgetty 000 0` as root 37 | * 3b1: Add the line "DIR tty000 0 9600" to /usr/lib/uucp/L-devices as root 38 | * e.g. as root: `echo "DIR tty000 0 9600" >> /usr/lib/uucp/L-devices` 39 | * Linux: Run `sudo agetty pts/? vt100` where pts/? is the pty linked to by ./serial-pty 40 | * e.g. if serial-pty -> /dev/pts/2, run `sudo agetty pts/2 vt100` 41 | * 3b1: Run `cu -l tty000` to connect to your Linux machine 42 | * Login to your Linux machine - when done, exiting your Linux shell should also end the agetty 43 | * If you'd like to login again, run the agetty command again 44 | * When done with cu, enter `~.` + Enter to exit cu 45 | 46 | ## Transferring files via Kermit 47 | * 3b1: Make sure getty is **NOT** running on tty000 48 | * Boot FreeBee and run `setgetty 000 0` as root 49 | * 3b1: 50 | * Download kermit binary if not already present on your system 51 | * Run `kermit -l /dev/tty000` 52 | * At `C-Kermit>` prompt: 53 | * Receive file with `receive` 54 | * Send file with `send ` 55 | * Linux: 56 | * Install package "gkermit" 57 | * Send file with `gkermit -s > ./serial-pty < ./serial-pty` 58 | * Receive file with `gkermit -r > ./serial-pty < ./serial-pty` 59 | -------------------------------------------------------------------------------- /dep/.keepme: -------------------------------------------------------------------------------- 1 | This file is a directory-keeper. Do not delete it. 2 | -------------------------------------------------------------------------------- /dep/musashi/.keepme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philpem/freebee/b727767d2ac48b86f984bf816dfbad9ece94776f/dep/musashi/.keepme -------------------------------------------------------------------------------- /dep/musashi/softfloat/.keepme: -------------------------------------------------------------------------------- 1 | This file is a directory-keeper. Do not delete it. 2 | -------------------------------------------------------------------------------- /doc/3B1 Memory Map.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philpem/freebee/b727767d2ac48b86f984bf816dfbad9ece94776f/doc/3B1 Memory Map.ods -------------------------------------------------------------------------------- /freebee.1: -------------------------------------------------------------------------------- 1 | .TH FREEBEE 1 "Feb 13 2021" "" "AT&T 3B1 Emulation" 2 | .SH NAME 3 | freebee \- emulate an AT&T 3B1 personal computer 4 | .SH SYNOPSIS 5 | .B freebee 6 | .SH DESCRIPTION 7 | .I freebee 8 | is an emulator for the AT&T 3B1 personal Unix computer. 9 | There were two models of this system: The Unix PC (also known 10 | as the PC 7300) and the 3B1. The two models differed primarily 11 | in the amount of main memory and disk that the various systems 12 | had; from a software standpoint they were identical. 13 | .PP 14 | .I freebee 15 | provides a system with an MC 68010 processor, 4 megabytes of RAM, 16 | a floppy drive, and up to two hard drives, each of which may be 17 | up to 175 megabytes in size. 18 | .PP 19 | The original software for the 3B1 is available. 20 | It includes the OS, software development tools, document preparation 21 | tools, and advanced editors (notably 22 | .IR vi (1)). 23 | Links to the software are 24 | provided on 25 | .IR freebee\^ 's 26 | home page. 27 | .PP 28 | Considerable third party software is available, some in source form, 29 | some in binary form, from archives that are also linked to from 30 | .IR freebee\^ 's 31 | home page. 32 | .PP 33 | The system more-or-less represents the state of the art in the 34 | Unix world as of about 1985. The Unix PC and 3B1 were the first 35 | .I personal 36 | Unix systems that were affordable by individuals, as opposed to higher-end 37 | workstations from companies like Sun, DEC, IBM, Tektronix, and Silicon Graphics. 38 | .PP 39 | The kernel is approximately System V Release 2 vintage, with a few 40 | System V Release 3 user-land components. In particular, the modern-day 41 | user will have to deal with both short filenames (14 characters maximum), 42 | and the lack of job control. 43 | .SH CONFIGURATION 44 | .I freebee 45 | can be configured by creating a file in TOML format containing 46 | appropriate values. 47 | .I freebee 48 | looks first for 49 | .I .freebee.toml 50 | in the current directory, and then 51 | .IR $HOME/.freebee.toml , 52 | reading the first one it can open and then stopping. 53 | See the file 54 | .I sample.freebee.toml 55 | in the source code for the options that are configurable. 56 | .SH FILES 57 | The following list of files are the defaults 58 | .I freebee 59 | uses if the filenames are not overridden by a configuration 60 | file (see the earlier section 61 | .BR CONFIGURATION ). 62 | .PP 63 | Files are assumed to live in the current directory in which 64 | .I freebee 65 | is run. 66 | .TP 67 | .I roms/* 68 | The ROM image files. Links to them are provided on the 69 | .I freebee 70 | home page. 71 | .TP 72 | .I floppy.img 73 | An optional image of a floppy disk. By default, 74 | .I freebee 75 | attaches this file when it starts up. Use F11 to unload and 76 | load this file while running (``change the floppy''). 77 | .TP 78 | .I hd.img 79 | An image for the first hard disk. This disk is mounted at / (the root). 80 | Pre-built images are available from the Internet, or you can 81 | create and install such a disk from scratch. 82 | .TP 83 | .I hd2.img 84 | An optional image for the second hard disk. 85 | This disk is not mounted when the system boots; you can use it 86 | for whatever you want. 87 | .TP 88 | .I serial-pty 89 | A symbolic link to the pseudo-terminal file 90 | .RI ( /dev/pts/? ) 91 | which can be used to connect to the 3B1's serial port for user login to 92 | the 3B1, dial-out from it, 93 | or for file transfer. See the file 94 | .I README.serial.md 95 | in the source code directory for more information and concise 96 | instructions on how to use the serial port. 97 | .SH SCALING THE DISPLAY 98 | You can scale the display by setting scale factors in the 99 | .I .freebee.toml 100 | file. 101 | Scale values must be greater than zero and less than or equal to 45. This 102 | facility is useful on large displays. 103 | .SH AUTHORS 104 | Phil Pemberton, with contributions from several others. 105 | .SH BUG REPORTS 106 | Please open an issue on the Github project, 107 | \f(CWhttps://github.com/philpem/freebee\fP. 108 | .SH BUGS 109 | The original 3B1 modem is not emulated. 110 | .PP 111 | To avoid Y2K bugs in the OS, the year in the running system is always 1987. 112 | -------------------------------------------------------------------------------- /obj/.keepme: -------------------------------------------------------------------------------- 1 | This file is a directory-keeper. Do not delete it. 2 | -------------------------------------------------------------------------------- /obj/musashi/.keepme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philpem/freebee/b727767d2ac48b86f984bf816dfbad9ece94776f/obj/musashi/.keepme -------------------------------------------------------------------------------- /obj/musashi/softfloat/.keepme: -------------------------------------------------------------------------------- 1 | This file is a directory-keeper. Do not delete it. 2 | -------------------------------------------------------------------------------- /sample.freebee.toml: -------------------------------------------------------------------------------- 1 | # Sample TOML configuration file for freebee 2 | # 3 | # freebee searches first for ./.freebee.toml and then 4 | # for $HOME/.freebee.toml. It reads the first one it 5 | # finds and then stops. 6 | 7 | [floppy] 8 | disk = "/path/to/floppy.img" # Floppy disk is optional 9 | 10 | [hard_disk] 11 | disk1 = "/path/to/hd.img" 12 | disk2 = "/path/to/hd2.img" # Second disk is optional 13 | # Default disk parameters. Overridden by disk label in 14 | # the hard disk image. 15 | heads = 8 16 | sectors_per_track = 17 17 | 18 | [display] 19 | x_scale = 1.0 # Scale in X dimension, 0 < n <= 45 20 | y_scale = 1.0 # Scale in Y dimension, 0 < n <= 45 21 | scale_quality = "linear" # "nearest" (fastest), "linear" (default), or "best" (anisotropic) 22 | red = 0x00 # foreground colors 23 | green = 0xFF 24 | blue = 0x00 25 | 26 | [roms] 27 | rom_14c = "/path/to/roms/14c.bin" # Odd locations 28 | rom_15c = "/path/to/roms/15c.bin" # Even locations 29 | 30 | [serial] 31 | symlink = "serial-pty" 32 | 33 | [vidpal] 34 | installed = true 35 | 36 | [memory] 37 | base_memory = 2048 # Units of 1K bytes, 2048 is the max. 38 | extended_memory = 2048 # Units of 1K bytes, 2048 is the max. 39 | -------------------------------------------------------------------------------- /src/diskimd.c: -------------------------------------------------------------------------------- 1 | #include "diskimg.h" 2 | 3 | //#define DISKIMD_DEBUG 4 | 5 | #ifndef DISKIMD_DEBUG 6 | #define NDEBUG 7 | #endif 8 | #include "utils.h" 9 | 10 | #define IMD_END_OF_COMMENT 0x1A 11 | #define IMD_HEAD_MASK 0x03 12 | #define IMD_SDR_DATA 0x01 13 | #define IMD_SDR_COMPRESSED 0x01 14 | 15 | typedef struct 16 | { 17 | uint8_t data_mode; // data mode (5 = 250kbps DD, 4 = 300kbps DD) 18 | uint8_t cyl; // cylinder 19 | uint8_t head; // head, flags (cylinder map, head map) 20 | uint8_t spt; // sectors/track 21 | uint8_t secsz_code; // sector size code (secsz = 128 << secsz_code) 22 | } IMD_TRACK_HEADER; 23 | 24 | static int init_imd(struct disk_image *ctx, FILE *fp, int secsz, int heads, int tracks) 25 | { 26 | uint8_t sdrType, track_sector_map[10], ch; 27 | IMD_TRACK_HEADER trackHeader; 28 | size_t filepos; 29 | 30 | // write out and advance past comments 31 | fseek(fp, 0, SEEK_SET); 32 | while (1) { 33 | ch = fgetc(fp); 34 | if (ch == IMD_END_OF_COMMENT || feof(fp)) 35 | break; 36 | else 37 | printf("%c", ch); 38 | } 39 | if (feof(fp)) return -1; 40 | 41 | // probe first track header to get spt 42 | filepos = ftell(fp); 43 | if (!fread(&trackHeader, sizeof(trackHeader), 1, fp)) 44 | { 45 | fprintf(stderr, "error reading IMD track header\n"); 46 | return -1; 47 | } 48 | fseek(fp, filepos, SEEK_SET); 49 | ctx->spt = trackHeader.spt; 50 | 51 | ctx->fp = fp; 52 | ctx->secsz = secsz; 53 | ctx->heads = heads; 54 | 55 | // allocate sectorMap 56 | if (ctx->sectorMap) free(ctx->sectorMap); 57 | ctx->sectorMap = malloc(tracks*heads*ctx->spt*sizeof(uint32_t)); 58 | if (!ctx->sectorMap) return -1; 59 | 60 | // tracks start, build sector offset map, check for unexpected SDRs 61 | for (int track_i=0; track_i < tracks*heads; track_i++) 62 | { 63 | if (!fread(&trackHeader, sizeof(trackHeader), 1, fp)) 64 | { 65 | fprintf(stderr, "error reading IMD track header\n"); 66 | return -1; 67 | } 68 | // data mode 4 and 5 supported, secsz = 128 << secsz_code, head map & cylinder map flags unsupported 69 | if (!(trackHeader.data_mode == 5 || trackHeader.data_mode == 4) || trackHeader.spt != ctx->spt || 70 | trackHeader.secsz_code != 2 || (trackHeader.head & ~IMD_HEAD_MASK)) 71 | { 72 | fprintf(stderr, "unexpected IMD track header data, track %i\n", track_i+1); 73 | return -1; 74 | } 75 | if (!fread(track_sector_map, ctx->spt, 1, fp)) 76 | { 77 | fprintf(stderr, "error reading IMD track sector map\n"); 78 | return -1; 79 | } 80 | 81 | for (int sect_i=0; sect_i < ctx->spt; sect_i++) 82 | { 83 | ctx->sectorMap[(track_i*ctx->spt) + track_sector_map[sect_i] - 1] = ftell(fp); 84 | sdrType = fgetc(fp); 85 | switch (sdrType) { 86 | case IMD_SDR_DATA: 87 | fseek(fp, secsz, SEEK_CUR); 88 | break; 89 | case (IMD_SDR_DATA + IMD_SDR_COMPRESSED): 90 | fgetc(fp); // fill byte 91 | break; 92 | default: 93 | fprintf(stderr, "unexpected IMD sector data record: %i\n", sdrType); 94 | return -1; 95 | } 96 | } 97 | } 98 | LOG("IMD file size: %li", ftell(fp)); 99 | return ctx->spt; 100 | } 101 | 102 | static void done_imd(struct disk_image *ctx) 103 | { 104 | if (ctx->sectorMap) free(ctx->sectorMap); 105 | ctx->sectorMap = NULL; 106 | 107 | ctx->fp = NULL; 108 | ctx->secsz = 0; 109 | ctx->heads = 0; 110 | ctx->spt = 0; 111 | } 112 | 113 | static size_t read_sector_imd(struct disk_image *ctx, int cyl, int head, int sect, uint8_t *data) 114 | { 115 | size_t bytes_read; 116 | uint8_t sdrType, fill; 117 | int lba; 118 | 119 | // Calculate the LBA address of the required sector 120 | // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1 121 | lba = (cyl * ctx->heads * ctx->spt) + (head * ctx->spt) + sect - 1; 122 | 123 | LOG("\tREAD(IMD), lba: %i, sectorMap offset: %i", lba, ctx->sectorMap[lba]); 124 | fseek(ctx->fp, ctx->sectorMap[lba], SEEK_SET); 125 | sdrType = fgetc(ctx->fp); 126 | switch (sdrType) { 127 | case IMD_SDR_DATA: 128 | bytes_read = fread(data, 1, ctx->secsz, ctx->fp); 129 | LOG("\tREAD(IMD) len=%lu, ssz=%d", bytes_read, ctx->secsz); 130 | break; 131 | case (IMD_SDR_DATA + IMD_SDR_COMPRESSED): 132 | fill = fgetc(ctx->fp); 133 | memset(data, fill, ctx->secsz); 134 | bytes_read = ctx->secsz; 135 | LOG("\tREAD(IMD, compressed) len=%lu, ssz=%d", bytes_read, ctx->secsz); 136 | break; 137 | default: 138 | fprintf(stderr, "unexpected sector data record: %i\n", sdrType); 139 | return -1; 140 | } 141 | return bytes_read; 142 | } 143 | 144 | static void write_sector_imd(struct disk_image *ctx, int cyl, int head, int sect, uint8_t *data) 145 | { 146 | uint8_t sdrType, fill; 147 | int lba; 148 | 149 | // Calculate the LBA address of the required sector 150 | // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1 151 | lba = (cyl * ctx->heads * ctx->spt) + (head * ctx->spt) + sect - 1; 152 | 153 | LOG("IMD write sector, lba: %i, sectorMap offset: %i", lba, ctx->sectorMap[lba]); 154 | 155 | // IMD writes only supported if sector was uncompressed, or write data is also compressable 156 | fseek(ctx->fp, ctx->sectorMap[lba], SEEK_SET); 157 | sdrType = fgetc(ctx->fp); 158 | switch (sdrType) { 159 | case IMD_SDR_DATA: 160 | fwrite(data, 1, ctx->secsz, ctx->fp); 161 | fflush(ctx->fp); 162 | LOG("WRITE(IMD), ssz=%i", ctx->secsz); 163 | break; 164 | case (IMD_SDR_DATA + IMD_SDR_COMPRESSED): 165 | fill = data[0]; 166 | // confirm write data is all the same 167 | for (int i=0; i < ctx->secsz; i++) 168 | { 169 | if (data[i] != fill) 170 | { 171 | fprintf(stderr, "**unsupported IMD sector write (can't expand compressed sector)**\n"); 172 | return; 173 | } 174 | } 175 | fputc(fill, ctx->fp); 176 | LOG("WRITE(IMD, compressed), ssz=%i", ctx->secsz); 177 | break; 178 | default: 179 | fprintf(stderr, "**unsupported IMD sector write**\n"); 180 | } 181 | } 182 | 183 | DISK_IMAGE imd_format = { 184 | .init = init_imd, 185 | .done = done_imd, 186 | .read_sector = read_sector_imd, 187 | .write_sector = write_sector_imd, 188 | .fp = NULL, 189 | .secsz = 0, 190 | .heads = 0, 191 | .spt = 0, 192 | .sectorMap = NULL 193 | }; 194 | -------------------------------------------------------------------------------- /src/diskimg.h: -------------------------------------------------------------------------------- 1 | #ifndef _DISKIMG_H 2 | #define _DISKIMG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct disk_image { 10 | int (*const init)(struct disk_image *ctx, FILE *fp, int secsz, int heads, int tracks); 11 | void (*const done)(struct disk_image *ctx); 12 | size_t (*const read_sector)(struct disk_image *ctx, int cyl, int head, int sect, uint8_t *data); 13 | void (*const write_sector)(struct disk_image *ctx, int cyl, int head, int sect, uint8_t *data); 14 | 15 | FILE *fp; 16 | int secsz, heads, spt; 17 | 18 | // IMD specific 19 | uint32_t *sectorMap; // sector offset map 20 | } DISK_IMAGE; 21 | 22 | typedef enum { 23 | DISK_IMAGE_RAW, 24 | DISK_IMAGE_IMD // ImageDisk 25 | } DISK_IMAGE_FORMAT; 26 | 27 | extern DISK_IMAGE raw_format; 28 | extern DISK_IMAGE imd_format; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/diskraw.c: -------------------------------------------------------------------------------- 1 | #include "diskimg.h" 2 | 3 | //#define DISKRAW_DEBUG 4 | 5 | #ifndef DISKRAW_DEBUG 6 | #define NDEBUG 7 | #endif 8 | #include "utils.h" 9 | 10 | static int init_raw(struct disk_image *ctx, FILE *fp, int secsz, int heads, int tracks) 11 | { 12 | size_t filesize; 13 | 14 | ctx->fp = fp; 15 | ctx->secsz = secsz; 16 | ctx->heads = heads; 17 | 18 | // Start by finding out how big the image file is 19 | fseek(fp, 0, SEEK_END); 20 | filesize = ftell(fp); 21 | fseek(fp, 0, SEEK_SET); 22 | 23 | // Calculate sectors per track 24 | ctx->spt = filesize / secsz / heads / tracks; 25 | 26 | return ctx->spt; 27 | } 28 | 29 | static void done_raw(struct disk_image *ctx) 30 | { 31 | ctx->fp = NULL; 32 | ctx->secsz = 0; 33 | ctx->heads = 0; 34 | ctx->spt = 0; 35 | } 36 | 37 | static size_t read_sector_raw(struct disk_image *ctx, int cyl, int head, int sect, uint8_t *data) 38 | { 39 | size_t bytes_read; 40 | int lba; 41 | 42 | // Calculate the LBA address of the required sector 43 | // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1 44 | lba = (cyl * ctx->heads * ctx->spt) + (head * ctx->spt) + sect - 1; 45 | LOG("\tREAD(raw) lba = %i (C,H,S = %i, %i, %i)", lba, cyl, head, sect); 46 | 47 | // convert LBA to byte address 48 | lba *= ctx->secsz; 49 | 50 | // Read the sector from the file 51 | fseek(ctx->fp, lba, SEEK_SET); 52 | 53 | // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr) 54 | bytes_read = fread(data, 1, ctx->secsz, ctx->fp); 55 | LOG("\tREAD(raw) len=%lu, ssz=%d", bytes_read, ctx->secsz); 56 | return bytes_read; 57 | } 58 | 59 | static void write_sector_raw(struct disk_image *ctx, int cyl, int head, int sect, uint8_t *data) 60 | { 61 | int lba; 62 | 63 | // Calculate the LBA address of the required sector 64 | // LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1 65 | lba = (cyl * ctx->heads * ctx->spt) + (head * ctx->spt) + sect - 1; 66 | LOG("\tWRITE(raw) lba = %i (C,H,S = %i, %i, %i)", lba, cyl, head, sect); 67 | 68 | // convert LBA to byte address 69 | lba *= ctx->secsz; 70 | 71 | fseek(ctx->fp, lba, SEEK_SET); 72 | fwrite(data, 1, ctx->secsz, ctx->fp); 73 | fflush(ctx->fp); 74 | } 75 | 76 | DISK_IMAGE raw_format = { 77 | .init = init_raw, 78 | .done = done_raw, 79 | .read_sector = read_sector_raw, 80 | .write_sector = write_sector_raw, 81 | .fp = NULL, 82 | .secsz = 0, 83 | .heads = 0, 84 | .spt = 0, 85 | .sectorMap = NULL 86 | }; 87 | -------------------------------------------------------------------------------- /src/fbconfig.c: -------------------------------------------------------------------------------- 1 | /* 2 | * fbconfig - freebee configuration routines. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "fbconfig.h" 10 | #include "toml.h" 11 | 12 | static bool inited = false; 13 | static bool have_toml = false; 14 | static toml_table_t *table_config = NULL; 15 | 16 | /* initialize --- initialize TOML files */ 17 | 18 | static void 19 | initialize(void) 20 | { 21 | FILE* fp; 22 | char errbuf[200]; 23 | char toml_file[BUFSIZ]; 24 | 25 | strcpy(toml_file, ".freebee.toml"); 26 | fp = fopen(toml_file, "r"); 27 | if (fp == NULL) { 28 | char *home = getenv("HOME"); 29 | 30 | if (home != NULL) { 31 | sprintf(toml_file, "%s/.freebee.toml", home); 32 | fp = fopen(toml_file, "r"); 33 | } 34 | } 35 | 36 | inited = true; 37 | if (fp == NULL) 38 | return; 39 | 40 | table_config = toml_parse_file(fp, errbuf, sizeof(errbuf)); 41 | fclose(fp); 42 | 43 | if (table_config == NULL) { 44 | fprintf(stderr, "freebee: %s: cannot parse: %s", 45 | toml_file, errbuf); 46 | return; 47 | } 48 | 49 | have_toml = true; 50 | } 51 | 52 | 53 | /* get_default_string --- get a string value from the configuration */ 54 | 55 | static const char * 56 | get_default_string(const char *heading, const char *item) 57 | { 58 | static struct default_strings { 59 | const char *heading; 60 | const char *item; 61 | const char *value; 62 | } defaults[] = { 63 | { "floppy", "disk", "floppy.img" }, 64 | { "hard_disk", "disk1", "hd.img" }, 65 | { "hard_disk", "disk2", "hd2.img" }, 66 | { "roms", "rom_14c", "roms/14c.bin" }, 67 | { "roms", "rom_15c", "roms/15c.bin" }, 68 | { "serial", "symlink", "serial-pty" }, 69 | { "display", "scale_quality", "linear" }, 70 | { NULL, NULL, NULL } 71 | }; 72 | 73 | int i; 74 | for (i = 0; defaults[i].heading != NULL; i++) { 75 | if (strcmp(defaults[i].heading, heading) == 0 && strcmp(defaults[i].item, item) == 0) { 76 | return defaults[i].value; 77 | } 78 | } 79 | 80 | return NULL; 81 | } 82 | 83 | /* get_default_double --- get a double value from the configuration */ 84 | 85 | static double 86 | get_default_double(const char *heading, const char *item) 87 | { 88 | static struct default_doubles { 89 | const char *heading; 90 | const char *item; 91 | double value; 92 | } defaults[] = { 93 | { "display", "x_scale", 1.0 }, 94 | { "display", "y_scale", 1.0 }, 95 | { NULL, NULL, 0.0 } 96 | }; 97 | 98 | int i; 99 | for (i = 0; defaults[i].heading != NULL; i++) { 100 | if (strcmp(defaults[i].heading, heading) == 0 && strcmp(defaults[i].item, item) == 0) { 101 | return defaults[i].value; 102 | } 103 | } 104 | 105 | return 0.0; 106 | } 107 | 108 | /* get_default_bool --- get a bool value from the configuration */ 109 | 110 | static bool 111 | get_default_bool(const char *heading, const char *item) 112 | { 113 | static struct default_doubles { 114 | const char *heading; 115 | const char *item; 116 | bool value; 117 | } defaults[] = { 118 | { "vidpal", "installed", true }, 119 | { NULL, NULL, false } 120 | }; 121 | 122 | int i; 123 | for (i = 0; defaults[i].heading != NULL; i++) { 124 | if (strcmp(defaults[i].heading, heading) == 0 && strcmp(defaults[i].item, item) == 0) { 125 | return defaults[i].value; 126 | } 127 | } 128 | 129 | return false; 130 | } 131 | 132 | /* get_default_int --- get an int value from the configuration */ 133 | 134 | static int 135 | get_default_int(const char *heading, const char *item) 136 | { 137 | static struct default_doubles { 138 | const char *heading; 139 | const char *item; 140 | int value; 141 | } defaults[] = { 142 | { "display", "red", 0x00 }, 143 | { "display", "green", 0xFF }, 144 | { "display", "blue", 0x00 }, 145 | { "hard_disk", "heads", 8 }, 146 | { "hard_disk", "sectors_per_track", 17 }, 147 | { "memory", "base_memory", 2048 }, 148 | { "memory", "extended_memory", 2048 }, 149 | { NULL, NULL, 0 } 150 | }; 151 | 152 | int i; 153 | for (i = 0; defaults[i].heading != NULL; i++) { 154 | if (strcmp(defaults[i].heading, heading) == 0 && strcmp(defaults[i].item, item) == 0) { 155 | return defaults[i].value; 156 | } 157 | } 158 | 159 | return 0; 160 | } 161 | 162 | 163 | /* fbc_get_string --- get a string value from the configuration */ 164 | 165 | const char * 166 | fbc_get_string(const char *heading, const char *item) 167 | { 168 | if (! inited) 169 | initialize(); 170 | 171 | if (have_toml) { 172 | toml_table_t* category = toml_table_in(table_config, heading); 173 | if (category != NULL) { 174 | toml_datum_t value = toml_string_in(category, item); 175 | if (value.ok) { 176 | const char *ret = strdup(value.u.s); 177 | free(value.u.s); 178 | return ret; 179 | } 180 | } 181 | } 182 | 183 | return get_default_string(heading, item); 184 | } 185 | 186 | /* fbc_get_double --- get a double value from the configuration */ 187 | 188 | double 189 | fbc_get_double(const char *heading, const char *item) 190 | { 191 | if (! inited) 192 | initialize(); 193 | 194 | if (have_toml) { 195 | toml_table_t* category = toml_table_in(table_config, heading); 196 | if (category != NULL) { 197 | toml_datum_t value = toml_double_in(category, item); 198 | if (value.ok) { 199 | return value.u.d; 200 | } 201 | } 202 | } 203 | 204 | return get_default_double(heading, item); 205 | } 206 | 207 | /* fbc_get_bool --- get a bool value from the configuration */ 208 | 209 | bool 210 | fbc_get_bool(const char *heading, const char *item) 211 | { 212 | if (! inited) 213 | initialize(); 214 | 215 | if (have_toml) { 216 | toml_table_t* category = toml_table_in(table_config, heading); 217 | if (category != NULL) { 218 | toml_datum_t value = toml_bool_in(category, item); 219 | if (value.ok) { 220 | return value.u.b; 221 | } 222 | } 223 | } 224 | 225 | 226 | return get_default_bool(heading, item); 227 | } 228 | 229 | /* fbc_get_int --- get a int value from the configuration */ 230 | 231 | int 232 | fbc_get_int(const char *heading, const char *item) 233 | { 234 | if (! inited) 235 | initialize(); 236 | 237 | if (have_toml) { 238 | toml_table_t* category = toml_table_in(table_config, heading); 239 | if (category != NULL) { 240 | toml_datum_t value = toml_int_in(category, item); 241 | if (value.ok) { 242 | return value.u.i; 243 | } 244 | } 245 | } 246 | 247 | 248 | return get_default_int(heading, item); 249 | } 250 | -------------------------------------------------------------------------------- /src/fbconfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * fbconfig - freebee configuration routines. 3 | */ 4 | 5 | #ifndef FBCONNFIG_H 6 | #define FBCONNFIG_H 1 7 | 8 | extern const char *fbc_get_string(const char *heading, const char *item); 9 | extern double fbc_get_double(const char *heading, const char *item); 10 | extern bool fbc_get_bool(const char *heading, const char *item); 11 | extern int fbc_get_int(const char *heading, const char *item); 12 | #endif /* FBCONNFIG_H */ 13 | -------------------------------------------------------------------------------- /src/i8274.c: -------------------------------------------------------------------------------- 1 | // 2 | // i8274 / uPD7210 (based on Z80 SIO) 3 | // 4 | // Channel A: rs232 5 | // Channel B: modem 6 | // 7 | // 3B1 uses "Status Affects Vector" with "Non-vectored" mode 8 | // 9 | // Logging into 3b1 via serial port: 10 | // 3b1: Make sure getty is running (/etc/inittab) 11 | // Linux: 12 | // Use 'minicom -D ./serial-pty' (Ctrl-A + Q to quit) 13 | // Or 'picocom --omap delbs ./serial-pty' to connect (Ctrl-A + Ctrl-Q to quit) 14 | // Or 'putty -serial ./serial-pty' (use Shift-Backspace to backspace, or configure to send ^H) 15 | // Or 'screen ./serial-pty' (Ctrl-A + '\' to quit) 16 | // 17 | // File transfer: 18 | // minicom: 19 | // send: Ctrl-A + s, receive: Ctrl-A + r, select xmodem 20 | // picocom: 21 | // Use 'picocom --omap delbs --send-cmd "sx -vv" --receive-cmd "rx -vv" ./serial-pty' 22 | // Linux: rzsz (or lrzsz) package must be installed 23 | // send: Ctrl-A + Ctrl-S, receive: Ctrl-A + Ctrl-R 24 | // 3b1: 25 | // send: 'umodem -sb ' 26 | // receive: 'umodem -rb ' 27 | // 28 | // Logging into linux machine via serial port: 29 | // 3b1: Make sure getty is NOT running on 3b1 (/etc/inittab) 30 | // 3b1: Add 'DIR tty000 0 9600' to /usr/lib/uucp/L-devices 31 | // Linux: Run 'agetty pts/? 9600 vt100' where pts/? is the PTY linked to by ./serial-pty 32 | // 3b1: Use 'cu -l tty000' to connect out 33 | // 34 | 35 | #include "i8274.h" 36 | #include 37 | #include 38 | 39 | //#define I8274_DEBUG 40 | #ifndef I8274_DEBUG 41 | #define NDEBUG 42 | #endif 43 | #include "utils.h" 44 | #include "fbconfig.h" 45 | 46 | #ifdef __linux__ 47 | // needed for PTY functions, unlink, symlink 48 | #define __USE_XOPEN_EXTENDED 49 | 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #endif 56 | 57 | #define WR1_EXT_INT_ENABLE 0x01 // Ext/Status Int Enable 58 | #define WR1_TX_INT_ENABLE 0x02 // TxInt Enable 59 | #define WR1_STATUS_AFFECTS_VECTOR 0x04 // Status Affects Vector 60 | 61 | #define WR1_RX_INT_ENABLE_MASK 0x18 // RxInt Enable Mask 62 | #define WR1_RX_INT_DISABLE 0x00 // RxInt Disable 63 | #define WR1_RX_INT_FIRST_CHAR 0x08 // RxInt on First Character 64 | #define WR1_RX_INT_ALL_PARITY 0x10 // RxInt on All Characters (Parity error affects vector) 65 | #define WR1_RX_INT_ALL 0x18 // RxInt on All Characters (Parity error does not affect vector) 66 | 67 | #define WR2_VECTORED_INT_MODE 0x20 // Vectored Interrupt Mode 68 | 69 | #define RR0_RX_CHAR_AVAILABLE 0x01 // Rx Character Available 70 | #define RR0_INTERRUPT_PENDING 0x02 // Int Pending (Chan A only, always 0 in Chan B) - aka "Interrupt In-Service" -- turn on as soon as interrupt requested 71 | #define RR0_TX_BUFFER_EMPTY 0x04 // Tx Buffer Empty 72 | #define RR0_DCD 0x08 // Ext/Status Int - DCD* (Data Carrier Detect) 73 | #define RR0_SYNC_HUNT 0x10 // Ext/Status Int - SYNDET* status in Async mode, always pulled high on 3b1 74 | #define RR0_CTS 0x20 // Ext/Status Int - CTS* (Clear to Send) 75 | #define RR0_TX_UNDERRUN 0x40 // Ext/Status Int - Tx Underrun/End of Message 76 | #define RR0_BREAK 0x80 // Ext/Status Int - Break (Async mode: set when Break sequence (null char plus framing error) detected in data stream) 77 | 78 | #define RR1_ALL_SENT 0x01 // All Sent 79 | #define RR1_PARITY_ERROR 0x10 // Parity Error 80 | #define RR1_RX_OVERRUN_ERROR 0x20 // Overrun Error 81 | #define RR1_CRC_FRAMING_ERROR 0x40 // CRC/Framing Error 82 | 83 | // 3b1 irq priority order 84 | const char* IRQ_PRIORITY_STR[] = { "RxA", "TxA", "RxB", "TxB", "ExtA", "ExtB" }; 85 | enum i8274_IRQ_PRIORITY_ORDER { 86 | IRQ_RXA, IRQ_TXA, 87 | IRQ_RXB, IRQ_TXB, 88 | IRQ_EXTA, IRQ_EXTB, 89 | IRQ_TOTAL 90 | }; 91 | 92 | static const char* serial_pty_filename; 93 | 94 | static bool fifo_empty(struct fifo *f) { return f->count == 0; } 95 | static bool fifo_full(struct fifo *f) { return f->count == FIFOSIZE; } 96 | static void fifo_reset(struct fifo *f) { f->count = f->head = f->tail = 0; } 97 | static int fifo_remaining(struct fifo *f) { return (FIFOSIZE - f->count); } 98 | static void fifo_put(struct fifo *f, uint8_t data) 99 | { 100 | if (fifo_full(f)) return; 101 | f->buf[f->head++] = data; 102 | f->count++; 103 | if (f->head == FIFOSIZE) f->head = 0; 104 | } 105 | static uint8_t fifo_get(struct fifo *f) 106 | { 107 | uint8_t data; 108 | 109 | if (fifo_empty(f)) return 0; 110 | data = f->buf[f->tail++]; 111 | f->count--; 112 | if (f->tail == FIFOSIZE) f->tail = 0; 113 | return data; 114 | } 115 | 116 | // Set "Rx char available" flag and RxInt if data in fifo 117 | static void check_rx_available(I8274_CTX *ctx, struct i8274_channel *chan) 118 | { 119 | if (fifo_empty(&chan->rx_fifo)) { 120 | chan->rr[0] &= ~RR0_RX_CHAR_AVAILABLE; 121 | // need to turn off RxInt to make sure 3b1 doesn't try to read again once fifo is empty 122 | ctx->irq_request[(chan->id==CHAN_A) ? IRQ_RXA : IRQ_RXB] &= ~IRQ_REQUESTED; 123 | } else { 124 | chan->rr[0] |= RR0_RX_CHAR_AVAILABLE; 125 | 126 | if (chan->wr[1] & WR1_RX_INT_ENABLE_MASK) { // RxInt enabled 127 | ctx->irq_request[(chan->id==CHAN_A) ? IRQ_RXA : IRQ_RXB] |= IRQ_REQUESTED; 128 | LOG("chan%c: **Rx IRQ (Char available) put in daisy chain", (chan->id==CHAN_A)?'A':'B'); 129 | } 130 | } 131 | } 132 | 133 | // assumes 8086/88 Mode (V2V1V0) used by 3b1 134 | static void set_vect_tx_buffer_empty(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id) 135 | { 136 | uint8_t rr2 = ctx->chanB.wr[2]; 137 | 138 | rr2 &= ~0x07; 139 | if (chan_id == CHAN_A) 140 | rr2 |= 0x04; // TxA 141 | else 142 | rr2 |= 0x00; // TxB 143 | 144 | ctx->chanB.rr[2] = rr2; 145 | } 146 | 147 | // assumes 8086/88 Mode (V2V1V0) used by 3b1 148 | static void set_vect_rx_char_received(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id) 149 | { 150 | uint8_t rr2 = ctx->chanB.wr[2]; 151 | 152 | rr2 &= ~0x07; 153 | if (chan_id == CHAN_A) 154 | rr2 |= 0x06; // RxA 155 | else 156 | rr2 |= 0x02; // RxB 157 | ctx->chanB.rr[2] = rr2; 158 | } 159 | 160 | // assumes 8086/88 Mode (V2V1V0) used by 3b1 161 | static void set_vect_no_int_pending(I8274_CTX *ctx) 162 | { 163 | ctx->chanB.rr[2] = ctx->chanB.wr[2]; 164 | ctx->chanB.rr[2] |= 0x07; 165 | } 166 | 167 | static void clear_irq_requests(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id) 168 | { 169 | if (chan_id == CHAN_A) 170 | ctx->irq_request[IRQ_RXA] = ctx->irq_request[IRQ_TXA] = ctx->irq_request[IRQ_EXTA] = IRQ_NONE; 171 | else 172 | ctx->irq_request[IRQ_RXB] = ctx->irq_request[IRQ_TXB] = ctx->irq_request[IRQ_EXTB] = IRQ_NONE; 173 | } 174 | 175 | static void interrupt_ack(I8274_CTX *ctx) 176 | { 177 | int i; 178 | 179 | // Find highest priority requested irq 180 | for (i=0; i < IRQ_TOTAL; i++) { 181 | if (ctx->irq_request[i] & IRQ_REQUESTED) break; 182 | } 183 | // Acknowldge interrupt about to be serviced 184 | if (i < IRQ_TOTAL) { 185 | LOG("acknowledging irq: %s", IRQ_PRIORITY_STR[i]); 186 | ctx->irq_request[i] |= IRQ_ACCEPTED; 187 | } else { 188 | fprintf(stderr, "ERROR (interrupt_ack): no irq to acknowledge\n"); 189 | } 190 | } 191 | 192 | static void end_of_interrupt(I8274_CTX *ctx) 193 | { 194 | int i; 195 | 196 | // Find highest priority accepted irq 197 | for (i=0; i < IRQ_TOTAL; i++) { 198 | if (ctx->irq_request[i] & IRQ_ACCEPTED) break; 199 | } 200 | // Disable highest priority irq just serviced 201 | if (i < IRQ_TOTAL) { 202 | LOG("disabling serviced irq: %s", IRQ_PRIORITY_STR[i]); 203 | ctx->irq_request[i] &= ~IRQ_ACCEPTED; 204 | } else { 205 | fprintf(stderr, "ERROR (EOI): can't find interrupt to disable\n"); 206 | } 207 | // should we clear INT Pending bit? probably can rely on i8274_get_irq() for setting that 208 | } 209 | 210 | static void pty_in(I8274_CTX *ctx) 211 | { 212 | #ifdef __linux__ 213 | uint8_t inbuf[FIFOSIZE], *byteptr; 214 | int bytes_read; 215 | 216 | // fill up FIFO with data from PTY 217 | while (!fifo_full(&ctx->chanA.rx_fifo) && 218 | (bytes_read = read(ctx->ptyfd, inbuf, fifo_remaining(&ctx->chanA.rx_fifo))) > 0) { 219 | byteptr = inbuf; 220 | while(bytes_read--) 221 | fifo_put(&ctx->chanA.rx_fifo, *(byteptr++)); 222 | } 223 | 224 | // set "Rx char available" flag and RxInt if needed 225 | check_rx_available(ctx, &ctx->chanA); 226 | #endif 227 | } 228 | 229 | // any new data incoming from serial PTY? 230 | // called from the 60Hz update 231 | void i8274_scan_incoming(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id) 232 | { 233 | if (chan_id == CHAN_A) pty_in(ctx); 234 | } 235 | 236 | bool i8274_get_irq(I8274_CTX *ctx) 237 | { 238 | int i; 239 | 240 | // Look for any interrupts under service 241 | for (i=0; i < IRQ_TOTAL; i++) { 242 | if (ctx->irq_request[i] & IRQ_ACCEPTED) { 243 | // IRQ under service, don't change anything 244 | LOG("IRQ under service: %s", IRQ_PRIORITY_STR[i]); 245 | return true; 246 | } 247 | } 248 | 249 | // If nothing under service, find highest priority requested irq 250 | for (i=0; i < IRQ_TOTAL; i++) { 251 | if (ctx->irq_request[i] & IRQ_REQUESTED) break; 252 | } 253 | // Assumes WR1_STATUS_AFFECTS_VECTOR is set 254 | // Set the interrupt vector, will be read in rr[2] to ack and start ISR 255 | switch (i) { 256 | case IRQ_RXA: 257 | set_vect_rx_char_received(ctx, CHAN_A); 258 | ctx->chanA.rr[0] |= RR0_INTERRUPT_PENDING; 259 | LOG("**requesting m68k interrupt: RxA, vect: %x", ctx->chanB.rr[2]); 260 | return true; 261 | break; 262 | case IRQ_TXA: 263 | set_vect_tx_buffer_empty(ctx, CHAN_A); 264 | ctx->chanA.rr[0] |= RR0_INTERRUPT_PENDING; 265 | LOG("**requesting m68k interrupt: TxA, vect: %x", ctx->chanB.rr[2]); 266 | return true; 267 | break; 268 | case IRQ_RXB: 269 | set_vect_rx_char_received(ctx, CHAN_B); 270 | ctx->chanA.rr[0] |= RR0_INTERRUPT_PENDING; 271 | LOG("**requesting m68k interrupt: RxB, vect: %x", ctx->chanB.rr[2]); 272 | return true; 273 | break; 274 | case IRQ_TXB: 275 | set_vect_tx_buffer_empty(ctx, CHAN_B); 276 | ctx->chanA.rr[0] |= RR0_INTERRUPT_PENDING; 277 | LOG("**requesting m68k interrupt: TxB, vect: %x", ctx->chanB.rr[2]); 278 | return true; 279 | break; 280 | case IRQ_EXTA: 281 | case IRQ_EXTB: 282 | LOGS("ExtA, ExtB -- unsupported IRQ"); 283 | // not implemented - fall through 284 | case IRQ_TOTAL: 285 | default: 286 | set_vect_no_int_pending(ctx); 287 | ctx->chanA.rr[0] &= ~RR0_INTERRUPT_PENDING; 288 | return false; 289 | break; 290 | } 291 | } 292 | 293 | // Resets latched status bits of RR0, INT prioritization logic, and all control regs (WR0-WR7) 294 | static void channel_reset(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id) 295 | { 296 | struct i8274_channel *chan = (chan_id==CHAN_A) ? &ctx->chanA : &ctx->chanB; 297 | 298 | fifo_reset(&chan->rx_fifo); 299 | 300 | // clear write registers 301 | for (int i=0; i < 8; i++) { 302 | chan->wr[i] = 0; 303 | } 304 | 305 | // set no interrupts pending 306 | clear_irq_requests(ctx, chan_id); 307 | 308 | chan->rr[0] &= ~RR0_RX_CHAR_AVAILABLE; 309 | chan->rr[0] |= RR0_TX_BUFFER_EMPTY; 310 | chan->rr[0] |= RR0_TX_UNDERRUN; 311 | 312 | chan->rr[0] &= ~RR0_SYNC_HUNT; // SYNDET is pulled high on 3b1 for Chan A and Chan B, so always reads OFF 313 | 314 | // Chan A: always set DCD and CTS to indicate DCD and CTS are coming from terminal/PTY 315 | if (chan_id == CHAN_A) 316 | { 317 | chan->rr[0] |= RR0_DCD; 318 | chan->rr[0] |= RR0_CTS; 319 | } 320 | // Chan B: DCD should reflect RI input, CTS should reflect DSR input, disable them for now (as they could auto-enable Rx/Tx if WR3:D5 set) 321 | if (chan_id == CHAN_B) 322 | { 323 | chan->rr[0] &= ~RR0_DCD; 324 | chan->rr[0] &= ~RR0_CTS; 325 | } 326 | 327 | chan->rr[1] &= ~(RR1_PARITY_ERROR | RR1_CRC_FRAMING_ERROR | RR1_RX_OVERRUN_ERROR); 328 | chan->rr[1] |= RR1_ALL_SENT; 329 | } 330 | 331 | #ifdef I8274_DEBUG 332 | static void log_read_register(struct i8274_channel *chan, uint8_t read_reg, uint8_t value) 333 | { 334 | printf("chan%c: <<<< read %02X from RR%i:", 'A'+chan->id, value, read_reg); 335 | switch (read_reg) { 336 | case 0: //RR0 337 | printf(" [%sRx Char Available]", (value & RR0_RX_CHAR_AVAILABLE) ? "" : "No "); 338 | printf(" [%sInt Pending]", (value & RR0_INTERRUPT_PENDING) ? "" : "No "); 339 | if (value & RR0_TX_BUFFER_EMPTY) printf(" [Tx Buffer Empty]"); 340 | printf(" [DCD: %s]", (value & RR0_DCD) ? "ON" : "OFF"); 341 | printf(" [SYNDET: %s]", (value & RR0_SYNC_HUNT) ? "ON" : "OFF"); 342 | printf(" [CTS: %s]", (value & RR0_CTS) ? "ON" : "OFF"); 343 | if (value & RR0_TX_UNDERRUN) printf(" [Tx Underrun]"); 344 | if (value & RR0_BREAK) printf(" [Break]"); 345 | break; 346 | 347 | case 1: //RR1 348 | if (value & RR1_ALL_SENT) printf(" [All sent]"); 349 | printf(" [Residue Code: %02X]", (value >> 1) & 7); 350 | if (value & RR1_PARITY_ERROR) printf(" [Parity Error]"); 351 | if (value & RR1_RX_OVERRUN_ERROR) printf(" [Rx Overrun Error]"); 352 | if (value & RR1_CRC_FRAMING_ERROR) printf(" [CRC/Framing Error]"); 353 | if ((value & (RR1_PARITY_ERROR | RR1_CRC_FRAMING_ERROR | RR1_RX_OVERRUN_ERROR)) == 0) printf(" [No Errors]"); 354 | if (value & 0x80) printf(" [End of Frame (SDLC)]"); 355 | break; 356 | 357 | case 2: //RR2 - Chan B Int Vector 358 | // If the "status affects vector" mode is selected (WR1:D2), 359 | // it contains the modified vector for the highest priority interrupt pending. 360 | // If no interrupts are pending, the variable bits in the vector are set to ones. 361 | if (chan->id == CHAN_B) printf(" [INT ACK][Interrupt Vector %02X]", value); 362 | break; 363 | } 364 | printf("\n"); 365 | } 366 | 367 | static void log_write_register(struct i8274_channel *chan, uint8_t write_reg, uint8_t value) 368 | { 369 | printf("chan%c: write %02X to WR%i:", 'A'+chan->id, value, write_reg); 370 | switch (write_reg) { 371 | case 1: //WR1 372 | // Changes to DCD, CTS, SYNDET generate interrupt 373 | printf(" [External/Status Interrupt: %s]", (value & WR1_EXT_INT_ENABLE) ? "Enabled" : "Disabled"); 374 | // Interrupt when Tx buffer empty 375 | printf(" [TxInt: %s]", (value & WR1_TX_INT_ENABLE) ? "Enabled" : "Disabled"); 376 | // Bit 2 inactive in channel A 377 | if (chan->id == CHAN_B && (value & WR1_STATUS_AFFECTS_VECTOR)) printf(" [Status Affects Vector]"); 378 | // Character Rx handling 379 | switch ((value >> 3) & 3) { 380 | case 0: printf(" [RxInt: Disabled]"); break; 381 | case 1: printf(" [RxInt On First Char Only]"); break; 382 | case 2: printf(" [RxInt On All Received Chars (with parity error)]"); break; 383 | case 3: printf(" [RxInt On All Received Chars]"); break; 384 | } 385 | if (value & 0x20) printf(" [Wait on Rx]"); 386 | if (value & 0x40) printf(" [Tx Byte Count Enable]"); 387 | if (value & 0x80) printf(" [Wait on Rx/Tx Enable]"); 388 | break; 389 | 390 | case 2: //WR2 391 | if (chan->id == CHAN_B) { // channel B 392 | printf(" [Interrupt Vector: %02X]", value); 393 | } else { // channel A 394 | if (value & 1) printf(" [Chan A: DMA, Chan B: Interrupt]"); 395 | if (value & 2) printf(" [Chan A/Chan B: DMA]"); 396 | if ((value & 3) == 0) printf(" [Chan A/Chan B: Interrupt]"); 397 | printf(" [Relative Priority: %s]", (value & 0x04) ? "RxA, RxB, TxA, TxB" : "RxA, TxA, RxB, TxB"); 398 | printf(" [%s Interrupt]", (value & WR2_VECTORED_INT_MODE) ? "Vectored" : "Non-vectored"); 399 | if ((value & 0x18) == 0x10) 400 | printf(" [8086/88 Mode (V2V1V0)]"); 401 | else 402 | printf(" [8085 Mode (V4V3V2)]"); 403 | printf(" [Chan B Pin 10 = %s]", (value & 0x80) ? "SYNDET" : "RTS"); // pin 10 (Chan B RTS*) is always high 404 | } 405 | break; 406 | 407 | case 3: //WR3 408 | printf(" [Receiver: %s]", (value & 1) ? "*Enable*" : "Disable"); 409 | if (value & 2) printf(" [Sync Char Load Inhibit]"); 410 | if (value & 4) printf(" [Address Search Mode]"); 411 | if (value & 8) printf(" [Rx CRC Enable]"); 412 | if (value & 0x10) printf(" [Enter Hunt Mode]"); 413 | if ((value & 0x1e) == 0) printf(" [Async Mode]"); // bits D1-D4 all zero in async mode 414 | // auto enable Tx when CTS, auto enable Rx when DCD [CTS/DCD being set for chan A in channel_reset(), may not want that] 415 | if (value & 0x20) printf(" [Auto Enable (DCD->Rx, CTS->Tx)]"); 416 | printf(" [Rx Bits/Char: %c]", "5768"[value >> 6]); 417 | break; 418 | 419 | case 4: //WR4 420 | printf(" [Parity: %s]", (value & 1) ? "Enabled" : "Disabled"); 421 | if (value & 1) printf(" [Parity: %s]", (value & 2) ? "Even" : "Odd"); 422 | switch ((value >> 2) & 3) { 423 | case 0: printf(" [Sync Mode]"); break; 424 | case 1: printf(" [Async Mode, 1 Stop Bit]"); break; 425 | case 2: printf(" [Async Mode, 1.5 Stop Bits]"); break; 426 | case 3: printf(" [Async Mode, 2 Stop Bits]"); break; 427 | } 428 | switch ((value >> 4) & 3) { 429 | case 0: printf(" [8-Bit Sync Char]"); break; 430 | case 1: printf(" [16-Bit Sync Char]"); break; 431 | case 2: printf(" [SDLC/HDLC]"); break; 432 | case 3: printf(" [Ext Sync (SYNC pin)]"); break; 433 | } 434 | // clock rate of 19.2k 435 | switch ((value >> 6) & 3) { // tx/rx clock rate 436 | case 0: printf(" [Data Rate = Clock Rate]"); break; 437 | case 1: printf(" [Data Rate = 1/16 Clock Rate = 1200 baud]"); break; 438 | case 2: printf(" [Data Rate = 1/32 Clock Rate]"); break; 439 | case 3: printf(" [Data Rate = 1/64 Clock Rate = 300 baud]"); break; 440 | } 441 | break; 442 | 443 | case 5: //WR5 444 | if (value & 1) printf(" [Tx CRC Enable]"); 445 | printf(" [RTS pin: %s]", (value & 2) ? "ON" : "OFF"); 446 | if (value & 4) printf(" [CRC-16]"); 447 | if ((value & 5) == 0) printf(" [Async Mode]"); // 0 in bit 0 and 2 likely async mode 448 | printf(" [Transmitter: %s]", (value & 8) ? "*Enable*" : "Disable"); 449 | if (value & 0x10) printf(" [Send Break]"); 450 | printf(" [Tx Bits/Char: %c]", "5768"[(value >> 5) & 3]); 451 | if (chan->id == CHAN_A) 452 | printf(" [DTR pin: %s]", (value & 0x80) ? "ON" : "OFF"); // connects to DCD 453 | else // Chan B: DTR used to select clock for Chan A 454 | printf(" [rs232 clock: %s]", (value & 0x80) ? "int baud gen TMOUT" : "ext rs232 clock"); 455 | break; 456 | 457 | case 6: //WR6 458 | printf(" [Sync Byte 1: %02X]", value); 459 | break; 460 | 461 | case 7: //WR7 462 | printf(" [Sync Byte 2: %02X]", value); 463 | break; 464 | } 465 | printf("\n"); 466 | } 467 | #endif 468 | 469 | // Rx: 3b1 receiving char from serial port (PTY) 470 | uint8_t i8274_data_in(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id) 471 | { 472 | uint8_t data; 473 | struct i8274_channel *chan = (chan_id==CHAN_A) ? &ctx->chanA : &ctx->chanB; 474 | 475 | if (fifo_empty(&chan->rx_fifo)) { 476 | data = 0; 477 | fprintf(stderr, "chan%c: ERROR - Rx fifo empty!\n", 'A'+chan_id); 478 | } else { 479 | data = fifo_get(&chan->rx_fifo); 480 | LOG("chan%c: data in <<< 0x%02X ('%c')", 'A'+chan_id, data, data); 481 | } 482 | 483 | // ISR is started with RxInt but will continue to read more data depending on the RR0_RX_CHAR_AVAILABLE flag 484 | // (and will also read RR1 to make sure there are no errors) 485 | // i.e. ISR will potentially read more than just one byte (unlike Tx which seems to want one TxInt per byte) 486 | // check_rx_available() will set RR0_RX_CHAR_AVAILABLE accordingly and continue to request RxInt or turn it off 487 | check_rx_available(ctx, chan); 488 | return data; 489 | } 490 | 491 | static void pty_out(I8274_CTX *ctx, uint8_t byte_out) 492 | { 493 | #ifdef __linux__ 494 | (void) write(ctx->ptyfd, &byte_out, 1); 495 | #endif 496 | } 497 | 498 | // Tx: 3b1 sending char out serial port (PTY) 499 | void i8274_data_out(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id, uint8_t data) 500 | { 501 | struct i8274_channel *chan = (chan_id==CHAN_A) ? &ctx->chanA : &ctx->chanB; 502 | LOG("chan%c: data out >>> 0x%02X ('%c')", 'A'+chan_id, data, data); 503 | 504 | // we immediately "process" the byte (send to PTY) so we can continue to say we are "buffer empty" 505 | if (chan_id == CHAN_A) pty_out(ctx, data); 506 | chan->rr[0] |= RR0_TX_BUFFER_EMPTY; 507 | 508 | // TODO: maybe do this TxInt Request in the 60Hz update so we aren't always immediately spamming TxInt back to the 3b1? 509 | // If enabled, need to request TxInt when Tx buffer is empty 510 | if ((chan->wr[1] & WR1_TX_INT_ENABLE) && (chan->rr[0] & RR0_TX_BUFFER_EMPTY)) { 511 | ctx->irq_request[(chan_id==CHAN_A) ? IRQ_TXA : IRQ_TXB] |= IRQ_REQUESTED; 512 | LOG("chan%c: **Tx IRQ (Tx buffer empty) put in daisy chain", 'A'+chan_id); 513 | } 514 | } 515 | 516 | // read from RR0-RR2 517 | // RR1 read is used to check for any Rx errors 518 | // RR2, Chan B read is used to get interrupt vector bits and call ISR 519 | uint8_t i8274_status_read(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id) 520 | { 521 | uint8_t regptr; 522 | struct i8274_channel *chan = (chan_id==CHAN_A) ? &ctx->chanA : &ctx->chanB; 523 | 524 | LOG("chan%c: ctrl in", 'A'+chan_id); 525 | regptr = chan->wr[0] & 0x07; 526 | chan->wr[0] &= ~0x07; 527 | #ifdef I8274_DEBUG 528 | log_read_register(chan, regptr, chan->rr[regptr]); 529 | #endif 530 | // Interrupt Acknowledged with Chan B RR2 read (getting the interrupt vector bits) 531 | if (chan_id == CHAN_B && regptr == 2) { 532 | interrupt_ack(ctx); 533 | 534 | // MAME: "in non-vectored mode this serves the same function as the end of the second acknowledge cycle" 535 | // end_of_interrupt() can be called here, but since 3b1 writes WR0 EOI at the end of ISR we'll just call end_of_interrupt() there 536 | #if 0 537 | if ((ctx->chanA.wr[2] & WR2_VECTORED_INT_MODE) == 0) { //non-vectored mode 538 | end_of_interrupt(ctx); 539 | } 540 | #endif 541 | } 542 | 543 | return chan->rr[regptr]; 544 | } 545 | 546 | // write to WR0-WR7 547 | void i8274_control_write(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id, uint8_t data) 548 | { 549 | uint8_t regptr; 550 | struct i8274_channel *chan = (chan_id==CHAN_A) ? &ctx->chanA : &ctx->chanB; 551 | 552 | LOG("chan%c: ctrl out %02X", 'A'+chan_id, data); 553 | regptr = chan->wr[0] & 0x07; 554 | chan->wr[0] &= ~0x07; 555 | chan->wr[regptr] = data; 556 | 557 | if (regptr == 0) { 558 | int cmd = (data >> 3) & 0x07; 559 | switch (cmd) { 560 | // WR0 Null 561 | case 0: 562 | break; 563 | 564 | // WR0 Send Abort 565 | case 1: 566 | LOG("chan%c: WR0 cmd: SDLC send abort", 'A'+chan_id); 567 | break; 568 | 569 | // WR0 Reset Ext/Status Interrupts 570 | // resets the latched status bits of RR0 and re-enables them, allowing interrupts to occur again 571 | case 2: 572 | LOG("chan%c: WR0 cmd: Reset ext/status interrupts", 'A'+chan_id); 573 | // supposed to relatch DCD and CTS here 574 | ctx->irq_request[(chan_id==CHAN_A) ? IRQ_EXTA : IRQ_EXTB] &= ~IRQ_REQUESTED; 575 | break; 576 | 577 | // WR0 Channel Reset 578 | case 3: 579 | LOG("chan%c: WR0 cmd: Channel reset", 'A'+chan_id); 580 | channel_reset(ctx, chan_id); 581 | break; 582 | 583 | // WR0 Enable INT on Next Rx Character 584 | // if INT on First Rx char mode selected, reactivate that mode after each complete msg received to prepare for next msg 585 | case 4: 586 | LOG("chan%c: WR0 cmd: Enable INT on next Rx char", 'A'+chan_id); 587 | break; 588 | 589 | // WR0 Reset TxINT Pending 590 | // 3b1 sends this cmd when last character sent 591 | // when no more chars to be sent, this cmd prevents further TxInt requests until next char completely sent 592 | case 5: 593 | LOG("chan%c: WR0 cmd: Reset TxINT pending", 'A'+chan_id); 594 | ctx->irq_request[(chan_id==CHAN_A) ? IRQ_TXA : IRQ_TXB] &= ~IRQ_REQUESTED; 595 | break; 596 | 597 | // WR0 Error Reset 598 | case 6: 599 | LOG("chan%c: WR0 cmd: Error reset", 'A'+chan_id); 600 | chan->rr[1] &= ~(RR1_PARITY_ERROR | RR1_CRC_FRAMING_ERROR | RR1_RX_OVERRUN_ERROR); 601 | break; 602 | 603 | // WR0 End of Interrupt (received on Chan A only, but applies to both channels) 604 | // 8274: "resets the interrupt-in-service latch of the highest-priority internal device under service" 605 | // 7201: "once an IRQ has been issued by 7201, all lower priority interrupts in the daisy chain are 606 | // held off to permit the current interrupt to be serviced while allowing higher priority interrupts 607 | // to occur. At some point in ISR (generally at the end), EOI cmd must be issued to Chan A to reenable 608 | // the daisy chain and allow any pending lower priority internal interrupts to occur." 609 | case 7: 610 | LOG("chan%c: WR0 cmd: End of Interrupt", 'A'+chan_id); 611 | // MAME treats this as a NOP for 8274, and does EOI at the same time as ACK in the Chan B RR2 read 612 | // but since 3b1 calls WR0 EOI at end of ISR, we'll just disable IRQ here as that seems more appropriate 613 | if (chan_id == CHAN_A) { 614 | end_of_interrupt(ctx); 615 | } 616 | break; 617 | } 618 | uint8_t crc_reset_code = (data & 0xC0) >> 6; 619 | switch (crc_reset_code) { 620 | case 0: 621 | break; 622 | case 1: 623 | LOG("chan%c: reset Rx CRC Checker", 'A'+chan_id); 624 | break; 625 | case 2: 626 | LOG("chan%c: reset Tx CRC Generator", 'A'+chan_id); 627 | break; 628 | case 3: 629 | LOG("chan%c: reset Tx Underrun/End of Message Latch", 'A'+chan_id); 630 | chan->rr[0] &= ~RR0_TX_UNDERRUN; 631 | break; 632 | } 633 | } 634 | #ifdef I8274_DEBUG 635 | if (regptr) log_write_register(chan, regptr, data); 636 | #endif 637 | } 638 | 639 | #ifdef __linux__ 640 | static void ttySetRaw(int fd) 641 | { 642 | struct termios t; 643 | 644 | tcgetattr(fd, &t); 645 | 646 | // Noncanonical mode - disable: signals, extended input processing, echoing 647 | t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO); 648 | 649 | // Disable special handling of CR, NL, and BREAK 650 | // No 8th-bit stripping or parity error handling 651 | // Disable START/STOP output flow control 652 | t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK); 653 | 654 | // Disable all output processing 655 | t.c_oflag &= ~OPOST; 656 | 657 | // Non-blocking 658 | t.c_cc[VMIN] = 0; 659 | t.c_cc[VTIME] = 0; 660 | 661 | tcsetattr(fd, TCSAFLUSH, &t); 662 | } 663 | #endif 664 | 665 | static void pty_init(I8274_CTX *ctx) 666 | { 667 | #ifdef __linux__ 668 | serial_pty_filename = fbc_get_string("serial", "symlink"); 669 | ctx->ptyfd = open("/dev/ptmx", O_RDWR | O_NONBLOCK); 670 | (void)grantpt(ctx->ptyfd); 671 | (void)unlockpt(ctx->ptyfd); 672 | ttySetRaw(ctx->ptyfd); 673 | unlink(serial_pty_filename); 674 | if (symlink(ptsname(ctx->ptyfd), serial_pty_filename) == 0) 675 | printf("Serial port (tty000) on pty %s\n", ptsname(ctx->ptyfd)); 676 | else 677 | fprintf(stderr, "Error symlinking to pty: %s\n", strerror(errno)); 678 | #endif 679 | } 680 | 681 | static void pty_done(I8274_CTX *ctx) 682 | { 683 | #ifdef __linux__ 684 | close(ctx->ptyfd); 685 | unlink(serial_pty_filename); 686 | #endif 687 | } 688 | 689 | void i8274_init(I8274_CTX *ctx) 690 | { 691 | memset(&ctx->chanA, 0, sizeof(ctx->chanA)); 692 | memset(&ctx->chanB, 0, sizeof(ctx->chanB)); 693 | ctx->chanA.id = CHAN_A; 694 | ctx->chanB.id = CHAN_B; 695 | channel_reset(ctx, CHAN_A); 696 | channel_reset(ctx, CHAN_B); 697 | pty_init(ctx); 698 | } 699 | 700 | void i8274_done(I8274_CTX *ctx) 701 | { 702 | pty_done(ctx); 703 | } 704 | -------------------------------------------------------------------------------- /src/i8274.h: -------------------------------------------------------------------------------- 1 | #ifndef _I8274_H 2 | #define _I8274_H 3 | 4 | #include 5 | #include 6 | 7 | #define FIFOSIZE 128 8 | 9 | struct fifo { 10 | uint8_t buf[FIFOSIZE]; 11 | int count, head, tail; 12 | }; 13 | 14 | typedef enum { 15 | CHAN_A, 16 | CHAN_B 17 | } i8274_CHANNEL_INDEX; 18 | 19 | struct i8274_channel { 20 | uint8_t wr[8]; 21 | uint8_t rr[3]; 22 | i8274_CHANNEL_INDEX id; 23 | struct fifo rx_fifo; 24 | }; 25 | 26 | typedef enum { 27 | IRQ_NONE, 28 | IRQ_REQUESTED, 29 | IRQ_ACCEPTED // aka Z80 IEO ("Interrupt Enable Out") 30 | } i8274_IRQ_STATUS; 31 | 32 | typedef struct { 33 | struct i8274_channel chanA, chanB; 34 | 35 | i8274_IRQ_STATUS irq_request[6]; 36 | 37 | #ifdef __linux__ 38 | int ptyfd; 39 | #endif 40 | 41 | } I8274_CTX; 42 | 43 | void i8274_init(I8274_CTX *ctx); 44 | void i8274_done(I8274_CTX *ctx); 45 | bool i8274_get_irq(I8274_CTX *ctx); 46 | void i8274_scan_incoming(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id); 47 | 48 | uint8_t i8274_status_read(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id); 49 | void i8274_control_write(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id, uint8_t data); 50 | 51 | uint8_t i8274_data_in(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id); 52 | void i8274_data_out(I8274_CTX *ctx, i8274_CHANNEL_INDEX chan_id, uint8_t data); 53 | 54 | #endif 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/keyboard.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "SDL.h" 3 | #include "utils.h" 4 | #include "keyboard.h" 5 | 6 | // Enable/disable KBC debugging 7 | #define kbc_debug false 8 | 9 | /** 10 | * Key map -- a mapping from SDLK_xxx constants to scancodes and vice versa. 11 | */ 12 | struct { 13 | SDL_Keycode key; ///< SDLK_xxx key code constant 14 | int extended; ///< 1 if this is an extended keycode 15 | unsigned char scancode; ///< Keyboard scan code 16 | } keymap[] = { 17 | { SDLK_UP, 0, 0x01 }, // ROLL/Up [UpArrow] 18 | { SDLK_KP_2, 0, 0x01 }, // ROLL/Up [Keypad 2] 19 | // { SDLK_, 1, 0x02 }, // Clear Line 20 | // { SDLK_, 1, 0x03 }, // Rstrt / Ref 21 | { SDLK_ESCAPE, 1, 0x04 }, // Exit 22 | { SDLK_KP_1, 0, 0x05 }, // PREV [Keypad 1] 23 | // { SDLK_, 1, 0x06 }, // Msg 24 | { SDLK_BACKSPACE, 1, 0x07 }, // Cancl 25 | { SDLK_BACKSPACE, 0, 0x08 }, // Backspace 26 | { SDLK_TAB, 0, 0x09 }, // Tab 27 | { SDLK_RETURN, 1, 0x0a }, // ENTER 28 | { SDLK_DOWN, 0, 0x0b }, // ROLL/Down [DownArrow] 29 | { SDLK_KP_0, 0, 0x0b }, // ROLL/Down [Keypad 0] 30 | { SDLK_KP_3, 0, 0x0c }, // NEXT [Keypad 3] 31 | { SDLK_RETURN, 0, 0x0d }, // RETURN [Return] 32 | { SDLK_LEFT, 0, 0x0e }, // <-- [LeftArrow] 33 | { SDLK_KP_MINUS, 0, 0x0e }, // <-- [Keypad -] 34 | { SDLK_RIGHT, 0, 0x0f }, // --> [RightArrow] 35 | { SDLK_KP_PERIOD, 0, 0x0f }, // --> [Keypad .] 36 | // { SDLK_, 1, 0x10 }, // Creat 37 | // { SDLK_, 1, 0x11 }, // Save 38 | // { SDLK_, 1, 0x12 }, // Move 39 | // { SDLK_, 1, 0x13 }, // Ops 40 | // { SDLK_, 1, 0x14 }, // Copy 41 | { SDLK_F1, 0, 0x15 }, // F1 42 | { SDLK_F2, 0, 0x16 }, // F2 43 | { SDLK_F3, 0, 0x17 }, // F3 44 | { SDLK_F4, 0, 0x18 }, // F4 45 | { SDLK_F5, 0, 0x19 }, // F5 46 | { SDLK_F6, 0, 0x1a }, // F6 47 | { SDLK_ESCAPE, 0, 0x1b }, // ESC/DEL [Escape] 48 | { SDLK_F7, 0, 0x1c }, // F7 49 | { SDLK_F8, 0, 0x1d }, // F8 50 | { SDLK_F9, 0, 0x1e }, // Suspd 51 | // { SDLK_, 1, 0x1f }, // Rsume 52 | { SDLK_SPACE, 0, 0x20 }, // SPACE [Spacebar] 53 | // { SDLK_, 1, 0x21 }, // Undo 54 | // { SDLK_, 1, 0x22 }, // Redo 55 | // { SDLK_, 1, 0x23 }, // FIND 56 | // { SDLK_, 1, 0x24 }, // RPLAC 57 | { SDLK_PAUSE, 0, 0x25 }, // RESET/BREAK [Pause/Break] 58 | // { SDLK_, 1, 0x26 }, // DleteChar 59 | { SDLK_QUOTE, 0, 0x27 }, // ' (single-quote) 60 | // { SDLK_, 1, 0x28 }, // SLCT/MARK 61 | // { SDLK_, 1, 0x29 }, // INPUT/MODE 62 | { SDLK_PAGEUP, 0, 0x2a }, // HELP 63 | // Keycode 2B not used 64 | { SDLK_COMMA, 0, 0x2c }, // , [Comma] 65 | { SDLK_MINUS, 0, 0x2d }, // - [Dash] 66 | { SDLK_PERIOD, 0, 0x2e }, // . [Period] 67 | { SDLK_SLASH, 0, 0x2f }, // / [Forward-slash] 68 | { SDLK_0, 0, 0x30 }, // 0 69 | { SDLK_1, 0, 0x31 }, // 1 70 | { SDLK_2, 0, 0x32 }, // 2 71 | { SDLK_3, 0, 0x33 }, // 3 72 | { SDLK_4, 0, 0x34 }, // 4 73 | { SDLK_5, 0, 0x35 }, // 5 74 | { SDLK_6, 0, 0x36 }, // 6 75 | { SDLK_7, 0, 0x37 }, // 7 76 | { SDLK_8, 0, 0x38 }, // 8 77 | { SDLK_9, 0, 0x39 }, // 9 78 | // Keycode 3A not used 79 | { SDLK_SEMICOLON, 0, 0x3b }, // ; [Semicolon] 80 | // Keycode 3C not used 81 | { SDLK_EQUALS, 0, 0x3d }, // = [Equals] 82 | // Keycodes 3E, 3F, 40 not used 83 | { SDLK_INSERT, 0, 0x41 }, // CMD 84 | // { SDLK_, 1, 0x42 }, // CLOSE/OPEN 85 | { SDLK_KP_7, 0, 0x43 }, // PRINT 86 | { SDLK_KP_8, 0, 0x44 }, // CLEAR/RFRSH 87 | { SDLK_CAPSLOCK, 0, 0x45 }, // Caps Lock 88 | { SDLK_PAGEDOWN, 0, 0x46 }, // PAGE 89 | { SDLK_KP_9, 0, 0x46 }, // PAGE 90 | { SDLK_KP_4, 0, 0x47 }, // BEG 91 | { SDLK_LSHIFT, 0, 0x48 }, // Left Shift 92 | { SDLK_RSHIFT, 0, 0x49 }, // Right Shift 93 | { SDLK_HOME, 0, 0x4a }, // Home 94 | { SDLK_KP_5, 0, 0x4a }, // Home [Keypad 5] 95 | { SDLK_END, 0, 0x4b }, // End 96 | { SDLK_KP_6, 0, 0x4b }, // End [Keypad 6] 97 | { SDLK_LCTRL, 0, 0x4c }, // Left Ctrl 98 | { SDLK_RCTRL, 0, 0x4d }, // Right Ctrl 99 | // Keycodes 4E thru 5A not used 100 | { SDLK_LEFTBRACKET, 0, 0x5b }, // [ 101 | { SDLK_BACKSLASH, 0, 0x5c }, // \ (backslash) 102 | { SDLK_RIGHTBRACKET, 0, 0x5d }, // ] 103 | // Keycodes 5E, 5F not used 104 | { SDLK_BACKQUOTE, 0, 0x60 }, // ` 105 | { SDLK_a, 0, 0x61 }, // A 106 | { SDLK_b, 0, 0x62 }, // B 107 | { SDLK_c, 0, 0x63 }, // C 108 | { SDLK_d, 0, 0x64 }, // D 109 | { SDLK_e, 0, 0x65 }, // E 110 | { SDLK_f, 0, 0x66 }, // F 111 | { SDLK_g, 0, 0x67 }, // G 112 | { SDLK_h, 0, 0x68 }, // H 113 | { SDLK_i, 0, 0x69 }, // I 114 | { SDLK_j, 0, 0x6a }, // J 115 | { SDLK_k, 0, 0x6b }, // K 116 | { SDLK_l, 0, 0x6c }, // L 117 | { SDLK_m, 0, 0x6d }, // M 118 | { SDLK_n, 0, 0x6e }, // N 119 | { SDLK_o, 0, 0x6f }, // O 120 | { SDLK_p, 0, 0x70 }, // P 121 | { SDLK_q, 0, 0x71 }, // Q 122 | { SDLK_r, 0, 0x72 }, // R 123 | { SDLK_s, 0, 0x73 }, // S 124 | { SDLK_t, 0, 0x74 }, // T 125 | { SDLK_u, 0, 0x75 }, // U 126 | { SDLK_v, 0, 0x76 }, // V 127 | { SDLK_w, 0, 0x77 }, // W 128 | { SDLK_x, 0, 0x78 }, // X 129 | { SDLK_y, 0, 0x79 }, // Y 130 | { SDLK_z, 0, 0x7a }, // Z 131 | // Keycodes 7B, 7C, 7D not used 132 | { SDLK_NUMLOCKCLEAR, 0, 0x7e }, // Numlock 133 | { SDLK_DELETE, 0, 0x7f } // Dlete 134 | }; 135 | 136 | /** 137 | * List of special key codes 138 | */ 139 | enum { 140 | KEY_ALL_UP = 0x40, ///< All keys up 141 | KEY_LIST_END = 0x80, ///< End of key code list 142 | KEY_BEGIN_MOUSE = 0xCE, ///< Mouse data follows (sys/mouse.h states 0xCE, with 0xCF as mouse lost) 143 | KEY_BEGIN_KEYBOARD = 0xDF, ///< Keyboard data follows 144 | }; 145 | 146 | /** 147 | * List of keyboard commands 148 | */ 149 | enum { 150 | KEY_CMD_RESET = 0x92, ///< Reset keyboard 151 | KEY_CMD_CAPSLED_OFF = 0xB0, ///< Caps Lock LED off 152 | KEY_CMD_CAPSLED_ON = 0xB1, ///< Caps Lock LED on 153 | KEY_CMD_NUMLED_OFF = 0xA0, ///< Num Lock LED off 154 | KEY_CMD_NUMLED_ON = 0xA1, ///< Num Lock LED on 155 | KEY_CMD_MOUSE_ENABLE = 0xD0, ///< Enable mouse 156 | KEY_CMD_MOUSE_DISABLE = 0xD1 ///< Disable mouse 157 | }; 158 | 159 | void keyboard_init(KEYBOARD_STATE *ks) 160 | { 161 | // Set all key states to "not pressed" 162 | for (int i=0; i<(sizeof(ks->keystate)/sizeof(ks->keystate[0])); i++) { 163 | ks->keystate[i] = 0; 164 | } 165 | 166 | // Reset the R/W pointers and length 167 | ks->readp = ks->writep = ks->buflen = 0; 168 | 169 | // Clear the update flag 170 | ks->update_flag = false; 171 | 172 | ks->mouse_enabled = 0; 173 | ks->lastdata_mouse = 0; 174 | } 175 | 176 | void keyboard_event(KEYBOARD_STATE *ks, SDL_Event *ev) 177 | { 178 | int v = 0; 179 | switch (ev->type) { 180 | case SDL_KEYDOWN: 181 | // Key down (pressed) 182 | v = 1; 183 | break; 184 | case SDL_KEYUP: 185 | // Key up (released) 186 | v = 0; 187 | break; 188 | default: 189 | // Not a keyboard event 190 | return; 191 | } 192 | 193 | // scan the keymap 194 | for (int i=0; i < sizeof(keymap)/sizeof(keymap[0]); i++) { 195 | if (keymap[i].key == ev->key.keysym.sym) { 196 | // Keycode match. Is this an Extended Map key? 197 | if (keymap[i].extended) { 198 | // Yes -- need ALT set when pressing the key for this to be a match 199 | if (ev->key.keysym.mod & KMOD_ALT) { 200 | ks->keystate[keymap[i].scancode] = v; 201 | ks->update_flag = true; 202 | break; 203 | } 204 | } else { 205 | // Standard Map key. ALT must NOT be pressed for this to be a match 206 | if (!(ev->key.keysym.mod & KMOD_ALT)) { 207 | ks->keystate[keymap[i].scancode] = v; 208 | ks->update_flag = true; 209 | break; 210 | } 211 | } 212 | } 213 | } 214 | } 215 | 216 | bool mouse_event(KEYBOARD_STATE *ks, int dx, int dy, int db) 217 | { 218 | uint8_t flags = 0, xbyte, ybyte; 219 | 220 | if (!ks->mouse_enabled){ 221 | return (1); 222 | } 223 | ks->buffer[ks->writep] = KEY_BEGIN_MOUSE; 224 | ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; 225 | if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; 226 | 227 | /* The first byte of mouse data contains the signs of X and Y as well as the 228 | * buttons (strangely, the signs of X and Y are opposite)*/ 229 | if (dx > 0){ 230 | flags |= 0x10; 231 | } 232 | if (dy < 0){ 233 | flags |= 0x08; 234 | } 235 | 236 | flags |= db; 237 | 238 | ks->buffer[ks->writep] = flags; 239 | ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; 240 | if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; 241 | 242 | /* Second and third bytes are X and Y deltas */ 243 | xbyte = (abs(dx) > 127) ? 127 : abs(dx); 244 | ks->buffer[ks->writep] = xbyte; 245 | ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; 246 | if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; 247 | 248 | ybyte = (abs(dy) > 127) ? 127 : abs(dy); 249 | ybyte |= 0x80; 250 | ks->buffer[ks->writep] = ybyte; 251 | ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; 252 | if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; 253 | 254 | // need to double send if KEY_BEGIN_KEYBOARD value ever encountered 255 | if (ybyte == KEY_BEGIN_KEYBOARD) { 256 | ks->buffer[ks->writep] = ybyte; 257 | ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; 258 | if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; 259 | } 260 | 261 | ks->lastdata_mouse = 1; 262 | return 1; 263 | } 264 | 265 | 266 | 267 | void keyboard_scan(KEYBOARD_STATE *ks) 268 | { 269 | int nkeys = 0; 270 | 271 | // Skip doing the scan if the keyboard hasn't changed state 272 | if (!ks->update_flag) return; 273 | 274 | if (ks->lastdata_mouse){ 275 | //Keyboard Data Begins Here (BEGKBD) 276 | //This is only supposed to be sent if the last data sent was from the 277 | //mouse (sending it otherwise breaks the keyboard)*/ 278 | ks->buffer[ks->writep] = KEY_BEGIN_KEYBOARD; 279 | ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; 280 | if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; 281 | ks->lastdata_mouse = 0; 282 | } 283 | 284 | for (int i=0; i<(sizeof(ks->keystate)/sizeof(ks->keystate[0])); i++) { 285 | if (ks->keystate[i]) { 286 | LOG_IF(kbc_debug, "KBC KEY DOWN: %d\n", i); 287 | ks->buffer[ks->writep] = i; 288 | ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; 289 | if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; 290 | nkeys++; 291 | } 292 | } 293 | if (nkeys) { 294 | if (ks->writep == 0) { 295 | ks->buffer[KEYBOARD_BUFFER_SIZE-1] |= KEY_LIST_END; 296 | } else { 297 | ks->buffer[ks->writep - 1] |= KEY_LIST_END; 298 | } 299 | }else{ 300 | // If no keys down, then send All Keys Up byte 301 | LOG_IFS(kbc_debug, "KBC ALL KEYS UP\n"); 302 | ks->buffer[ks->writep] = KEY_ALL_UP; 303 | ks->writep = (ks->writep + 1) % KEYBOARD_BUFFER_SIZE; 304 | if (ks->buflen < KEYBOARD_BUFFER_SIZE) ks->buflen++; 305 | } 306 | 307 | // Clear the update flag 308 | ks->update_flag = false; 309 | } 310 | 311 | bool keyboard_get_irq(KEYBOARD_STATE *ks) 312 | { 313 | bool irq_status = false; 314 | 315 | // Conditions which may cause an IRQ :- 316 | // Read Data Reg has data and RxIRQ enabled 317 | if (ks->rxie) 318 | if (ks->buflen > 0) irq_status = true; 319 | 320 | // Transmit Data Reg empty and TxIRQ enabled 321 | // if (ks->txie) 322 | 323 | // DCD set and RxIRQ enabled 324 | // 325 | 326 | // returns interrupt status -- i.e. is there data in the buffer? 327 | return irq_status; 328 | } 329 | 330 | uint8_t keyboard_read(KEYBOARD_STATE *ks, uint8_t addr) 331 | { 332 | if ((addr & 1) == 0) { 333 | // Status register -- RS=0, read 334 | uint8_t sr = 0; 335 | if (ks->buflen > 0) sr |= 1; // SR0: a new character has been received 336 | sr |= 2; // SR1: Transmitter Data Register Empty 337 | // 0 + // SR2: Data Carrier Detect 338 | // 0 + // SR3: Clear To Send 339 | // 0 + // SR4: Framing Error 340 | // 0 + // SR5: Receiver Overrun 341 | // 0 + // SR6: Parity Error 342 | if (keyboard_get_irq(ks)) sr |= 0x80; // SR7: IRQ status 343 | //LOG_IF(kbc_debug, "KBC DBG: sr=%02X\n", sr); 344 | return sr; 345 | } else { 346 | // return data, pop off the fifo 347 | uint8_t x = ks->buffer[ks->readp]; 348 | ks->readp = (ks->readp + 1) % KEYBOARD_BUFFER_SIZE; 349 | if (ks->buflen > 0) ks->buflen--; 350 | //LOG_IF(kbc_debug, "\tKBC DBG: rxd=%02X\n", x); 351 | return x; 352 | } 353 | } 354 | 355 | void keyboard_write(KEYBOARD_STATE *ks, uint8_t addr, uint8_t val) 356 | { 357 | if ((addr & 1) == 0) { 358 | // write to control register 359 | // transmit intr enabled when CR6,5 = 01 360 | // receive intr enabled when CR7 = 1 361 | 362 | // CR0,1 = divider registers. When =11, do a software reset 363 | if ((val & 3) == 3) { 364 | ks->readp = ks->writep = ks->buflen = 0; 365 | } 366 | 367 | // Ignore CR2,3,4 (word length)... 368 | 369 | // CR5,6 = Transmit Mode 370 | ks->txie = (val & 0x60)==0x20; 371 | 372 | // CR7 = Receive Interrupt Enable 373 | ks->rxie = (val & 0x80)==0x80; 374 | } else { 375 | // Write command to KBC 376 | if (val == KEY_CMD_RESET) { 377 | LOG_IFS(kbc_debug, "KBC: KEYBOARD RESET!\n"); 378 | ks->readp = ks->writep = ks->buflen = 0; 379 | } else if (val == KEY_CMD_MOUSE_ENABLE){ 380 | ks->mouse_enabled = 1; 381 | } else if (val == KEY_CMD_MOUSE_DISABLE){ 382 | ks->mouse_enabled = 0; 383 | } else { 384 | LOG("KBC TODO: write keyboard data 0x%02X\n", val); 385 | } 386 | } 387 | } 388 | 389 | -------------------------------------------------------------------------------- /src/keyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef _KEYBOARD_H 2 | #define _KEYBOARD_H 3 | 4 | #include "SDL.h" 5 | 6 | /// Keyboard buffer size in bytes 7 | #define KEYBOARD_BUFFER_SIZE 256 8 | 9 | #define MOUSE_BUTTON_RIGHT 0x01 10 | #define MOUSE_BUTTON_MIDDLE 0x02 11 | #define MOUSE_BUTTON_LEFT 0x04 12 | 13 | typedef struct { 14 | /// Key states 15 | int keystate[0x80]; 16 | 17 | /// Keyboard buffer 18 | uint8_t buffer[KEYBOARD_BUFFER_SIZE]; 19 | 20 | /// Read pointer 21 | size_t readp; 22 | 23 | /// Write pointer 24 | size_t writep; 25 | 26 | /// Number of bytes in keyboard buffer 27 | size_t buflen; 28 | 29 | /// Transmit Interrupt Enable 30 | bool txie; 31 | 32 | /// Receive Interrupt Enable 33 | bool rxie; 34 | 35 | /// "Keyboard State Changed" flag 36 | bool update_flag; 37 | 38 | /// Mouse enable flag 39 | bool mouse_enabled; 40 | 41 | /// Flag indicating whether last data sent was from the mouse 42 | bool lastdata_mouse; 43 | } KEYBOARD_STATE; 44 | 45 | /** 46 | * Initialise a keyboard state block. 47 | * 48 | * Call this once when the keyboard is added to the emulation. 49 | */ 50 | void keyboard_init(KEYBOARD_STATE *ks); 51 | 52 | /** 53 | * SDL_Event delegation routine. 54 | * 55 | * Call this when an SDL keyup or keydown event is received. 56 | */ 57 | void keyboard_event(KEYBOARD_STATE *ks, SDL_Event *ev); 58 | 59 | /** 60 | * Keyboard scan routine. 61 | * 62 | * Call this periodically to scan the keyboard. 60 times/sec should be fine. 63 | */ 64 | void keyboard_scan(KEYBOARD_STATE *ks); 65 | 66 | bool keyboard_get_irq(KEYBOARD_STATE *ks); 67 | uint8_t keyboard_read(KEYBOARD_STATE *ks, uint8_t addr); 68 | void keyboard_write(KEYBOARD_STATE *ks, uint8_t addr, uint8_t val); 69 | 70 | bool mouse_event(KEYBOARD_STATE *ks, int dx, int dy, int db); 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /src/lightbar.c: -------------------------------------------------------------------------------- 1 | /* GIMP RGBA C-Source image dump (lightbar.c) */ 2 | 3 | static const struct { 4 | unsigned int width; 5 | unsigned int height; 6 | unsigned int bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */ 7 | unsigned char pixel_data[32 * 8 * 4 + 1]; 8 | } lightbar = { 9 | 32, 8, 4, 10 | "\000\000\000\000\000\000\000\000\310\305\304\377\366\313\307\377\366\313\307\377\310\305" 11 | "\304\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\304\306\304\377\307\332\307\377" 12 | "\307\332\307\377\304\306\304\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\307\306" 13 | "\304\377\352\332\303\377\352\332\303\377\307\306\304\377\000\000\000\000\000\000\000\000" 14 | "\000\000\000\000\000\000\000\000\262\262\262\377\326\326\326\377\326\326\326\377\262\262" 15 | "\262\377\000\000\000\000\000\000\000\000\000\000\000\000\351\312\307\377\371\216p\377\377\243\201" 16 | "\377\377\243\201\377\371\216p\377\351\312\307\377\000\000\000\000\000\000\000\000\307\326" 17 | "\307\377q\317q\377\203\351\203\377\203\351\203\377q\317q\377\307\326\307" 18 | "\377\000\000\000\000\000\000\000\000\341\327\305\377\354\331\223\377\375\371\313\377\375" 19 | "\371\313\377\354\331\223\377\341\327\305\377\000\000\000\000\000\000\000\000\323\323\323" 20 | "\377\277\277\277\377\330\330\330\377\330\330\330\377\277\277\277\377\323" 21 | "\323\323\377\000\000\000\000\307\305\304\377\366\203f\377\377\233y\377\377\233y\377" 22 | "\377\233y\377\377\233y\377\366\203f\377\307\305\304\377\304\306\304\377j" 23 | "\307j\377\177\345\177\377\177\345\177\377\177\345\177\377\177\345\177\377" 24 | "j\307j\377\304\306\304\377\306\306\304\377\351\321~\377\377\366\265\377\377" 25 | "\366\265\377\377\366\265\377\377\366\265\377\351\321~\377\306\306\304\377" 26 | "\262\262\262\377\267\267\267\377\324\324\324\377\324\324\324\377\324\324" 27 | "\324\377\324\324\324\377\267\267\267\377\262\262\262\377\356\307\305\377" 28 | "\371tS\377\374~\\\377\376\206d\377\376\206d\377\374~\\\377\371tS\377\356" 29 | "\307\305\377\305\324\305\377_\304_\377i\316i\377q\327q\377q\327q\377i\316" 30 | "i\377_\304_\377\305\324\305\377\342\324\303\377\362\324e\377\370\337v\377" 31 | "\376\350\203\377\376\350\203\377\370\337v\377\362\324e\377\342\324\303\377" 32 | "\321\321\321\377\270\270\270\377\300\300\300\377\307\307\307\377\307\307" 33 | "\307\377\300\300\300\377\270\270\270\377\321\321\321\377\353\306\304\377" 34 | "\365Y\067\377\360H&\377\360H&\377\360H&\377\360H&\377\365Y\067\377\353\306" 35 | "\304\377\305\322\305\377J\260J\377\067\235\067\377\067\235\067\377\067\235\067" 36 | "\377\067\235\067\377J\260J\377\305\322\305\377\340\322\303\377\352\300(\377" 37 | "\340\256\006\377\340\256\006\377\340\256\006\377\340\256\006\377\352\300(\377\340" 38 | "\322\303\377\317\317\317\377\247\247\247\377\234\234\234\377\234\234\234" 39 | "\377\234\234\234\377\234\234\234\377\247\247\247\377\317\317\317\377\307" 40 | "\304\304\377\351W>\377\370V\064\377\367T\062\377\367T\062\377\370V\064\377\351" 41 | "W>\377\307\304\304\377\304\305\304\377N\247N\377L\262L\377I\257I\377I\257" 42 | "I\377L\262L\377N\247N\377\304\305\304\377\306\305\304\377\333\261\062\377" 43 | "\361\303\025\377\356\300\023\377\356\300\023\377\361\303\025\377\333\261\062\377" 44 | "\306\305\304\377\262\262\262\377\231\231\231\377\250\250\250\377\246\246" 45 | "\246\377\246\246\246\377\250\250\250\377\231\231\231\377\262\262\262\377" 46 | "\000\000\000\000\340\305\304\377\350P\071\377\373T\062\377\373T\062\377\350P\071\377" 47 | "\340\305\304\377\000\000\000\000\000\000\000\000\305\317\305\377K\243K\377Q\266Q\377Q\266" 48 | "Q\377K\243K\377\305\317\305\377\000\000\000\000\000\000\000\000\331\317\304\377\333\255," 49 | "\377\370\306\006\377\370\306\006\377\333\255,\377\331\317\304\377\000\000\000\000\000\000" 50 | "\000\000\314\314\314\377\224\224\224\377\246\246\246\377\246\246\246\377\224" 51 | "\224\224\377\314\314\314\377\000\000\000\000\000\000\000\000\000\000\000\000\306\304\304\377\344" 52 | "\304\303\377\344\304\303\377\306\304\304\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000" 53 | "\000\000\304\305\304\377\304\316\304\377\304\316\304\377\304\305\304\377\000\000" 54 | "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\306\305\304\377\332\316\303\377\332\316\303" 55 | "\377\306\305\304\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\262\262\262\377\313" 56 | "\313\313\377\313\313\313\377\262\262\262\377\000\000\000\000\000\000\000\000", 57 | }; 58 | 59 | -------------------------------------------------------------------------------- /src/m68kconf.h: -------------------------------------------------------------------------------- 1 | /* ======================================================================== */ 2 | /* ========================= LICENSING & COPYRIGHT ======================== */ 3 | /* ======================================================================== */ 4 | /* 5 | * MUSASHI 6 | * Version 3.32 7 | * 8 | * A portable Motorola M680x0 processor emulation engine. 9 | * Copyright Karl Stenerud. All rights reserved. 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | 31 | 32 | #ifndef M68KCONF__HEADER 33 | #define M68KCONF__HEADER 34 | 35 | 36 | /* Configuration switches. 37 | * Use OPT_SPECIFY_HANDLER for configuration options that allow callbacks. 38 | * OPT_SPECIFY_HANDLER causes the core to link directly to the function 39 | * or macro you specify, rather than using callback functions whose pointer 40 | * must be passed in using m68k_set_xxx_callback(). 41 | */ 42 | #define OPT_OFF 0 43 | #define OPT_ON 1 44 | #define OPT_SPECIFY_HANDLER 2 45 | 46 | 47 | /* ======================================================================== */ 48 | /* ============================== MAME STUFF ============================== */ 49 | /* ======================================================================== */ 50 | 51 | /* If you're compiling this for MAME, only change M68K_COMPILE_FOR_MAME 52 | * to OPT_ON and use m68kmame.h to configure the 68k core. 53 | */ 54 | #ifndef M68K_COMPILE_FOR_MAME 55 | #define M68K_COMPILE_FOR_MAME OPT_OFF 56 | #endif /* M68K_COMPILE_FOR_MAME */ 57 | 58 | 59 | #if M68K_COMPILE_FOR_MAME == OPT_OFF 60 | 61 | 62 | /* ======================================================================== */ 63 | /* ============================= CONFIGURATION ============================ */ 64 | /* ======================================================================== */ 65 | 66 | /* Turn ON if you want to use the following M68K variants */ 67 | #define M68K_EMULATE_010 OPT_ON 68 | #define M68K_EMULATE_EC020 OPT_OFF 69 | #define M68K_EMULATE_020 OPT_OFF 70 | #define M68K_EMULATE_030 OPT_OFF 71 | #define M68K_EMULATE_040 OPT_OFF 72 | 73 | /* If ON, the CPU will call m68k_read_immediate_xx() for immediate addressing 74 | * and m68k_read_pcrelative_xx() for PC-relative addressing. 75 | * If off, all read requests from the CPU will be redirected to m68k_read_xx() 76 | */ 77 | #define M68K_SEPARATE_READS OPT_OFF 78 | 79 | /* If ON, the CPU will call m68k_write_32_pd() when it executes move.l with a 80 | * predecrement destination EA mode instead of m68k_write_32(). 81 | * To simulate real 68k behavior, m68k_write_32_pd() must first write the high 82 | * word to [address+2], and then write the low word to [address]. 83 | */ 84 | #define M68K_SIMULATE_PD_WRITES OPT_OFF 85 | 86 | /* If ON, CPU will call the interrupt acknowledge callback when it services an 87 | * interrupt. 88 | * If off, all interrupts will be autovectored and all interrupt requests will 89 | * auto-clear when the interrupt is serviced. 90 | */ 91 | #define M68K_EMULATE_INT_ACK OPT_OFF 92 | #define M68K_INT_ACK_CALLBACK(A) your_int_ack_handler_function(A) 93 | 94 | 95 | /* If ON, CPU will call the breakpoint acknowledge callback when it encounters 96 | * a breakpoint instruction and it is running a 68010+. 97 | */ 98 | #define M68K_EMULATE_BKPT_ACK OPT_OFF 99 | #define M68K_BKPT_ACK_CALLBACK() your_bkpt_ack_handler_function() 100 | 101 | 102 | /* If ON, the CPU will monitor the trace flags and take trace exceptions 103 | */ 104 | #define M68K_EMULATE_TRACE OPT_OFF 105 | 106 | 107 | /* If ON, CPU will call the output reset callback when it encounters a reset 108 | * instruction. 109 | */ 110 | #define M68K_EMULATE_RESET OPT_OFF 111 | #define M68K_RESET_CALLBACK() your_reset_handler_function() 112 | 113 | /* If ON, CPU will call the callback when it encounters a cmpi.l #v, dn 114 | * instruction. 115 | */ 116 | #define M68K_CMPILD_HAS_CALLBACK OPT_OFF 117 | #define M68K_CMPILD_CALLBACK(v,r) your_cmpild_handler_function(v,r) 118 | 119 | 120 | /* If ON, CPU will call the callback when it encounters a rte 121 | * instruction. 122 | */ 123 | #define M68K_RTE_HAS_CALLBACK OPT_OFF 124 | #define M68K_RTE_CALLBACK() your_rte_handler_function() 125 | 126 | /* If ON, CPU will call the callback when it encounters a tas 127 | * instruction. 128 | */ 129 | #define M68K_TAS_HAS_CALLBACK OPT_OFF 130 | #define M68K_TAS_CALLBACK() your_tas_handler_function() 131 | 132 | /* If ON, CPU will call the callback when it encounters an illegal instruction 133 | * passing the opcode as argument. If the callback returns 1, then it's considered 134 | * as a normal instruction, and the illegal exception in canceled. If it returns 0, 135 | * the exception occurs normally. 136 | * The callback looks like int callback(int opcode) 137 | * You should put OPT_SPECIFY_HANDLER here if you cant to use it, otherwise it will 138 | * use a dummy default handler and you'll have to call m68k_set_illg_instr_callback explicitely 139 | */ 140 | #define M68K_ILLG_HAS_CALLBACK OPT_OFF 141 | #define M68K_ILLG_CALLBACK(opcode) op_illg(opcode) 142 | 143 | /* If ON, CPU will call the set fc callback on every memory access to 144 | * differentiate between user/supervisor, program/data access like a real 145 | * 68000 would. This should be enabled and the callback should be set if you 146 | * want to properly emulate the m68010 or higher. (moves uses function codes 147 | * to read/write data from different address spaces) 148 | */ 149 | #define M68K_EMULATE_FC OPT_OFF 150 | #define M68K_SET_FC_CALLBACK(A) your_set_fc_handler_function(A) 151 | 152 | /* If ON, CPU will call the pc changed callback when it changes the PC by a 153 | * large value. This allows host programs to be nicer when it comes to 154 | * fetching immediate data and instructions on a banked memory system. 155 | */ 156 | #define M68K_MONITOR_PC OPT_OFF 157 | #define M68K_SET_PC_CALLBACK(A) your_pc_changed_handler_function(A) 158 | 159 | 160 | /* If ON, CPU will call the instruction hook callback before every 161 | * instruction. 162 | */ 163 | #define M68K_INSTRUCTION_HOOK OPT_OFF 164 | #define M68K_INSTRUCTION_CALLBACK(pc) your_instruction_hook_function(pc) 165 | 166 | 167 | /* If ON, the CPU will emulate the 4-byte prefetch queue of a real 68000 */ 168 | #define M68K_EMULATE_PREFETCH OPT_ON 169 | 170 | 171 | /* If ON, the CPU will generate address error exceptions if it tries to 172 | * access a word or longword at an odd address. 173 | * NOTE: This is only emulated properly for 68000 mode. 174 | */ 175 | #define M68K_EMULATE_ADDRESS_ERROR OPT_OFF 176 | 177 | 178 | /* Turn ON to enable logging of illegal instruction calls. 179 | * M68K_LOG_FILEHANDLE must be #defined to a stdio file stream. 180 | * Turn on M68K_LOG_1010_1111 to log all 1010 and 1111 calls. 181 | */ 182 | #define M68K_LOG_ENABLE OPT_OFF 183 | #define M68K_LOG_1010_1111 OPT_OFF 184 | #define M68K_LOG_FILEHANDLE some_file_handle 185 | 186 | /* Emulate PMMU : if you enable this, there will be a test to see if the current chip has some enabled pmmu added to every memory access, 187 | * so enable this only if it's useful */ 188 | #define M68K_EMULATE_PMMU OPT_OFF 189 | 190 | /* ----------------------------- COMPATIBILITY ---------------------------- */ 191 | 192 | /* The following options set optimizations that violate the current ANSI 193 | * standard, but will be compliant under the forthcoming C9X standard. 194 | */ 195 | 196 | 197 | /* If ON, the enulation core will use 64-bit integers to speed up some 198 | * operations. 199 | */ 200 | #define M68K_USE_64_BIT OPT_ON 201 | 202 | 203 | #endif /* M68K_COMPILE_FOR_MAME */ 204 | 205 | /* ======================================================================== */ 206 | /* ============================== END OF FILE ============================= */ 207 | /* ======================================================================== */ 208 | 209 | #endif /* M68KCONF__HEADER */ 210 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "SDL.h" 10 | 11 | #include "musashi/m68k.h" 12 | #include "version.h" 13 | #include "state.h" 14 | #include "memory.h" 15 | #include "fbconfig.h" 16 | #include "utils.h" 17 | 18 | #include "lightbar.c" 19 | #include "i8274.h" 20 | 21 | extern int cpu_log_enabled; 22 | 23 | void FAIL(char *err) 24 | { 25 | state_done(); 26 | fprintf(stderr, "ERROR: %s\nExiting...\n", err); 27 | exit(EXIT_FAILURE); 28 | } 29 | 30 | static int load_fd() 31 | { 32 | int writeable = 1; 33 | 34 | const char *discim = fbc_get_string("floppy", "disk"); 35 | state.fdc_disc = fopen(discim, "r+b"); 36 | if (!state.fdc_disc){ 37 | writeable = 0; 38 | state.fdc_disc = fopen(discim, "rb"); 39 | } 40 | if (!state.fdc_disc){ 41 | fprintf(stderr, "ERROR loading floppy image '%s'.\n", discim); 42 | state.fdc_disc = NULL; 43 | return (0); 44 | }else{ 45 | wd2797_load(&state.fdc_ctx, state.fdc_disc, 512, 2, 40, writeable); 46 | return (1); 47 | } 48 | } 49 | 50 | static int load_hd() 51 | { 52 | int ret = 0; 53 | const char *disk1 = fbc_get_string("hard_disk", "disk1"); 54 | const char *disk2 = fbc_get_string("hard_disk", "disk2"); 55 | int sectors_per_track = fbc_get_int("hard_disk", "sectors_per_track"); 56 | int heads = fbc_get_int("hard_disk", "heads"); 57 | // bytes per sector is fixed at 512, not configurable, all hard drives of the 3B1 58 | // era used 512-byte sectors. 59 | const int bytes_per_sector = 512; 60 | 61 | state.hdc_disc0 = fopen(disk1, "r+b"); 62 | if (!state.hdc_disc0){ 63 | fprintf(stderr, "Drive 0: ERROR loading disc image '%s'.\n", disk1); 64 | state.hdc_disc0 = NULL; 65 | return (0); 66 | } else { 67 | if (wd2010_init(&state.hdc_ctx, state.hdc_disc0, 0, bytes_per_sector, sectors_per_track, heads) == WD2010_ERR_OK) { 68 | printf("Drive 0: Disc image '%s' loaded.\n", disk1); 69 | ret = 1; 70 | } else { 71 | fprintf(stderr, "Drive 0: ERROR loading disc image '%s'.\n", disk1); 72 | ret = 0; 73 | } 74 | } 75 | 76 | state.hdc_disc1 = fopen(disk2, "r+b"); 77 | if (!state.hdc_disc1){ 78 | fprintf(stderr, "Drive 1: ERROR loading disc image '%s'.\n", disk2); 79 | state.hdc_disc1 = NULL; 80 | } else { 81 | if (wd2010_init(&state.hdc_ctx, state.hdc_disc1, 1, bytes_per_sector, sectors_per_track, heads) == WD2010_ERR_OK) { 82 | printf("Drive 1: Disc image '%s' loaded.\n", disk2); 83 | } else { 84 | fprintf(stderr, "Drive 1: ERROR loading disc image '%s'.\n", disk2); 85 | } 86 | } 87 | return ret; 88 | } 89 | 90 | 91 | 92 | /** 93 | * @brief Set the pixel at (x, y) to the given value 94 | * @note The surface must be locked before calling this! 95 | * @param surface SDL surface upon which to draw 96 | * @param x X co-ordinate 97 | * @param y Y co-ordinate 98 | * @param pixel Pixel value (from SDL_MapRGB) 99 | */ 100 | static inline void putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel) 101 | { 102 | int bpp = surface->format->BytesPerPixel; 103 | /* Here p is the address to the pixel we want to set */ 104 | Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; 105 | 106 | switch (bpp) { 107 | case 1: 108 | *p = pixel; 109 | break; 110 | 111 | case 2: 112 | *(Uint16 *)p = pixel; 113 | break; 114 | 115 | case 3: 116 | if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { 117 | p[0] = (pixel >> 16) & 0xff; 118 | p[1] = (pixel >> 8) & 0xff; 119 | p[2] = pixel & 0xff; 120 | } 121 | else { 122 | p[0] = pixel & 0xff; 123 | p[1] = (pixel >> 8) & 0xff; 124 | p[2] = (pixel >> 16) & 0xff; 125 | } 126 | break; 127 | 128 | case 4: 129 | *(Uint32 *)p = pixel; 130 | break; 131 | 132 | default: 133 | break; /* shouldn't happen, but avoids warnings */ 134 | } // switch 135 | } 136 | 137 | 138 | /** 139 | * @brief Refresh the screen. 140 | * @param surface SDL surface upon which to draw. 141 | * @param renderer SDL renderer. 142 | * @param texture SDL texture to copy surface to. 143 | */ 144 | void refreshScreen(SDL_Surface *s, SDL_Renderer *r, SDL_Texture *t) 145 | { 146 | // Lock the screen surface (if necessary) 147 | if (SDL_MUSTLOCK(s)) { 148 | if (SDL_LockSurface(s) < 0) { 149 | fprintf(stderr, "ERROR: Unable to lock screen!\n"); 150 | exit(EXIT_FAILURE); 151 | } 152 | } 153 | 154 | static int red, green, blue; 155 | static bool inited = false; 156 | if (! inited) { 157 | inited = true; 158 | red = fbc_get_int("display", "red"); 159 | green = fbc_get_int("display", "green"); 160 | blue = fbc_get_int("display", "blue"); 161 | } 162 | 163 | // Map the foreground and background colours 164 | // Uint32 fg = SDL_MapRGB(s->format, 0xFF, 0xC1, 0x06); // amber foreground 165 | // Uint32 fg = SDL_MapRGB(s->format, 0xFF, 0xFF, 0xFF); // white foreground 166 | // Uint32 fg = SDL_MapRGB(s->format, 0x50, 0xFF, 0xA0); // minty foreground (possibly closer to actual color?) 167 | // Uint32 fg = SDL_MapRGB(s->format, 0x00, 0xFF, 0x00); // green foreground 168 | Uint32 fg = SDL_MapRGB(s->format, red, green, blue); 169 | Uint32 bg = SDL_MapRGB(s->format, 0x00, 0x00, 0x00); // black background 170 | 171 | // Refresh the 3B1 screen area first. TODO: only do this if VRAM has actually changed! 172 | uint32_t vram_address = 0; 173 | for (int y=0; y<348; y++) { 174 | for (int x=0; x<720; x+=16) { // 720 pixels, monochrome, packed into 16bit words 175 | // Get the pixel 176 | uint16_t val = RD16(state.vram, vram_address, sizeof(state.vram)-1); 177 | vram_address += 2; 178 | // Now copy it to the video buffer 179 | for (int px=0; px<16; px++) { 180 | if (val & 1) 181 | putpixel(s, x+px, y, fg); 182 | else 183 | putpixel(s, x+px, y, bg); 184 | val >>= 1; 185 | } 186 | } 187 | } 188 | 189 | // Unlock the screen surface 190 | if (SDL_MUSTLOCK(s)) { 191 | SDL_UnlockSurface(s); 192 | } 193 | 194 | // Update framebuffer texture 195 | SDL_UpdateTexture(t, NULL, s->pixels, s->pitch); 196 | SDL_RenderCopy(r, t, NULL, NULL); 197 | } 198 | 199 | #define LED_SIZE 8 200 | 201 | void refreshStatusBar(SDL_Renderer *r, SDL_Texture *lightbar_tex) 202 | { 203 | SDL_Rect red_led = { 0, 0, LED_SIZE, LED_SIZE }; 204 | SDL_Rect green_led = { LED_SIZE, 0, LED_SIZE, LED_SIZE }; 205 | SDL_Rect yellow_led = { LED_SIZE*2, 0, LED_SIZE, LED_SIZE }; 206 | SDL_Rect inactive_led = { LED_SIZE*3, 0, LED_SIZE, LED_SIZE }; 207 | SDL_Rect dstrect = { 720-LED_SIZE*4, 348-LED_SIZE, LED_SIZE, LED_SIZE }; 208 | 209 | // LED bit values are inverse of documentation (leftmost LED is LSB) 210 | // Red user LED (leftmost LED) can be turned on using "syslocal(SYSL_LED, 1)" from sys/syslocal.h 211 | SDL_RenderCopy(r, lightbar_tex, (state.leds & 1) ? &red_led : &inactive_led, &dstrect); 212 | dstrect.x += LED_SIZE; 213 | SDL_RenderCopy(r, lightbar_tex, (state.leds & 2) ? &green_led : &inactive_led, &dstrect); 214 | dstrect.x += LED_SIZE; 215 | SDL_RenderCopy(r, lightbar_tex, (state.leds & 4) ? &yellow_led : &inactive_led, &dstrect); 216 | dstrect.x += LED_SIZE; 217 | SDL_RenderCopy(r, lightbar_tex, (state.leds & 8) ? &red_led : &inactive_led, &dstrect); 218 | } 219 | 220 | /** 221 | * @brief Handle events posted by SDL. 222 | */ 223 | bool HandleSDLEvents(SDL_Window *window) 224 | { 225 | SDL_Event event; 226 | static int mouse_grabbed = 0, mouse_buttons = 0; 227 | int dx = 0, dy = 0; 228 | 229 | while (SDL_PollEvent(&event)) 230 | { 231 | if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) { 232 | keyboard_event(&state.kbd, &event); 233 | } 234 | 235 | switch (event.type) { 236 | case SDL_QUIT: 237 | // Quit button tagged. Exit. 238 | return true; 239 | case SDL_KEYDOWN: 240 | switch (event.key.keysym.sym) { 241 | case SDLK_F10: 242 | if (mouse_grabbed){ 243 | SDL_SetRelativeMouseMode(SDL_FALSE); 244 | mouse_grabbed = 0; 245 | }else{ 246 | SDL_SetRelativeMouseMode(SDL_TRUE); 247 | mouse_grabbed = 1; 248 | } 249 | break; 250 | case SDLK_F11: 251 | if (state.fdc_disc) { 252 | wd2797_unload(&state.fdc_ctx); 253 | fclose(state.fdc_disc); 254 | state.fdc_disc = NULL; 255 | printf("Floppy image unloaded.\n"); 256 | } else { 257 | load_fd(); 258 | } 259 | break; 260 | case SDLK_F12: 261 | if (event.key.keysym.mod & (KMOD_LALT | KMOD_RALT)) 262 | // ALT-F12 pressed; exit emulator 263 | return true; 264 | break; 265 | default: 266 | break; 267 | } 268 | break; 269 | case SDL_MOUSEMOTION: 270 | SDL_GetRelativeMouseState(&dx, &dy); 271 | if (dx==0 && dy==0) break; // sometimes SDL returns 0 for both, don't process 272 | case SDL_MOUSEBUTTONUP: 273 | case SDL_MOUSEBUTTONDOWN: 274 | if (mouse_grabbed){ 275 | if (event.type == SDL_MOUSEBUTTONDOWN) { 276 | if (event.button.button == SDL_BUTTON_LEFT){ 277 | mouse_buttons |= MOUSE_BUTTON_LEFT; 278 | }else if (event.button.button == SDL_BUTTON_MIDDLE){ 279 | mouse_buttons |= MOUSE_BUTTON_MIDDLE; 280 | }else if (event.button.button == SDL_BUTTON_RIGHT){ 281 | mouse_buttons |= MOUSE_BUTTON_RIGHT; 282 | } 283 | } else if (event.type == SDL_MOUSEBUTTONUP) { 284 | if (event.button.button == SDL_BUTTON_LEFT){ 285 | mouse_buttons &= ~MOUSE_BUTTON_LEFT; 286 | }else if (event.button.button == SDL_BUTTON_MIDDLE){ 287 | mouse_buttons &= ~MOUSE_BUTTON_MIDDLE; 288 | }else if (event.button.button == SDL_BUTTON_RIGHT){ 289 | mouse_buttons &= ~MOUSE_BUTTON_RIGHT; 290 | } 291 | } 292 | mouse_event(&state.kbd, dx, dy, mouse_buttons); 293 | dx = 0; 294 | dy = 0; 295 | } 296 | break; 297 | default: 298 | break; 299 | } 300 | } 301 | 302 | return false; 303 | } 304 | 305 | 306 | /** 307 | * @brief Validate the memory amounts requested. 308 | */ 309 | 310 | void validate_memory(int base_memory, int extended_memory) 311 | { 312 | static const int base_memsizes_allowed[] = { 313 | 512, 1024, 2048 314 | }; 315 | static const int extended_memsizes_allowed[] = { 316 | 0, 512, 1024, 1536, 2048 317 | }; 318 | 319 | bool base_ok = false; 320 | bool extended_ok = false; 321 | 322 | int i; 323 | 324 | for (i = 0; i < NELEMS(base_memsizes_allowed); i++) { 325 | if (base_memory == base_memsizes_allowed[i]) { 326 | base_ok = true; 327 | break; 328 | } 329 | } 330 | 331 | for (i = 0; i < NELEMS(extended_memsizes_allowed); i++) { 332 | if (extended_memory == extended_memsizes_allowed[i]) { 333 | extended_ok = true; 334 | break; 335 | } 336 | } 337 | 338 | if (! base_ok) { 339 | fprintf(stderr, "Motherboard memory size %dK is invalid; it must be 512, 1024, or 2048.\n", 340 | base_memory); 341 | exit(EXIT_FAILURE); 342 | } 343 | 344 | if (! extended_ok) { 345 | fprintf(stderr, "Extension memory size %dK is invalid; it must be a multiple of 512K.\n", 346 | extended_memory); 347 | exit(EXIT_FAILURE); 348 | } 349 | 350 | printf("Memory config: %iKB On-board, %iKB Expansion\n", base_memory, extended_memory); 351 | if (base_memory + extended_memory < 1024) 352 | printf("*WARNING*: 1MB or higher RAM recommended for UNIX 3.51.\n\n"); 353 | } 354 | 355 | /**************************** 356 | * blessed be thy main()... 357 | ****************************/ 358 | 359 | int main(int argc, char *argv[]) 360 | { 361 | float scalex = fbc_get_double("display", "x_scale"); 362 | float scaley = fbc_get_double("display", "y_scale"); 363 | 364 | if (scalex <= 0 || scalex > 45 || scaley <= 0 || scaley > 45) { 365 | // 45 chosen as max because 45 * 720 < INT16_MAX 366 | fprintf(stderr, "scale factors must be greater than zero and less than or equal to 45\n"); 367 | exit(EXIT_FAILURE); 368 | } 369 | 370 | // copyright banner 371 | printf("FreeBee: A Quick-and-Dirty AT&T 3B1 Emulator. Version %s, %s mode.\n", VER_FULLSTR, VER_BUILD_TYPE); 372 | printf("Copyright (C) 2010 P. A. Pemberton. All rights reserved.\nLicensed under the Apache License Version 2.0.\n"); 373 | printf("Musashi M680x0 emulator engine developed by Karl Stenerud \n"); 374 | printf("\n"); 375 | 376 | // set up system state 377 | // RAM sizes come from config, default 2 Meg for each kind of memory 378 | int i; 379 | int base_memory = fbc_get_int("memory", "base_memory"); 380 | int extended_memory = fbc_get_int("memory", "extended_memory"); 381 | 382 | validate_memory(base_memory, extended_memory); // exits if problem 383 | 384 | base_memory *= 1024; 385 | extended_memory *= 1024; 386 | if ((i = state_init(base_memory, extended_memory)) != STATE_E_OK) { 387 | fprintf(stderr, "ERROR: Emulator initialisation failed. Error code %d.\n", i); 388 | return i; 389 | } 390 | 391 | // set up musashi and reset the CPU 392 | m68k_init(); 393 | m68k_set_cpu_type(M68K_CPU_TYPE_68010); 394 | m68k_pulse_reset(); 395 | 396 | // Set up SDL 397 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) { 398 | fprintf(stderr, "Could not initialise SDL: %s.\n", SDL_GetError()); 399 | exit(EXIT_FAILURE); 400 | } 401 | 402 | // Make sure SDL cleans up after itself 403 | atexit(SDL_Quit); 404 | 405 | // Set up the video display 406 | SDL_Window *window; 407 | if ((window = SDL_CreateWindow("FreeBee 3B1 Emulator", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 408 | (int) ceilf(720*scalex), (int) ceilf(348*scaley), 0)) == NULL) { 409 | fprintf(stderr, "Error creating SDL window: %s.\n", SDL_GetError()); 410 | exit(EXIT_FAILURE); 411 | } 412 | // SDL default is "nearest", our default is "linear" if there's scaling 413 | if (scalex != 1.0 || scaley != 1.0) 414 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, fbc_get_string("display", "scale_quality")); 415 | SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0); 416 | SDL_RenderSetScale(renderer, scalex, scaley); 417 | if (!renderer){ 418 | fprintf(stderr, "Error creating SDL renderer: %s.\n", SDL_GetError()); 419 | exit(EXIT_FAILURE); 420 | } 421 | SDL_Texture *fbTexture = SDL_CreateTexture(renderer, 422 | SDL_PIXELFORMAT_RGB888, 423 | SDL_TEXTUREACCESS_STREAMING, 424 | 720, 348); 425 | if (!fbTexture){ 426 | fprintf(stderr, "Error creating SDL FB texture: %s.\n", SDL_GetError()); 427 | exit(EXIT_FAILURE); 428 | } 429 | SDL_Surface *screen = SDL_CreateRGBSurface(0, 720, 348, 32, 0, 0, 0, 0); 430 | if (!screen){ 431 | fprintf(stderr, "Error creating SDL FB surface: %s.\n", SDL_GetError()); 432 | exit(EXIT_FAILURE); 433 | } 434 | // Load in status LED sprites 435 | SDL_Surface *surf = SDL_CreateRGBSurfaceFrom((void*)lightbar.pixel_data, lightbar.width, lightbar.height, 436 | lightbar.bytes_per_pixel*8, lightbar.bytes_per_pixel*lightbar.width, 437 | #if SDL_BYTEORDER == SDL_BIG_ENDIAN 438 | 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF 439 | #else 440 | 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 441 | #endif 442 | ); 443 | SDL_Texture *lightbarTexture = SDL_CreateTextureFromSurface(renderer, surf); 444 | SDL_FreeSurface(surf); 445 | 446 | printf("Set %dx%d at %d bits-per-pixel mode\n\n", (int) ceilf(720*scalex), (int) ceilf(348*scaley), screen->format->BitsPerPixel); 447 | 448 | // Load a disc image 449 | load_fd(); 450 | 451 | load_hd(); 452 | 453 | /*** 454 | * The 3B1 CPU runs at 10MHz, with DMA running at 1MHz and video refreshing at 455 | * 60.821331Hz, with a 60Hz periodic interrupt. 456 | */ 457 | const uint32_t SYSTEM_CLOCK = 10e6; // Hz 458 | const uint32_t TIMESLOT_FREQUENCY = 100;//240; // Hz 459 | const uint32_t MILLISECS_PER_TIMESLOT = 1e3 / TIMESLOT_FREQUENCY; 460 | const uint32_t CYCLES_PER_TIMESLOT = SYSTEM_CLOCK / TIMESLOT_FREQUENCY; 461 | const uint32_t CLOCKS_PER_60HZ = (SYSTEM_CLOCK / 60); 462 | const uint32_t NUM_CPU_TIMESLOTS = 500; 463 | uint32_t next_timeslot = SDL_GetTicks() + MILLISECS_PER_TIMESLOT; 464 | uint32_t clock_cycles = 0, cycles_run; 465 | bool exitEmu = false; 466 | uint8_t last_leds = 255; 467 | 468 | /*bool lastirq_fdc = false;*/ 469 | for (;;) { 470 | for (i = 0; i < CYCLES_PER_TIMESLOT; i += cycles_run){ 471 | // Run the CPU for however many cycles we need to. CPU core clock is 472 | // 10MHz, and we're running at 240Hz/timeslot. Thus: 10e6/240 or 473 | // 41667 cycles per timeslot. 474 | cycles_run = m68k_execute(CYCLES_PER_TIMESLOT / NUM_CPU_TIMESLOTS); 475 | clock_cycles += cycles_run; 476 | 477 | // Run the DMA engine 478 | if (state.dmaen) { 479 | // DMA ready to go -- so do it. 480 | size_t num = 0; 481 | while (state.dma_count < 0x4000) { 482 | uint16_t d = 0; 483 | 484 | // num tells us how many words we've copied. If this is greater than the per-timeslot DMA maximum, bail out! 485 | if (num > (1e6/TIMESLOT_FREQUENCY / NUM_CPU_TIMESLOTS)) break; 486 | 487 | // Evidently we have more words to copy. Copy them. 488 | if (state.dma_dev == DMA_DEV_FD){ 489 | if (!wd2797_get_drq(&state.fdc_ctx)) { 490 | // Bail out, no data available. Try again later. 491 | break; 492 | } 493 | }else if (state.dma_dev == DMA_DEV_HD0){ 494 | if (!wd2010_get_drq(&state.hdc_ctx)) { 495 | // Bail out, no data available. Try again later. 496 | break; 497 | } 498 | }else{ 499 | fprintf(stderr, "ERROR: DMA attempt with no drive selected!\n"); 500 | } 501 | if (!access_check_dma(state.dma_reading)) { 502 | break; 503 | } 504 | uint32_t newAddr; 505 | // Map logical address to a physical RAM address 506 | newAddr = mapAddr(state.dma_address, !state.dma_reading); 507 | 508 | if (!state.dma_reading) { 509 | // Data available. Get it from the FDC or HDC. 510 | if (state.dma_dev == DMA_DEV_FD) { 511 | d = wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA); 512 | d <<= 8; 513 | d += wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA); 514 | }else if (state.dma_dev == DMA_DEV_HD0) { 515 | d = wd2010_read_data(&state.hdc_ctx); 516 | d <<= 8; 517 | d += wd2010_read_data(&state.hdc_ctx); 518 | } 519 | if (newAddr <= 0x1FFFFF) { 520 | WR16(state.base_ram, newAddr, state.base_ram_size - 1, d); 521 | } else if (newAddr >= 0x200000) { 522 | WR16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1, d); 523 | } 524 | } else { 525 | // Data write to FDC or HDC. 526 | 527 | // Get the data from RAM 528 | if (newAddr <= 0x1fffff) { 529 | d = RD16(state.base_ram, newAddr, state.base_ram_size - 1); 530 | } else { 531 | if (newAddr <= (state.exp_ram_size + 0x200000 - 1)) 532 | d = RD16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1); 533 | else 534 | d = 0xffff; 535 | } 536 | 537 | // Send the data to the FDD or HDD 538 | if (state.dma_dev == DMA_DEV_FD){ 539 | wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d >> 8)); 540 | wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d & 0xff)); 541 | }else if (state.dma_dev == DMA_DEV_HD0){ 542 | wd2010_write_data(&state.hdc_ctx, (d >> 8)); 543 | wd2010_write_data(&state.hdc_ctx, (d & 0xff)); 544 | } 545 | } 546 | 547 | // Increment DMA address 548 | state.dma_address+=2; 549 | // Increment number of words transferred 550 | num++; state.dma_count++; 551 | } 552 | 553 | // Turn off DMA engine if we finished this cycle 554 | if (state.dma_count >= 0x4000) { 555 | // FIXME? apparently this isn't required... or is it? 556 | state.dma_count = 0x3fff; 557 | /*state.dmaen = false;*/ 558 | } 559 | }else if (wd2010_get_drq(&state.hdc_ctx)){ 560 | wd2010_dma_miss(&state.hdc_ctx); 561 | }else if (wd2797_get_drq(&state.fdc_ctx)){ 562 | wd2797_dma_miss(&state.fdc_ctx); 563 | } 564 | 565 | 566 | // Any interrupts? --> TODO: masking 567 | /* if (!lastirq_fdc) { 568 | if (wd2797_get_irq(&state.fdc_ctx)) { 569 | lastirq_fdc = true; 570 | m68k_set_irq(2); 571 | } 572 | } 573 | */ 574 | if (i8274_get_irq(&state.serial_ctx)) { 575 | m68k_set_irq(4); 576 | } else if (keyboard_get_irq(&state.kbd)) { 577 | m68k_set_irq(3); 578 | } else if (wd2797_get_irq(&state.fdc_ctx) || wd2010_get_irq(&state.hdc_ctx)) { 579 | m68k_set_irq(2); 580 | } else { 581 | // if (!state.timer_asserted){ 582 | m68k_set_irq(0); 583 | // } 584 | } 585 | } 586 | // Is it time to run the 60Hz periodic interrupt yet? 587 | if (clock_cycles > CLOCKS_PER_60HZ) { 588 | // Refresh the screen if VRAM has been changed 589 | if (state.vram_updated){ 590 | refreshScreen(screen, renderer, fbTexture); 591 | } 592 | if (state.vram_updated || last_leds != state.leds){ 593 | refreshStatusBar(renderer, lightbarTexture); 594 | last_leds = state.leds; 595 | } 596 | state.vram_updated = false; 597 | SDL_RenderPresent(renderer); 598 | 599 | if (state.timer_enabled){ 600 | m68k_set_irq(6); 601 | state.timer_asserted = true; 602 | } 603 | // scan the keyboard 604 | keyboard_scan(&state.kbd); 605 | // scan the serial pty for new data 606 | i8274_scan_incoming(&state.serial_ctx, CHAN_A); 607 | // decrement clock cycle counter, we've handled the intr. 608 | clock_cycles -= CLOCKS_PER_60HZ; 609 | } 610 | 611 | // handle SDL events -- returns true if we need to exit 612 | if (HandleSDLEvents(window)) 613 | exitEmu = true; 614 | 615 | // make sure frame rate is equal to real time 616 | uint32_t now = SDL_GetTicks(); 617 | if (now < next_timeslot) { 618 | // timeslot finished early -- eat up some time 619 | SDL_Delay(next_timeslot - now); 620 | } else { 621 | // timeslot finished late -- skip ahead to gain time 622 | // TODO: if this happens a lot, we should let the user know 623 | // that their PC might not be fast enough... 624 | next_timeslot = now; 625 | } 626 | // advance to the next timeslot 627 | next_timeslot += MILLISECS_PER_TIMESLOT; 628 | 629 | // if we've been asked to exit the emulator, then do so. 630 | if (exitEmu) break; 631 | } 632 | 633 | // Close the disc images before exiting 634 | wd2797_unload(&state.fdc_ctx); 635 | 636 | if (state.fdc_disc != NULL) { 637 | fclose(state.fdc_disc); 638 | } 639 | 640 | // Clean up SDL 641 | SDL_DestroyTexture(lightbarTexture); 642 | SDL_FreeSurface(screen); 643 | SDL_DestroyTexture(fbTexture); 644 | SDL_DestroyRenderer(renderer); 645 | SDL_DestroyWindow(window); 646 | 647 | // clean up all hardware state 648 | state_done(); 649 | 650 | return 0; 651 | } 652 | -------------------------------------------------------------------------------- /src/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef _MEMORY_H 2 | #define _MEMORY_H 3 | 4 | /*********************************** 5 | * Array read/write utility macros 6 | * "Don't Repeat Yourself" :) 7 | ***********************************/ 8 | 9 | /// Array read, 32-bit 10 | #define RD32(array, address, andmask) \ 11 | (((uint32_t)array[(address + 0) & (andmask)] << 24) | \ 12 | ((uint32_t)array[(address + 1) & (andmask)] << 16) | \ 13 | ((uint32_t)array[(address + 2) & (andmask)] << 8) | \ 14 | ((uint32_t)array[(address + 3) & (andmask)])) 15 | 16 | /// Array read, 16-bit 17 | #define RD16(array, address, andmask) \ 18 | (((uint32_t)array[(address + 0) & (andmask)] << 8) | \ 19 | ((uint32_t)array[(address + 1) & (andmask)])) 20 | 21 | /// Array read, 8-bit 22 | #define RD8(array, address, andmask) \ 23 | ((uint32_t)array[(address + 0) & (andmask)]) 24 | 25 | /// Array write, 32-bit 26 | #define WR32(array, address, andmask, value) do { \ 27 | array[(address + 0) & (andmask)] = (value >> 24) & 0xff; \ 28 | array[(address + 1) & (andmask)] = (value >> 16) & 0xff; \ 29 | array[(address + 2) & (andmask)] = (value >> 8) & 0xff; \ 30 | array[(address + 3) & (andmask)] = value & 0xff; \ 31 | } while (0) 32 | 33 | /// Array write, 16-bit 34 | #define WR16(array, address, andmask, value) do { \ 35 | array[(address + 0) & (andmask)] = (value >> 8) & 0xff; \ 36 | array[(address + 1) & (andmask)] = value & 0xff; \ 37 | } while (0) 38 | 39 | /// Array write, 8-bit 40 | #define WR8(array, address, andmask, value) do { \ 41 | array[(address + 0) & (andmask)] = value & 0xff; \ 42 | } while (0) 43 | 44 | /****************** 45 | * Memory mapping 46 | ******************/ 47 | 48 | typedef enum { 49 | MEM_ALLOWED = 0, 50 | MEM_PAGEFAULT, // Page fault -- page not present 51 | MEM_PAGE_NO_WE, // Page not write enabled 52 | MEM_KERNEL, // User attempted to access kernel memory 53 | MEM_UIE // User Nonmemory Location Access 54 | } MEM_STATUS; 55 | 56 | /** 57 | * @brief Check memory access permissions for a given address. 58 | * @param addr Address. 59 | * @param writing true if writing to memory, false if reading. 60 | * @return One of the MEM_STATUS constants, specifying whether the access is 61 | * permitted, or what error occurred. 62 | */ 63 | MEM_STATUS checkMemoryAccess(uint32_t addr, bool writing, bool dma); 64 | 65 | /** 66 | * @brief Map a CPU memory address into physical memory space. 67 | * @param addr Address. 68 | * @param writing true if writing to memory, false if reading. 69 | * @return Address, remapped into physical memory. 70 | */ 71 | uint32_t mapAddr(uint32_t addr, bool writing); 72 | 73 | /** 74 | * @brief Check access flags for a DMA transfer and trigger an exception if 75 | * the access is not permitted 76 | * @param reading true if reading from memory, false if writing 77 | * @return true if the access is permitted, false if not 78 | */ 79 | bool access_check_dma(int reading); 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/state.c: -------------------------------------------------------------------------------- 1 | #define _STATE_C 2 | #include 3 | #include 4 | #include 5 | #include "wd279x.h" 6 | #include "wd2010.h" 7 | #include "keyboard.h" 8 | #include "state.h" 9 | #include "i8274.h" 10 | #include "fbconfig.h" 11 | 12 | int state_init(size_t base_ram_size, size_t exp_ram_size) 13 | { 14 | // Free RAM if it's allocated 15 | if (state.base_ram != NULL) 16 | free(state.base_ram); 17 | if (state.exp_ram != NULL) 18 | free(state.exp_ram); 19 | 20 | // Initialise hardware registers 21 | state.romlmap = false; 22 | state.idmarw = state.dmaen = false; 23 | state.dma_count = state.dma_address = 0; 24 | state.pie = 0; 25 | state.ee = 0; 26 | state.leds = 0; 27 | state.genstat = 0; // FIXME: check this 28 | state.bsr0 = state.bsr1 = 0; // FIXME: check this 29 | state.timer_enabled = state.timer_asserted = false; 30 | state.dma_dev = DMA_DEV_UNDEF; 31 | state.mcr2mirror = 0; 32 | 33 | // Enable VIDPAL mod (allows user writing to VRAM), per config setting 34 | state.vidpal = fbc_get_bool("vidpal", "installed"); 35 | 36 | // Allocate Base RAM, making sure the user has specified a valid RAM amount first 37 | // Basically: 512KiB minimum, 2MiB maximum, in increments of 512KiB. 38 | if ((base_ram_size < 512*1024) || (base_ram_size > 2048*1024) || ((base_ram_size % (512*1024)) != 0)) 39 | return -1; 40 | state.base_ram = malloc(base_ram_size); 41 | if (state.base_ram == NULL) 42 | return -2; 43 | state.base_ram_size = base_ram_size; 44 | 45 | // Now allocate expansion RAM 46 | // The difference here is that we can have zero bytes of Expansion RAM; we're not limited to having a minimum of 512KiB. 47 | if ((exp_ram_size > 2048*1024) || ((exp_ram_size % (512*1024)) != 0)) 48 | return -1; 49 | state.exp_ram = malloc(exp_ram_size); 50 | if (state.exp_ram == NULL) 51 | return -2; 52 | state.exp_ram_size = exp_ram_size; 53 | 54 | // Load ROMs 55 | const char *rom14c = fbc_get_string("roms", "rom_14c"); 56 | const char *rom15c = fbc_get_string("roms", "rom_15c"); 57 | FILE *r14c, *r15c; 58 | r14c = fopen(rom14c, "rb"); 59 | if (r14c == NULL) { 60 | fprintf(stderr, "[state] Error loading roms/14c.bin.\n"); 61 | return -3; 62 | } 63 | r15c = fopen(rom15c, "rb"); 64 | if (r15c == NULL) { 65 | fprintf(stderr, "[state] Error loading roms/15c.bin.\n"); 66 | return -3; 67 | } 68 | 69 | // get ROM file size 70 | fseek(r14c, 0, SEEK_END); 71 | size_t romlen = ftell(r14c); 72 | fseek(r14c, 0, SEEK_SET); 73 | fseek(r15c, 0, SEEK_END); 74 | size_t romlen2 = ftell(r15c); 75 | fseek(r15c, 0, SEEK_SET); 76 | if (romlen2 != romlen) { 77 | fprintf(stderr, "[state] ROMs are not the same size!\n"); 78 | return -3; 79 | } 80 | if ((romlen + romlen2) > ROM_SIZE) { 81 | fprintf(stderr, "[state] ROM files are too large!\n"); 82 | return -3; 83 | } 84 | 85 | // sanity checks completed; load the ROMs! 86 | uint8_t *romdat1, *romdat2; 87 | romdat1 = malloc(romlen); 88 | romdat2 = malloc(romlen2); 89 | if (fread(romdat1, 1, romlen, r15c) != romlen) { 90 | fprintf(stderr, "[state] Error reading ROM 15C.\n"); 91 | return -3; 92 | } 93 | if (fread(romdat2, 1, romlen2, r14c) != romlen) { 94 | fprintf(stderr, "[state] Error reading ROM 14C.\n"); 95 | return -3; 96 | } 97 | 98 | // convert the ROM data 99 | for (size_t i=0; i<(romlen + romlen2); i+=2) { 100 | state.rom[i+0] = romdat1[i/2]; 101 | state.rom[i+1] = romdat2[i/2]; 102 | } 103 | 104 | // TODO: if ROM buffer not filled, repeat the ROM data we read until it is (wraparound emulation) 105 | 106 | // free the data arrays and close the files 107 | free(romdat1); 108 | free(romdat2); 109 | fclose(r14c); 110 | fclose(r15c); 111 | 112 | // Initialise the disc controller 113 | wd2797_init(&state.fdc_ctx); 114 | // Initialise the keyboard controller 115 | keyboard_init(&state.kbd); 116 | // Initialise the serial controller 117 | i8274_init(&state.serial_ctx); 118 | 119 | return 0; 120 | } 121 | 122 | void state_done() 123 | { 124 | if (state.base_ram != NULL) { 125 | free(state.base_ram); 126 | state.base_ram = NULL; 127 | } 128 | 129 | if (state.exp_ram != NULL) { 130 | free(state.exp_ram); 131 | state.exp_ram = NULL; 132 | } 133 | 134 | // Deinitialise the disc controller 135 | wd2797_done(&state.fdc_ctx); 136 | wd2010_done(&state.hdc_ctx); 137 | // Deinitialise the serial controller 138 | i8274_done(&state.serial_ctx); 139 | } 140 | 141 | 142 | -------------------------------------------------------------------------------- /src/state.h: -------------------------------------------------------------------------------- 1 | #ifndef _STATE_H 2 | #define _STATE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "wd279x.h" 8 | #include "wd2010.h" 9 | #include "keyboard.h" 10 | #include "tc8250.h" 11 | #include "i8274.h" 12 | 13 | 14 | // Maximum size of the Boot PROMs. Must be a binary power of two. 15 | #define ROM_SIZE 32768 16 | 17 | #define DMA_DEV_UNDEF -1 18 | #define DMA_DEV_FD 0 19 | #define DMA_DEV_HD0 1 20 | 21 | /** 22 | * State error codes 23 | */ 24 | typedef enum { 25 | STATE_E_OK = 0, ///< Operation succeeded 26 | STATE_E_BAD_RAMSIZE = -1, ///< Bad RAM size specified (not a multiple of 512K, or less than 512K) 27 | STATE_E_NO_MEMORY = -2, ///< Out of memory while allocating state variables 28 | STATE_E_ROM_LOAD_FAIL = -3 ///< Error loading ROMs 29 | } STATE_ERR; 30 | 31 | /** 32 | * @brief Emulator state storage 33 | * 34 | * This structure stores the internal state of the emulator. 35 | */ 36 | typedef struct { 37 | // Boot PROM can be up to 32Kbytes total size 38 | uint8_t rom[ROM_SIZE]; ///< Boot PROM data buffer 39 | 40 | //// Main system RAM 41 | uint8_t *base_ram; ///< Base RAM data buffer 42 | size_t base_ram_size; ///< Size of Base RAM buffer in bytes 43 | uint8_t *exp_ram; ///< Expansion RAM data buffer 44 | size_t exp_ram_size; ///< Size of Expansion RAM buffer in bytes 45 | 46 | /// Video RAM 47 | uint8_t vram[0x8000]; 48 | 49 | /// Map RAM 50 | uint8_t map[0x800]; 51 | 52 | //// Registers 53 | uint16_t genstat; ///< General Status Register 54 | uint16_t bsr0; ///< Bus Status Register 0 55 | uint16_t bsr1; ///< Bus Status Register 1 56 | 57 | //// MISCELLANEOUS CONTROL REGISTER 58 | bool dma_reading; ///< True if Disc DMA reads from the controller, false otherwise 59 | uint8_t leds; ///< LED status, 1=on, in order red3/green2/yellow1/red0 from bit3 to bit0 60 | 61 | bool timer_enabled; 62 | bool timer_asserted; 63 | 64 | //// GENERAL CONTROL REGISTER 65 | /// GENCON.ROMLMAP -- false ORs the address with 0x800000, forcing the 66 | /// 68010 to access ROM instead of RAM when booting. TRM page 2-36. 67 | bool romlmap; 68 | /// GENCON.PIE -- Parity Error Check Enable 69 | bool pie; 70 | /// GENCON.EE -- Error Enable 71 | bool ee; 72 | 73 | /// DMA Address Register 74 | uint32_t dma_address; 75 | 76 | /// DMA count 77 | uint32_t dma_count; 78 | 79 | /// DMA direction 80 | bool idmarw; 81 | /// DMA enable 82 | bool dmaen; 83 | 84 | /// DMA device selection flags 85 | bool fd_selected; 86 | bool hd_selected; 87 | 88 | int dma_dev; 89 | /// Floppy disc controller context 90 | WD2797_CTX fdc_ctx; 91 | /// Current disc image file 92 | FILE *fdc_disc; 93 | 94 | /// Hard disc controller context 95 | WD2010_CTX hdc_ctx; 96 | FILE *hdc_disc0; 97 | FILE *hdc_disc1; 98 | 99 | /// Keyboard controller context 100 | KEYBOARD_STATE kbd; 101 | 102 | /// Real time clock context 103 | TC8250_CTX rtc_ctx; 104 | 105 | /// Serial controller context 106 | I8274_CTX serial_ctx; 107 | 108 | /// VIDPAL mod (allows user writing to VRAM) 109 | bool vidpal; 110 | 111 | /// Update screen only when VRAM has been changed 112 | bool vram_updated; 113 | 114 | /// MCR2 mirror bit for P5.1 hardware detection 115 | bool mcr2mirror; 116 | } S_state; 117 | 118 | // Global emulator state. Yes, I know global variables are evil, please don't 119 | // email me and lecture me about it. -philpem 120 | #ifndef _STATE_C 121 | extern S_state state; 122 | #else 123 | S_state state; 124 | #endif 125 | 126 | /** 127 | * @brief Initialise system state 128 | * 129 | * @param base_ram_size Base RAM size in bytes -- must be a multiple of 512KiB, min 512KiB, max 2MiB. 130 | * @param exp_ram_size Expansion RAM size in bytes -- must be a multiple of 512KiB, min 0, max 2MiB. 131 | * 132 | * Initialises the emulator's internal state. 133 | */ 134 | int state_init(size_t base_ram_size, size_t exp_ram_size); 135 | 136 | /** 137 | * @brief Deinitialise system state 138 | * 139 | * Deinitialises the saved state, and frees all memory. Call this function 140 | * before exiting your program to avoid memory leaks. 141 | */ 142 | void state_done(); 143 | 144 | #endif 145 | -------------------------------------------------------------------------------- /src/tc8250.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "tc8250.h" 6 | 7 | #ifndef TC8250_DEBUG 8 | #define NDEBUG 9 | #endif 10 | #include "utils.h" 11 | 12 | void tc8250_init(TC8250_CTX *ctx) 13 | { 14 | ctx->chip_enable = false; 15 | ctx->address_latch_enable = false; 16 | ctx->write_enable = false; 17 | ctx->address = 0; 18 | } 19 | 20 | void tc8250_set_chip_enable(TC8250_CTX *ctx, bool enabled) 21 | { 22 | LOG("tc8250_set_chip_enable %d\n", enabled); 23 | ctx->chip_enable = enabled; 24 | } 25 | 26 | void tc8250_set_address_latch_enable(TC8250_CTX *ctx, bool enabled) 27 | { 28 | LOG("tc8250_set_address_latch_enable %d\n", enabled); 29 | ctx->address_latch_enable = enabled; 30 | } 31 | 32 | void tc8250_set_write_enable(TC8250_CTX *ctx, bool enabled) 33 | { 34 | LOG("tc8250_set_write_enable %d\n", enabled); 35 | ctx->write_enable = enabled; 36 | } 37 | 38 | uint8_t get_second(TC8250_CTX *ctx) 39 | { 40 | time_t t; 41 | struct tm g; 42 | uint8_t ret; 43 | t = time(NULL); 44 | localtime_r(&t, &g); 45 | ret = g.tm_sec; 46 | return (ret); 47 | } 48 | 49 | uint8_t get_minute(TC8250_CTX *ctx) 50 | { 51 | time_t t; 52 | struct tm g; 53 | uint8_t ret; 54 | t = time(NULL); 55 | localtime_r(&t, &g); 56 | ret = g.tm_min; 57 | return (ret); 58 | } 59 | 60 | uint8_t get_hour(TC8250_CTX *ctx) 61 | { 62 | time_t t; 63 | struct tm g; 64 | uint8_t ret; 65 | t = time(NULL); 66 | localtime_r(&t, &g); 67 | ret = g.tm_hour; 68 | return (ret); 69 | } 70 | 71 | uint8_t get_day(TC8250_CTX *ctx) 72 | { 73 | time_t t; 74 | struct tm g; 75 | uint8_t ret; 76 | t = time(NULL); 77 | localtime_r(&t, &g); 78 | ret = g.tm_mday; 79 | return (ret); 80 | } 81 | 82 | uint8_t get_month(TC8250_CTX *ctx) 83 | { 84 | time_t t; 85 | struct tm g; 86 | uint8_t ret; 87 | t = time(NULL); 88 | localtime_r(&t, &g); 89 | ret = g.tm_mon+1; 90 | return (ret); 91 | } 92 | 93 | uint8_t get_year(TC8250_CTX *ctx) 94 | { 95 | /*time_t t; 96 | struct tm g; 97 | uint8_t ret; 98 | t = time(NULL); 99 | localtime_r(&t, &g); 100 | ret = g.tm_year; 101 | return (ret);*/ 102 | return (87); 103 | } 104 | 105 | uint8_t get_weekday(TC8250_CTX *ctx) 106 | { 107 | time_t t; 108 | struct tm g; 109 | uint8_t ret; 110 | t = time(NULL); 111 | localtime_r(&t, &g); 112 | ret = g.tm_wday; 113 | return (ret); 114 | } 115 | 116 | uint8_t tc8250_read_reg(TC8250_CTX *ctx) 117 | { 118 | LOG("tc8250_read_reg %x\n", ctx->address); 119 | switch (ctx->address){ 120 | case ONE_SEC_DIGT: 121 | return (get_second(ctx) % 10); 122 | case TEN_SEC_DIGT: 123 | return (get_second(ctx) / 10); 124 | case ONE_MIN_DIGT: 125 | return (get_minute(ctx) % 10); 126 | case TEN_MIN_DIGT: 127 | return (get_minute(ctx) / 10); 128 | case ONE_HR_DIGT: 129 | return (get_hour(ctx) % 10); 130 | case TEN_HR_DIGT: 131 | return (get_hour(ctx) / 10); 132 | case ONE_DAY_DIGT: 133 | return (get_day(ctx) % 10); 134 | case TEN_DAY_DIGT: 135 | return (get_day(ctx) / 10); 136 | case ONE_MNTH_DIGT: 137 | return (get_month(ctx) % 10); 138 | case TEN_MNTH_DIGT: 139 | return (get_month(ctx) / 10); 140 | case ONE_YR_DIGT: 141 | return (get_year(ctx) % 10); 142 | case TEN_YR_DIGT: 143 | return (get_year(ctx) / 10); 144 | case WEEK_DAY: 145 | return (get_weekday(ctx)); 146 | case TOUT_CONTROL: 147 | return (0); 148 | case PROTECT_KEY: 149 | return (0); 150 | case RTC_STATUS: 151 | return (0); 152 | default: 153 | return (0); 154 | } 155 | } 156 | 157 | void set_seconds(TC8250_CTX *ctx, uint8_t val) 158 | { 159 | } 160 | 161 | void set_minutes(TC8250_CTX *ctx, uint8_t val) 162 | { 163 | } 164 | 165 | void set_hours(TC8250_CTX *ctx, uint8_t val) 166 | { 167 | } 168 | 169 | void set_days(TC8250_CTX *ctx, uint8_t val) 170 | { 171 | } 172 | 173 | void set_months(TC8250_CTX *ctx, uint8_t val) 174 | { 175 | } 176 | 177 | void set_years(TC8250_CTX *ctx, uint8_t val) 178 | { 179 | } 180 | 181 | void set_weekday(TC8250_CTX *ctx, uint8_t val) 182 | { 183 | } 184 | 185 | void tc8250_write_reg(TC8250_CTX *ctx, uint8_t val) 186 | { 187 | LOG("tc8250_write_reg %x", val); 188 | if (ctx->address_latch_enable){ 189 | LOG(" address\n"); 190 | ctx->address = val; 191 | return; 192 | } 193 | if (ctx->chip_enable){ 194 | LOG(" %x\n", ctx->address); 195 | switch (ctx->address){ 196 | case ONE_SEC_DIGT: 197 | set_seconds(ctx, val % 10); 198 | break; 199 | case TEN_SEC_DIGT: 200 | set_seconds(ctx, val % 10 * 10); 201 | break; 202 | case ONE_MIN_DIGT: 203 | set_minutes(ctx, val % 10); 204 | break; 205 | case TEN_MIN_DIGT: 206 | set_minutes(ctx, val % 10 * 10); 207 | break; 208 | case ONE_HR_DIGT: 209 | set_hours(ctx, val % 10); 210 | break; 211 | case TEN_HR_DIGT: 212 | set_hours(ctx, val % 10 * 10); 213 | break; 214 | case ONE_DAY_DIGT: 215 | set_days(ctx, val % 10); 216 | break; 217 | case TEN_DAY_DIGT: 218 | set_days(ctx, val % 10 * 10); 219 | break; 220 | case ONE_MNTH_DIGT: 221 | set_months(ctx, val % 10); 222 | break; 223 | case TEN_MNTH_DIGT: 224 | set_months(ctx, val % 10 * 10); 225 | break; 226 | case ONE_YR_DIGT: 227 | set_years(ctx, val % 10); 228 | break; 229 | case TEN_YR_DIGT: 230 | set_years(ctx, val % 10 * 10); 231 | break; 232 | case WEEK_DAY: 233 | set_weekday(ctx, val % 10); 234 | break; 235 | case TOUT_CONTROL: 236 | break; 237 | case PROTECT_KEY: 238 | break; 239 | case RTC_STATUS: 240 | break; 241 | default: 242 | break; 243 | } 244 | }else{ 245 | LOG("\n"); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/tc8250.h: -------------------------------------------------------------------------------- 1 | #ifndef _TC8250_H 2 | #define _TC8250_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct { 10 | bool chip_enable; 11 | bool address_latch_enable; 12 | bool write_enable; 13 | uint8_t address; 14 | uint8_t seconds_offset; 15 | uint8_t minutes_offset; 16 | uint8_t hours_offset; 17 | uint8_t days_offset; 18 | uint8_t months_offset; 19 | uint8_t years_offset; 20 | uint8_t weekday_offset; 21 | } TC8250_CTX; 22 | 23 | void tc8250_init(TC8250_CTX *ctx); 24 | void tc8250_set_chip_enable(TC8250_CTX *ctx, bool enabled); 25 | void tc8250_set_address_latch_enable(TC8250_CTX *ctx, bool enabled); 26 | void tc8250_set_write_enable(TC8250_CTX *ctx, bool enabled); 27 | uint8_t tc8250_read_reg(TC8250_CTX *ctx); 28 | void tc8250_write_reg(TC8250_CTX *ctx, uint8_t val); 29 | 30 | enum { 31 | ONE_SEC_DIGT = 0x0, /* 1 sec digit */ 32 | TEN_SEC_DIGT = 0x1, /* 10 sec digit */ 33 | ONE_MIN_DIGT = 0x2, /* 1 minute digit */ 34 | TEN_MIN_DIGT = 0x3, /* 10 minutes digit */ 35 | ONE_HR_DIGT = 0x4, /* 1 hour digit */ 36 | TEN_HR_DIGT = 0x5, /* 10 hours digit */ 37 | ONE_DAY_DIGT = 0x6, /* 1 day digit */ 38 | TEN_DAY_DIGT = 0x7, /* 10 days digit */ 39 | ONE_MNTH_DIGT = 0x8, /* 1 month digit */ 40 | TEN_MNTH_DIGT = 0x9, /* 10 month digit */ 41 | ONE_YR_DIGT = 0xa, /* 1 year digit */ 42 | TEN_YR_DIGT = 0xb, /* 10 year digit */ 43 | WEEK_DAY = 0xc, /* day of the week */ 44 | TOUT_CONTROL = 0xd, /* Tout control */ 45 | PROTECT_KEY = 0xe, /* protection key */ 46 | RTC_STATUS = 0xf /* real time clock status */ 47 | }; 48 | #endif 49 | -------------------------------------------------------------------------------- /src/toml.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 - 2019 CK Tan 5 | https://github.com/cktan/tomlc99 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | #ifndef TOML_H 26 | #define TOML_H 27 | 28 | 29 | #include 30 | #include 31 | 32 | 33 | #ifdef __cplusplus 34 | #define TOML_EXTERN extern "C" 35 | #else 36 | #define TOML_EXTERN extern 37 | #endif 38 | 39 | typedef struct toml_timestamp_t toml_timestamp_t; 40 | typedef struct toml_table_t toml_table_t; 41 | typedef struct toml_array_t toml_array_t; 42 | typedef struct toml_datum_t toml_datum_t; 43 | 44 | /* Parse a file. Return a table on success, or 0 otherwise. 45 | * Caller must toml_free(the-return-value) after use. 46 | */ 47 | TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp, 48 | char* errbuf, 49 | int errbufsz); 50 | 51 | /* Parse a string containing the full config. 52 | * Return a table on success, or 0 otherwise. 53 | * Caller must toml_free(the-return-value) after use. 54 | */ 55 | TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */ 56 | char* errbuf, 57 | int errbufsz); 58 | 59 | /* Free the table returned by toml_parse() or toml_parse_file(). Once 60 | * this function is called, any handles accessed through this tab 61 | * directly or indirectly are no longer valid. 62 | */ 63 | TOML_EXTERN void toml_free(toml_table_t* tab); 64 | 65 | 66 | /* Timestamp types. The year, month, day, hour, minute, second, z 67 | * fields may be NULL if they are not relevant. e.g. In a DATE 68 | * type, the hour, minute, second and z fields will be NULLs. 69 | */ 70 | struct toml_timestamp_t { 71 | struct { /* internal. do not use. */ 72 | int year, month, day; 73 | int hour, minute, second, millisec; 74 | char z[10]; 75 | } __buffer; 76 | int *year, *month, *day; 77 | int *hour, *minute, *second, *millisec; 78 | char* z; 79 | }; 80 | 81 | 82 | /*----------------------------------------------------------------- 83 | * Enhanced access methods 84 | */ 85 | struct toml_datum_t { 86 | int ok; 87 | union { 88 | toml_timestamp_t* ts; /* ts must be freed after use */ 89 | char* s; /* string value. s must be freed after use */ 90 | int b; /* bool value */ 91 | int64_t i; /* int value */ 92 | double d; /* double value */ 93 | } u; 94 | }; 95 | 96 | /* on arrays: */ 97 | /* ... retrieve size of array. */ 98 | TOML_EXTERN int toml_array_nelem(const toml_array_t* arr); 99 | /* ... retrieve values using index. */ 100 | TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx); 101 | TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx); 102 | TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx); 103 | TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx); 104 | TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx); 105 | /* ... retrieve array or table using index. */ 106 | TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx); 107 | TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx); 108 | 109 | /* on tables: */ 110 | /* ... retrieve the key in table at keyidx. Return 0 if out of range. */ 111 | TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx); 112 | /* ... retrieve values using key. */ 113 | TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key); 114 | TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key); 115 | TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key); 116 | TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key); 117 | TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key); 118 | /* .. retrieve array or table using key. */ 119 | TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab, 120 | const char* key); 121 | TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab, 122 | const char* key); 123 | 124 | /*----------------------------------------------------------------- 125 | * lesser used 126 | */ 127 | /* Return the array kind: 't'able, 'a'rray, 'v'alue */ 128 | TOML_EXTERN char toml_array_kind(const toml_array_t* arr); 129 | 130 | /* For array kind 'v'alue, return the type of values 131 | i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp 132 | 0 if unknown 133 | */ 134 | TOML_EXTERN char toml_array_type(const toml_array_t* arr); 135 | 136 | /* Return the key of an array */ 137 | TOML_EXTERN const char* toml_array_key(const toml_array_t* arr); 138 | 139 | /* Return the number of key-values in a table */ 140 | TOML_EXTERN int toml_table_nkval(const toml_table_t* tab); 141 | 142 | /* Return the number of arrays in a table */ 143 | TOML_EXTERN int toml_table_narr(const toml_table_t* tab); 144 | 145 | /* Return the number of sub-tables in a table */ 146 | TOML_EXTERN int toml_table_ntab(const toml_table_t* tab); 147 | 148 | /* Return the key of a table*/ 149 | TOML_EXTERN const char* toml_table_key(const toml_table_t* tab); 150 | 151 | /*-------------------------------------------------------------- 152 | * misc 153 | */ 154 | TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret); 155 | TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]); 156 | TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t), 157 | void (*xxfree)(void*)); 158 | 159 | 160 | /*-------------------------------------------------------------- 161 | * deprecated 162 | */ 163 | /* A raw value, must be processed by toml_rto* before using. */ 164 | typedef const char* toml_raw_t; 165 | TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key); 166 | TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx); 167 | TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret); 168 | TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret); 169 | TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret); 170 | TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret); 171 | TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen); 172 | TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret); 173 | 174 | 175 | #endif /* TOML_H */ 176 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _UTILS_H 2 | #define _UTILS_H 3 | 4 | #include 5 | 6 | #ifndef NDEBUG 7 | /// Log a message to stderr 8 | # define LOG(x, ...) do { fprintf(stderr, "%s:%d:%s(): " x "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__); } while (0) 9 | # define LOGS(x) do { fprintf(stderr, "%s:%d:%s(): " x "\n", __FILE__, __LINE__, __func__); } while (0) 10 | /// Log a message to stderr if 'cond' is true 11 | # define LOG_IF(cond, x, ...) do { if (cond) fprintf(stderr, "%s:%d:%s(): " x "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__); } while (0) 12 | # define LOG_IFS(cond, x) do { if (cond) fprintf(stderr, "%s:%d:%s(): " x "\n", __FILE__, __LINE__, __func__); } while (0) 13 | #else 14 | #define LOG(x, ...) 15 | #define LOGS(x) 16 | #define LOG_IF(cond, x, ...) 17 | #define LOG_IFS(cond, x) 18 | #endif 19 | 20 | /// Get the number of elements in an array 21 | #define NELEMS(x) (sizeof(x)/sizeof(x[0])) 22 | 23 | #endif // _H_UTILS 24 | -------------------------------------------------------------------------------- /src/version.h.in: -------------------------------------------------------------------------------- 1 | #define VER_COMPILE_DATETIME "@@datetime@@" 2 | #define VER_COMPILE_DATE "@@date@@" 3 | #define VER_COMPILE_TIME "@@time@@" 4 | #define VER_BUILD_TYPE "@@buildtype@@" 5 | 6 | #define VER_MAJOR @@majorver@@ 7 | #define VER_MINOR @@minorver@@ 8 | #define VER_BUILDNUM @@buildnum@@ 9 | #define VER_EXTRA "@@extraver@@" 10 | #define VER_VCSREV "@@vcsstr@@" 11 | 12 | #define VER_FULLSTR "@@fullverstr@@" 13 | 14 | -------------------------------------------------------------------------------- /src/wd2010.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "SDL.h" 8 | #include "musashi/m68k.h" 9 | #include "wd2010.h" 10 | 11 | //#define WD2010_DEBUG 12 | 13 | #ifndef WD2010_DEBUG 14 | #define NDEBUG 15 | #endif 16 | #include "utils.h" 17 | 18 | #ifndef WD2010_SEEK_DELAY 19 | #define WD2010_SEEK_DELAY 30 20 | #endif 21 | 22 | #define CMD_ENABLE_RETRY 0x01 23 | #define CMD_LONG_MODE 0x02 24 | #define CMD_MULTI_SECTOR 0x04 25 | #define CMD_INTRQ_WHEN_COMPLETE 0x08 26 | 27 | #define ER_BAD_BLOCK 0x80 28 | #define ER_CRC 0x40 29 | #define ER_ID_NOT_FOUND 0x10 30 | #define ER_ABORTED_COMMAND 0x04 31 | #define ER_NO_TK0 0x02 32 | #define ER_NO_ADDRESS_MARK 0x01 33 | 34 | #define SR_BUSY 0x80 35 | #define SR_READY 0x40 36 | #define SR_WRITE_FAULT 0x20 37 | #define SR_SEEK_COMPLETE 0x10 38 | #define SR_DRQ 0x08 39 | #define SR_CORRECTED 0x04 40 | #define SR_COMMAND_IN_PROGRESS 0x02 41 | #define SR_ERROR 0x01 42 | 43 | // Cylinder high mask. 44 | // 3.51m Kernel uses the width of Cylinder High to identify whether the controller 45 | // is WD1010 or WD2010. 46 | #ifdef EMULATE_WD1010 47 | # define CYLH_MASK 0x03 48 | #else 49 | # define CYLH_MASK 0x07 50 | #endif 51 | 52 | extern int cpu_log_enabled; 53 | static int wd2010_default_init(WD2010_CTX *ctx, FILE *fp, int drivenum, int secsz, int spt, int heads); 54 | static int wd2010_disk_label_init(WD2010_CTX *ctx, FILE *fp, int drivenum); 55 | static int wd2010_pre_label_init(WD2010_CTX *ctx, FILE *fp, int drivenum); 56 | 57 | /// WD2010 command constants 58 | enum { 59 | CMD_MASK = 0xF0, ///< Bit mask to detect command bits 60 | CMD_2010_EXT = 0x00, ///< WD2010 extended commands (compute correction, set parameter) 61 | CMD_RESTORE = 0x10, ///< Restore (recalibrate, seek to track 0) 62 | CMD_READ_SECTOR = 0x20, ///< Read sector 63 | CMD_WRITE_SECTOR = 0x30, ///< Write sector 64 | CMD_SCAN_ID = 0x40, ///< Scan ID 65 | CMD_WRITE_FORMAT = 0x50, ///< Write format 66 | CMD_SEEK = 0x70, ///< Seek to given track 67 | }; 68 | 69 | 70 | static int wd2010_default_init(WD2010_CTX *ctx, FILE *fp, int drivenum, int secsz, int spt, int heads) 71 | { 72 | size_t filesize; 73 | 74 | // Start by finding out how big the image file is 75 | fseek(fp, 0, SEEK_END); 76 | filesize = ftell(fp); 77 | fseek(fp, 0, SEEK_SET); 78 | 79 | // Now figure out how many tracks it contains 80 | unsigned int tracks = filesize / secsz / spt / heads; 81 | // Confirm... 82 | if (tracks < 1 || tracks > 1400) { 83 | if (tracks > 1400) { 84 | fprintf(stderr, "ERROR hard disk %d: cylinders > 1400 unsupported by UNIX.\n", drivenum); 85 | } 86 | return WD2010_ERR_BAD_GEOM; 87 | } 88 | 89 | drivenum = drivenum ? 1 : 0; // force to 1 or 0 90 | // Load the geometry data 91 | ctx->geometry[drivenum].tracks = tracks; 92 | ctx->geometry[drivenum].secsz = secsz; 93 | ctx->geometry[drivenum].heads = heads; 94 | ctx->geometry[drivenum].spt = spt; 95 | 96 | return WD2010_ERR_OK; 97 | } 98 | 99 | static int wd2010_disk_label_init(WD2010_CTX *ctx, FILE *fp, int drivenum) 100 | { 101 | ssize_t count; 102 | /* 103 | * As seen in the s4 utils, the UNIX PC was ahead of most of its 104 | * contemporaries, sporting a disk label describing the disk's geometry. 105 | * We read that label and pull the interestings bits out of it. 106 | */ 107 | struct s4_dswprt { 108 | char magic[4]; /* magic number */ 109 | int32_t checksum; 110 | char name[6]; /* name, sort of */ 111 | uint16_t cyls; /* the number of cylinders for this disk */ 112 | uint16_t heads; /* number of heads per cylinder */ 113 | uint16_t psectrk; /* number of physical sectors per track */ 114 | uint16_t pseccyl; /* number of physical sectors per cylinder */ 115 | char flags; /* floppy density and high tech drive flags */ 116 | char step; /* stepper motor rate to controller */ 117 | uint16_t sectorsz; /* physical sector size in bytes */ 118 | } __attribute__((__packed__)); 119 | struct s4_dswprt disk_label; 120 | 121 | (void) fseek(fp, 0L, SEEK_SET); 122 | if ((count = fread(& disk_label, 1, sizeof(disk_label), fp)) != sizeof(disk_label)) { 123 | fprintf(stderr, "I/O error reading disk image: %s\n", strerror(errno)); 124 | return WD2010_ERR_IO_ERROR; 125 | } 126 | (void) fseek(fp, 0L, SEEK_SET); 127 | 128 | drivenum = drivenum ? 1 : 0; // force to 1 or 0 129 | // convert big endian data to native data 130 | ctx->geometry[drivenum].tracks = ntohs(disk_label.cyls); 131 | ctx->geometry[drivenum].secsz = ntohs(disk_label.sectorsz); 132 | ctx->geometry[drivenum].heads = ntohs(disk_label.heads); 133 | ctx->geometry[drivenum].spt = ntohs(disk_label.psectrk); 134 | 135 | return WD2010_ERR_OK; 136 | } 137 | 138 | static int wd2010_pre_label_init(WD2010_CTX *ctx, FILE *fp, int drivenum) 139 | { 140 | int numheads, numcyls, blocks_per_track, block_size; 141 | int count; 142 | char buffer[BUFSIZ]; 143 | 144 | (void) fseek(fp, 0L, SEEK_SET); 145 | (void) fgets(buffer, sizeof(buffer), fp); // skip magic 146 | 147 | if (fgets(buffer, sizeof(buffer), fp) == NULL) 148 | return WD2010_ERR_IO_ERROR; 149 | 150 | count = sscanf(buffer, "heads: %d cyls: %d bpt: %d blksiz: %d", 151 | & numheads, & numcyls, & blocks_per_track, & block_size); 152 | if (count != 4) 153 | return WD2010_ERR_BAD_GEOM; 154 | 155 | (void) fseek(fp, 0L, SEEK_SET); 156 | 157 | drivenum = drivenum ? 1 : 0; // force to 1 or 0 158 | ctx->geometry[drivenum].tracks = numcyls; 159 | ctx->geometry[drivenum].secsz = block_size; 160 | ctx->geometry[drivenum].heads = numheads; 161 | ctx->geometry[drivenum].spt = blocks_per_track; 162 | 163 | return WD2010_ERR_OK; 164 | } 165 | 166 | 167 | int wd2010_init(WD2010_CTX *ctx, FILE *fp, int drivenum, int secsz, int spt, int heads) 168 | { 169 | int result; 170 | char magic[4]; 171 | 172 | wd2010_reset(ctx); 173 | 174 | // read first 4 bytes 175 | // if UNIX PC magic, get real geometry 176 | // else if early magic, get user-specified geometry 177 | // else do default settings 178 | (void) fseek(fp, 0L, SEEK_SET); 179 | if (fread(magic, 1, 4, fp) != 4) 180 | return WD2010_ERR_IO_ERROR; 181 | 182 | if (strncmp(magic, "UQVQ", 4) == 0) { 183 | result = wd2010_disk_label_init(ctx, fp, drivenum); 184 | } else if (strncmp(magic, "free", 4) == 0) { 185 | result = wd2010_pre_label_init(ctx, fp, drivenum); 186 | } else { 187 | result = wd2010_default_init(ctx, fp, drivenum, secsz, spt, heads); 188 | } 189 | 190 | if (result != WD2010_ERR_OK) return result; 191 | 192 | drivenum = drivenum ? 1 : 0; // force to 1 or 0 193 | printf("Drive %d initialised: %d cylinders, %d heads, %d sectors per track\n", drivenum, 194 | ctx->geometry[drivenum].tracks, ctx->geometry[drivenum].heads, 195 | ctx->geometry[drivenum].spt); 196 | 197 | // Allocate enough memory to store one disc track 198 | if (ctx->data[drivenum]) { 199 | free(ctx->data[drivenum]); 200 | } 201 | ctx->data[drivenum] = malloc(ctx->geometry[drivenum].secsz * ctx->geometry[drivenum].spt); 202 | if (!ctx->data[drivenum]) 203 | return WD2010_ERR_NO_MEMORY; 204 | 205 | (void) fseek(fp, 0L, SEEK_SET); 206 | ctx->disc_image[drivenum] = fp; 207 | 208 | return result; 209 | } 210 | 211 | 212 | void wd2010_reset(WD2010_CTX *ctx) 213 | { 214 | // track, head and sector unknown 215 | ctx->track = ctx->head = ctx->sector = 0; 216 | 217 | // no IRQ pending 218 | ctx->irq = false; 219 | 220 | // no data available 221 | ctx->data_pos = ctx->data_len = 0; 222 | 223 | // Status register clear, not busy 224 | ctx->status = 0; 225 | 226 | ctx->sector_count = 0; 227 | ctx->sector_number = 0; 228 | ctx->cylinder_low_reg = 0; 229 | ctx->cylinder_high_reg = 0; 230 | ctx->sdh = 0; 231 | ctx->mcr2_hdsel3 = 0; 232 | ctx->mcr2_ddrive1 = 0; 233 | } 234 | 235 | void wd2010_done(WD2010_CTX *ctx) 236 | { 237 | int i; 238 | 239 | // Reset the WD2010 240 | wd2010_reset(ctx); 241 | 242 | // Free any allocated memory 243 | for (i = 0; i < 2; i++) { 244 | if (ctx->data[i]) { 245 | free(ctx->data[i]); 246 | ctx->data[i] = NULL; 247 | } 248 | } 249 | } 250 | 251 | 252 | bool wd2010_get_irq(WD2010_CTX *ctx) 253 | { 254 | return ctx->irq; 255 | } 256 | 257 | bool wd2010_get_drq(WD2010_CTX *ctx) 258 | { 259 | return (ctx->drq && ctx->data_pos < ctx->data_len); 260 | } 261 | 262 | void wd2010_dma_miss(WD2010_CTX *ctx) 263 | { 264 | ctx->data_pos = ctx->data_len; 265 | ctx->write_pos = 0; 266 | ctx->status = SR_READY | SR_SEEK_COMPLETE; 267 | ctx->irq = true; 268 | } 269 | 270 | uint8_t wd2010_read_data(WD2010_CTX *ctx) 271 | { 272 | // If there's data in the buffer, return it. Otherwise return 0xFF. 273 | if (ctx->data_pos < ctx->data_len) { 274 | if (ctx->multi_sector && (ctx->data_pos > 0) && ((ctx->data_pos % ctx->geometry[ctx->mcr2_ddrive1].secsz) == 0)){ 275 | ctx->sector_count--; 276 | ctx->sector_number++; 277 | } 278 | // set IRQ if this is the last data byte 279 | if (ctx->data_pos == (ctx->data_len-1)) { 280 | ctx->status = SR_READY | SR_SEEK_COMPLETE; 281 | // Set IRQ 282 | ctx->irq = true; 283 | ctx->drq = false; 284 | LOG("WD2010: read done"); 285 | } 286 | // return data byte and increment pointer 287 | return ctx->data[ctx->mcr2_ddrive1][ctx->data_pos++]; 288 | } else { 289 | // empty buffer (this shouldn't happen) 290 | LOGS("WD2010: attempt to read from empty data buffer"); 291 | return 0xff; 292 | } 293 | } 294 | 295 | void wd2010_write_data(WD2010_CTX *ctx, uint8_t val) 296 | { 297 | // If we're processing a write command, and there's space in the 298 | // buffer, allow the write. 299 | if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) { 300 | // store data byte and increment pointer 301 | if (ctx->multi_sector && (ctx->data_pos > 0) && ((ctx->data_pos % ctx->geometry[ctx->mcr2_ddrive1].secsz) == 0)){ 302 | ctx->sector_count--; 303 | ctx->sector_number++; 304 | } 305 | ctx->data[ctx->mcr2_ddrive1][ctx->data_pos++] = val; 306 | // set IRQ and write data if this is the last data byte 307 | if (ctx->data_pos == ctx->data_len) { 308 | if (!ctx->formatting){ 309 | fseek(ctx->disc_image[ctx->mcr2_ddrive1], ctx->write_pos, SEEK_SET); 310 | fwrite(ctx->data[ctx->mcr2_ddrive1], 1, ctx->data_len, ctx->disc_image[ctx->mcr2_ddrive1]); 311 | fflush(ctx->disc_image[ctx->mcr2_ddrive1]); 312 | } 313 | ctx->formatting = false; 314 | ctx->status = SR_READY | SR_SEEK_COMPLETE; 315 | // Set IRQ and reset write pointer 316 | ctx->irq = true; 317 | ctx->write_pos = -1; 318 | ctx->drq = false; 319 | LOG("WD2010: write done"); 320 | } 321 | }else{ 322 | LOGS("WD2010: attempt to write to data buffer without a write command in progress"); 323 | } 324 | } 325 | 326 | uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx) 327 | { 328 | /*m68k_end_timeslice();*/ 329 | ctx->status = SR_READY | SR_SEEK_COMPLETE; 330 | ctx->irq = true; 331 | return (0); 332 | } 333 | 334 | uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx) 335 | { 336 | /*m68k_end_timeslice();*/ 337 | ctx->drq = true; 338 | return (0); 339 | } 340 | 341 | uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr) 342 | { 343 | uint8_t temp = 0; 344 | 345 | /*cpu_log_enabled = 1;*/ 346 | 347 | switch (addr & 0x07) { 348 | case WD2010_REG_ERROR: 349 | return ctx->error_reg; 350 | case WD2010_REG_SECTOR_COUNT: 351 | return ctx->sector_count; 352 | case WD2010_REG_SECTOR_NUMBER: 353 | return ctx->sector_number; 354 | case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder 355 | return ctx->cylinder_high_reg & CYLH_MASK; 356 | case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder 357 | return ctx->cylinder_low_reg; 358 | case WD2010_REG_SDH: 359 | return ctx->sdh; 360 | case WD2010_REG_STATUS: // Status register 361 | // Read from status register clears IRQ 362 | ctx->irq = false; 363 | // Get current status flags (set by last command) 364 | // DRQ bit 365 | if (ctx->cmd_has_drq) { 366 | temp = ctx->status & ~(SR_BUSY & SR_DRQ); 367 | temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0; 368 | LOG("\tWDFDC rd sr, has drq, pos=%zu len=%zu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp); 369 | } else { 370 | temp = ctx->status & ~0x80; 371 | } 372 | /*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/ 373 | // HDC is busy if there is still data in the buffer 374 | temp |= (ctx->data_pos < ctx->data_len) ? SR_BUSY : 0; // if data in buffer, then DMA hasn't copied it yet, and we're still busy! 375 | // TODO: also if seek delay / read delay hasn't passed (but that's for later) 376 | /*XXX: should anything else be set here?*/ 377 | return temp; 378 | default: 379 | // shut up annoying compilers which don't recognise unreachable code when they see it 380 | // (here's looking at you, gcc!) 381 | return 0xff; 382 | } 383 | } 384 | 385 | 386 | void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val) 387 | { 388 | uint8_t cmd = val & CMD_MASK; 389 | size_t lba; 390 | int new_track; 391 | int sector_count; 392 | 393 | m68k_end_timeslice(); 394 | 395 | /*cpu_log_enabled = 1;*/ 396 | 397 | if (addr == UNIXPC_REG_MCR2) { 398 | // The UNIX PC has an "MCR2" register with the following format: 399 | // [ 7..2 ][1][0] 400 | // Bits 7..2: Not used 401 | // Bit 1: DDRIVE1 (hard disk drive 1 select - not used?) 402 | // Bit 0: HDSEL3 (head-select bit 3) 403 | ctx->mcr2_hdsel3 = ((val & 1) == 1); 404 | ctx->mcr2_ddrive1 = ((val & 2) == 2); 405 | return; 406 | } 407 | 408 | switch (addr & 0x07) { 409 | case WD2010_REG_WRITE_PRECOMP_CYLINDER: 410 | break; 411 | case WD2010_REG_SECTOR_COUNT: 412 | ctx->sector_count = val; 413 | break; 414 | case WD2010_REG_SECTOR_NUMBER: 415 | // HDSEL3 is also in bit 5 of sector number 416 | ctx->sector_number = val & 0x1f; 417 | break; 418 | case WD2010_REG_CYLINDER_HIGH: // High byte of cylinder 419 | ctx->cylinder_high_reg = val & CYLH_MASK; 420 | break; 421 | case WD2010_REG_CYLINDER_LOW: // Low byte of cylinder 422 | ctx->cylinder_low_reg = val; 423 | break; 424 | case WD2010_REG_SDH: 425 | /*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/ 426 | //ctx->data_pos = ctx->data_len = 0; 427 | ctx->sdh = val; 428 | break; 429 | case WD2010_REG_COMMAND: // Command register 430 | // write to command register clears interrupt request 431 | ctx->irq = false; 432 | ctx->error_reg = 0; 433 | 434 | /*cpu_log_enabled = 1;*/ 435 | switch (cmd) { 436 | case CMD_RESTORE: 437 | // Restore. Set track to 0 and throw an IRQ. 438 | ctx->track = 0; 439 | SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_TimerCallback)seek_complete, ctx); 440 | break; 441 | case CMD_SCAN_ID: 442 | ctx->cylinder_high_reg = (ctx->track >> 8) & CYLH_MASK; 443 | ctx->cylinder_low_reg = ctx->track & 0xff; 444 | ctx->sector_number = ctx->sector; 445 | ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7); 446 | case CMD_WRITE_FORMAT: 447 | case CMD_SEEK: 448 | case CMD_READ_SECTOR: 449 | case CMD_WRITE_SECTOR: 450 | // Seek. Seek to the track specced in the cylinder 451 | // registers. 452 | new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg; 453 | if (new_track < ctx->geometry[ctx->mcr2_ddrive1].tracks) { 454 | ctx->track = new_track; 455 | } else { 456 | // Seek error. :( 457 | fprintf(stderr, "WD2010 ALERT: track %d out of range\n", new_track); 458 | ctx->status = SR_ERROR; 459 | ctx->error_reg = ER_ID_NOT_FOUND; 460 | ctx->irq = true; 461 | break; 462 | } 463 | // The SDH register provides 3 head select bits; the 4th comes from MCR2. 464 | ctx->head = (ctx->sdh & 0x07) + (ctx->mcr2_hdsel3 ? 8 : 0); 465 | ctx->sector = ctx->sector_number; 466 | 467 | ctx->formatting = cmd == CMD_WRITE_FORMAT; 468 | switch (cmd){ 469 | case CMD_SEEK: 470 | SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_TimerCallback)seek_complete, ctx); 471 | break; 472 | case CMD_READ_SECTOR: 473 | /*XXX: does a separate function to set the head have to be added?*/ 474 | LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d nsectors=%d", cmd, ctx->track, ctx->head, ctx->sector, ctx->sector_count); 475 | 476 | // Read Sector 477 | 478 | // Check to see if the cyl, hd and sec are valid 479 | if (cmd != CMD_WRITE_FORMAT && ((ctx->track > (ctx->geometry[ctx->mcr2_ddrive1].tracks-1)) || (ctx->head > (ctx->geometry[ctx->mcr2_ddrive1].heads-1)) || ((ctx->sector + ctx->sector_count - 1) > ctx->geometry[ctx->mcr2_ddrive1].spt-1))) { 480 | fprintf(stderr, "*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, nSecs=%d, endSec=%d maxCHS=%d:%d:%d\n", 481 | ctx->track, ctx->head, ctx->sector, 482 | ctx->sector_count, 483 | ctx->sector + ctx->sector_count - 1, 484 | ctx->geometry[ctx->mcr2_ddrive1].tracks-1, ctx->geometry[ctx->mcr2_ddrive1].heads-1, ctx->geometry[ctx->mcr2_ddrive1].spt); 485 | // CHS parameters exceed limits 486 | ctx->status = SR_ERROR; 487 | ctx->error_reg = ER_ID_NOT_FOUND; 488 | // Set IRQ 489 | ctx->irq = true; 490 | break; 491 | } 492 | 493 | // reset data pointers 494 | ctx->data_pos = ctx->data_len = 0; 495 | 496 | if (val & CMD_MULTI_SECTOR){ 497 | ctx->multi_sector = 1; 498 | sector_count = ctx->sector_count; 499 | }else{ 500 | ctx->multi_sector = 0; 501 | sector_count = 1; 502 | } 503 | for (int i=0; itrack * ctx->geometry[ctx->mcr2_ddrive1].heads * ctx->geometry[ctx->mcr2_ddrive1].spt) + (ctx->head * ctx->geometry[ctx->mcr2_ddrive1].spt) + ctx->sector) + i); 507 | // convert LBA to byte address 508 | lba *= ctx->geometry[ctx->mcr2_ddrive1].secsz; 509 | LOG("\tREAD lba = %zu", lba); 510 | 511 | // Read the sector from the file 512 | fseek(ctx->disc_image[ctx->mcr2_ddrive1], lba, SEEK_SET); 513 | // TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr) 514 | ctx->data_len += fread(&ctx->data[ctx->mcr2_ddrive1][ctx->data_len], 1, ctx->geometry[ctx->mcr2_ddrive1].secsz, ctx->disc_image[ctx->mcr2_ddrive1]); 515 | LOG("\tREAD len=%zu, pos=%zu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geometry[ctx->mcr2_ddrive1].secsz); 516 | } 517 | 518 | ctx->status = 0; 519 | ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00; 520 | /*SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_TimerCallback)transfer_seek_complete, ctx);*/ 521 | ctx->drq = true; 522 | 523 | break; 524 | case CMD_WRITE_FORMAT: 525 | ctx->sector = 0; 526 | case CMD_WRITE_SECTOR: 527 | LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d nsectors=%d", cmd, ctx->track, ctx->head, ctx->sector, ctx->sector_count); 528 | // Write Sector 529 | 530 | // Check to see if the cyl, hd and sec are valid 531 | if (cmd != CMD_WRITE_FORMAT && ((ctx->track > (ctx->geometry[ctx->mcr2_ddrive1].tracks-1)) || (ctx->head > (ctx->geometry[ctx->mcr2_ddrive1].heads-1)) || ((ctx->sector + ctx->sector_count - 1) > ctx->geometry[ctx->mcr2_ddrive1].spt-1))) { 532 | fprintf(stderr, "*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, nSecs=%d, endSec=%d maxCHS=%d:%d:%d\n", 533 | ctx->track, ctx->head, ctx->sector, 534 | ctx->sector_count, 535 | ctx->sector + ctx->sector_count - 1, 536 | ctx->geometry[ctx->mcr2_ddrive1].tracks-1, ctx->geometry[ctx->mcr2_ddrive1].heads-1, ctx->geometry[ctx->mcr2_ddrive1].spt); 537 | // CHS parameters exceed limits 538 | ctx->status = SR_ERROR; 539 | ctx->error_reg = ER_ID_NOT_FOUND; 540 | // Set IRQ 541 | ctx->irq = true; 542 | break; 543 | } 544 | 545 | // reset data pointers 546 | ctx->data_pos = ctx->data_len = 0; 547 | 548 | if (val & CMD_MULTI_SECTOR){ 549 | ctx->multi_sector = 1; 550 | sector_count = ctx->sector_count; 551 | }else{ 552 | ctx->multi_sector = 0; 553 | sector_count = 1; 554 | } 555 | ctx->data_len = ctx->geometry[ctx->mcr2_ddrive1].secsz * sector_count; 556 | lba = (((ctx->track * ctx->geometry[ctx->mcr2_ddrive1].heads * ctx->geometry[ctx->mcr2_ddrive1].spt) + (ctx->head * ctx->geometry[ctx->mcr2_ddrive1].spt) + ctx->sector)); 557 | // convert LBA to byte address 558 | ctx->write_pos = (lba *= ctx->geometry[ctx->mcr2_ddrive1].secsz); 559 | LOG("\tWRITE lba = %zu", lba); 560 | 561 | ctx->status = 0; 562 | ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00; 563 | /*SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_TimerCallback)transfer_seek_complete, ctx);*/ 564 | ctx->drq = true; 565 | 566 | break; 567 | default: 568 | LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd); 569 | break; 570 | } 571 | break; 572 | case CMD_2010_EXT: /* not implemented */ 573 | default: 574 | LOG("WD2010: unknown command %x\n", cmd); 575 | ctx->status = SR_ERROR; 576 | ctx->error_reg = ER_ABORTED_COMMAND; 577 | ctx->irq = true; 578 | break; 579 | } 580 | break; 581 | 582 | } 583 | } 584 | 585 | -------------------------------------------------------------------------------- /src/wd2010.h: -------------------------------------------------------------------------------- 1 | #ifndef _WD2010_H 2 | #define _WD2010_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /// WD2010 registers 10 | typedef enum { 11 | WD2010_REG_ERROR = 1, ///< Error register 12 | WD2010_REG_WRITE_PRECOMP_CYLINDER = 1, ///< Write precompensation cylinder register 13 | WD2010_REG_SECTOR_COUNT = 2, ///< Sector count register 14 | WD2010_REG_SECTOR_NUMBER = 3, ///< Sector number register 15 | WD2010_REG_CYLINDER_LOW = 4, ///< Low byte of cylinder 16 | WD2010_REG_CYLINDER_HIGH = 5, ///< High byte of cylinder 17 | WD2010_REG_SDH = 6, ///< Sector size, drive, and head 18 | WD2010_REG_STATUS = 7, ///< Status register 19 | WD2010_REG_COMMAND = 7, ///< Command register 20 | UNIXPC_REG_MCR2 = 255 ///< UNIX-PC MCR2 register (special!) 21 | } WD2010_REG; 22 | 23 | /// WD2010 emulator error codes 24 | typedef enum { 25 | WD2010_ERR_OK = 0, ///< Operation succeeded 26 | WD2010_ERR_BAD_GEOM = -1, ///< Bad geometry, or image file too small 27 | WD2010_ERR_NO_MEMORY = -2, ///< Out of memory 28 | WD2010_ERR_IO_ERROR = -3 ///< I/O problem 29 | } WD2010_ERR; 30 | 31 | typedef struct { 32 | // Current track, head and sector 33 | int track, head, sector; 34 | // Geometry of current disc 35 | struct geom { 36 | int secsz, spt, heads, tracks; 37 | } geometry[2]; 38 | // IRQ status 39 | bool irq; 40 | // Status of last command 41 | uint8_t status; 42 | // Error resgister 43 | uint8_t error_reg; 44 | // Cylinder number registers 45 | uint8_t cylinder_high_reg, cylinder_low_reg; 46 | // SDH register (sets sector size, drive number, and head number) 47 | uint8_t sdh; 48 | // MCR2 register (LSB is HDSEL3 - head select bit 3) 49 | bool mcr2_hdsel3, mcr2_ddrive1; 50 | // Sector number and count registers 51 | int sector_number, sector_count; 52 | // Last command has the multiple sector flag set? 53 | bool multi_sector; 54 | // Last command uses DRQ bit? 55 | bool cmd_has_drq; 56 | // Current write is a format? 57 | bool formatting; 58 | // Data buffer, current DRQ pointer and length 59 | uint8_t *data[2]; 60 | size_t data_pos, data_len; 61 | // Current disc image file 62 | FILE *disc_image[2]; 63 | // LBA at which to start writing 64 | int write_pos; 65 | // Flag to allow delaying DRQ 66 | bool drq; 67 | } WD2010_CTX; 68 | 69 | /** 70 | * @brief Initialise a WD2010 context. 71 | * @param ctx WD2010 context. 72 | * 73 | * This must be run once when the context is created. 74 | */ 75 | int wd2010_init(WD2010_CTX *ctx, FILE *fp, int drivenum, int secsz, int spt, int heads); 76 | 77 | /** 78 | * @brief Reset a WD2010 context. 79 | * @param ctx WD2010 context. 80 | * 81 | * This should be run if the WD2010 needs to be reset (MR/ line toggled). 82 | */ 83 | void wd2010_reset(WD2010_CTX *ctx); 84 | 85 | /** 86 | * Deinitialise a WD2010 context. 87 | * @param ctx WD2010 context. 88 | */ 89 | void wd2010_done(WD2010_CTX *ctx); 90 | 91 | /** 92 | * @brief Read IRQ Rising Edge status. 93 | * @param ctx WD2010 context. 94 | */ 95 | bool wd2010_get_irq(WD2010_CTX *ctx); 96 | 97 | /** 98 | * @brief Read DRQ status. 99 | * @param ctx WD2010 context. 100 | */ 101 | bool wd2010_get_drq(WD2010_CTX *ctx); 102 | 103 | /** 104 | * @brief Read WD2010 register. 105 | * @param ctx WD2010 context 106 | * @param addr Register address (0, 1, 2, 3, 4, 5, 6, 7, or 8) 107 | */ 108 | uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr); 109 | 110 | /** 111 | * @brief Write WD2010 register 112 | * @param ctx WD2010 context 113 | * @param addr Register address (0, 1, 2, 3, 4, 5, 6, 7, or 8) 114 | * @param val Value to write 115 | */ 116 | void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val); 117 | 118 | /** 119 | * @brief Read a data byte from the data buffer 120 | * @param ctx WD2010 context 121 | */ 122 | uint8_t wd2010_read_data(WD2010_CTX *ctx); 123 | 124 | /** 125 | * @brief Write a value to the data buffer 126 | * @param ctx WD2010 context 127 | * @param val Value to write 128 | */ 129 | void wd2010_write_data(WD2010_CTX *ctx, uint8_t val); 130 | 131 | void wd2010_dma_miss(WD2010_CTX *ctx); 132 | #endif 133 | -------------------------------------------------------------------------------- /src/wd279x.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "musashi/m68k.h" 5 | #include "wd279x.h" 6 | #include "diskimg.h" 7 | 8 | //#define WD279X_DEBUG 9 | 10 | #ifndef WD279X_DEBUG 11 | #define NDEBUG 12 | #endif 13 | #include "utils.h" 14 | 15 | /// WD2797 command constants 16 | enum { 17 | CMD_MASK = 0xF0, ///< Bit mask to detect command bits 18 | CMD_RESTORE = 0x00, ///< Restore (recalibrate, seek to track 0) 19 | CMD_SEEK = 0x10, ///< Seek to given track 20 | CMD_STEP = 0x20, ///< Step 21 | CMD_STEP_TU = 0x30, ///< Step and update track register 22 | CMD_STEPIN = 0x40, ///< Step In 23 | CMD_STEPIN_TU = 0x50, ///< Step In and update track register 24 | CMD_STEPOUT = 0x60, ///< Step Out 25 | CMD_STEPOUT_TU = 0x70, ///< Step Out and update track register 26 | CMD_READ_SECTOR = 0x80, ///< Read Sector 27 | CMD_READ_SECTOR_MULTI = 0x90, ///< Read Multiple Sectors 28 | CMD_WRITE_SECTOR = 0xA0, ///< Write Sector 29 | CMD_WRITE_SECTOR_MULTI = 0xB0, ///< Write Multiple Sectors 30 | CMD_READ_ADDRESS = 0xC0, ///< Read Address (IDAM contents) 31 | CMD_FORCE_INTERRUPT = 0xD0, ///< Force Interrupt 32 | CMD_READ_TRACK = 0xE0, ///< Read Track 33 | CMD_FORMAT_TRACK = 0xF0 ///< Format Track 34 | }; 35 | 36 | 37 | void wd2797_init(WD2797_CTX *ctx) 38 | { 39 | // track, head and sector unknown 40 | ctx->track = ctx->head = ctx->sector = 0; 41 | ctx->track_reg = 0; 42 | 43 | // no IRQ pending 44 | ctx->irq = false; 45 | 46 | // no data available 47 | ctx->data_pos = ctx->data_len = 0; 48 | ctx->data = NULL; 49 | 50 | // Status register clear, not busy; type1 command 51 | ctx->status = 0; 52 | ctx->cmd_has_drq = false; 53 | 54 | // No format command in progress 55 | ctx->formatting = false; 56 | 57 | // Clear data register 58 | ctx->data_reg = 0; 59 | 60 | // Last step direction = "towards zero" 61 | ctx->last_step_dir = -1; 62 | 63 | // No disc image loaded 64 | ctx->disc_image = NULL; 65 | ctx->dif = NULL; 66 | ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0; 67 | } 68 | 69 | 70 | void wd2797_reset(WD2797_CTX *ctx) 71 | { 72 | // track, head and sector unknown 73 | ctx->track = ctx->head = ctx->sector = 0; 74 | ctx->track_reg = 0; 75 | 76 | // no IRQ pending 77 | ctx->irq = false; 78 | 79 | // no data available 80 | ctx->data_pos = ctx->data_len = 0; 81 | 82 | // Status register clear, not busy 83 | ctx->status = 0; 84 | 85 | // Clear data register 86 | ctx->data_reg = 0; 87 | 88 | // Last step direction 89 | ctx->last_step_dir = -1; 90 | } 91 | 92 | 93 | void wd2797_done(WD2797_CTX *ctx) 94 | { 95 | // Reset the WD2797 96 | wd2797_reset(ctx); 97 | 98 | // Free any allocated memory 99 | if (ctx->data) { 100 | free(ctx->data); 101 | ctx->data = NULL; 102 | } 103 | } 104 | 105 | 106 | bool wd2797_get_irq(WD2797_CTX *ctx) 107 | { 108 | return ctx->irq; 109 | } 110 | 111 | bool wd2797_get_drq(WD2797_CTX *ctx) 112 | { 113 | return (ctx->data_pos < ctx->data_len); 114 | } 115 | 116 | 117 | WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int heads, int tracks, int writeable) 118 | { 119 | uint8_t buf[4]; 120 | 121 | // Check if it's an ImageDisk 122 | fseek(fp, 0, SEEK_SET); 123 | if (!fread(buf, 4, 1, fp)) { 124 | return WD2797_ERR_BAD_GEOM; 125 | } 126 | // Assign disk format accordingly 127 | if (!memcmp(buf, "IMD ", 4)) { 128 | ctx->dif = &imd_format; 129 | } else { 130 | ctx->dif = &raw_format; 131 | } 132 | 133 | // Init disk format, and get spt (IMD's are 8 or 10, MS-DOS is 9) 134 | ctx->geom_spt = ctx->dif->init(ctx->dif, fp, secsz, heads, tracks); 135 | if (ctx->geom_spt < 1) 136 | return WD2797_ERR_BAD_GEOM; 137 | 138 | // Allocate enough memory to store one disc track 139 | if (ctx->data) { 140 | free(ctx->data); 141 | } 142 | ctx->data = malloc(secsz * ctx->geom_spt); 143 | if (!ctx->data) 144 | return WD2797_ERR_NO_MEMORY; 145 | 146 | // Load the image and the geometry data 147 | ctx->disc_image = fp; 148 | ctx->geom_tracks = tracks; 149 | ctx->geom_secsz = secsz; 150 | ctx->geom_heads = heads; 151 | ctx->writeable = writeable; 152 | 153 | printf("Floppy image loaded (%s, %i sectors/track).\n", (ctx->dif == &imd_format) ? "ImageDisk" : "raw", ctx->geom_spt); 154 | return WD2797_ERR_OK; 155 | } 156 | 157 | 158 | void wd2797_unload(WD2797_CTX *ctx) 159 | { 160 | // Free memory buffer 161 | if (ctx->data) { 162 | free(ctx->data); 163 | ctx->data = NULL; 164 | } 165 | 166 | // Clear file pointer 167 | ctx->disc_image = NULL; 168 | 169 | // Uninit disk format 170 | if (ctx->dif) ctx->dif->done(ctx->dif); 171 | ctx->dif = NULL; 172 | 173 | // Clear the disc geometry 174 | ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0; 175 | } 176 | 177 | 178 | uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr) 179 | { 180 | uint8_t temp = 0; 181 | m68k_end_timeslice(); 182 | 183 | switch (addr & 0x03) { 184 | case WD2797_REG_STATUS: // Status register 185 | // Read from status register clears IRQ 186 | ctx->irq = false; 187 | 188 | // Get current status flags (set by last command) 189 | // DRQ bit 190 | if (ctx->cmd_has_drq) { 191 | temp = ctx->status & ~0x03; 192 | temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 193 | LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp); 194 | } else { 195 | temp = ctx->status & ~0x01; 196 | } 197 | // FDC is busy if there is still data in the buffer 198 | temp |= (ctx->data_pos < ctx->data_len) ? 0x81 : 0x00; // if data in buffer, then DMA hasn't copied it yet, and we're still busy! 199 | // TODO: also if seek delay / read delay hasn't passed (but that's for later) 200 | return temp; 201 | 202 | case WD2797_REG_TRACK: // Track register 203 | return ctx->track_reg; 204 | 205 | case WD2797_REG_SECTOR: // Sector register 206 | return ctx->sector; 207 | 208 | case WD2797_REG_DATA: // Data register 209 | // If there's data in the buffer, return it. Otherwise return 0xFF. 210 | if (ctx->data_pos < ctx->data_len) { 211 | // set IRQ if this is the last data byte 212 | if (ctx->data_pos == (ctx->data_len-1)) { 213 | // Set IRQ 214 | ctx->irq = true; 215 | } 216 | // return data byte and increment pointer 217 | return ctx->data[ctx->data_pos++]; 218 | } else { 219 | // command finished 220 | return ctx->data_reg; 221 | } 222 | 223 | default: 224 | // shut up annoying compilers which don't recognise unreachable code when they see it 225 | // (here's looking at you, gcc!) 226 | return 0xff; 227 | } 228 | } 229 | 230 | 231 | void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val) 232 | { 233 | uint8_t cmd = val & CMD_MASK; 234 | size_t lba; 235 | bool is_type1 = false; 236 | int temp; 237 | m68k_end_timeslice(); 238 | 239 | switch (addr) { 240 | case WD2797_REG_COMMAND: // Command register 241 | // write to command register clears interrupt request 242 | LOG("WD279X: command %x", val); 243 | ctx->irq = false; 244 | 245 | // Is the drive ready? 246 | if (ctx->disc_image == NULL) { 247 | // No disc image, thus the drive is busy. 248 | ctx->status = 0x80; 249 | ctx->irq = true; 250 | return; 251 | } 252 | 253 | // Handle Type 1 commands 254 | switch (cmd) { 255 | case CMD_RESTORE: 256 | // Restore. Set track to 0 and throw an IRQ. 257 | is_type1 = true; 258 | ctx->track = ctx->track_reg = 0; 259 | break; 260 | 261 | case CMD_SEEK: 262 | // Seek. Seek to the track specced in the Data Register. 263 | is_type1 = true; 264 | if (ctx->data_reg < ctx->geom_tracks) { 265 | ctx->track = ctx->track_reg = ctx->data_reg; 266 | } else { 267 | // Seek error. :( 268 | ctx->status = 0x10; 269 | } 270 | 271 | case CMD_STEP: 272 | // TODO! deal with trk0! 273 | // Need to keep a copy of the track register; when it hits 0, set the TRK0 flag. 274 | is_type1 = true; 275 | break; 276 | 277 | case CMD_STEPIN: 278 | case CMD_STEPOUT: 279 | case CMD_STEP_TU: 280 | case CMD_STEPIN_TU: 281 | case CMD_STEPOUT_TU: 282 | // if this is a Step In or Step Out cmd, set the step-direction 283 | if ((cmd & ~0x10) == CMD_STEPIN) { 284 | ctx->last_step_dir = 1; 285 | } else if ((cmd & ~0x10) == CMD_STEPOUT) { 286 | ctx->last_step_dir = -1; 287 | } 288 | 289 | 290 | // Seek one step in the last direction used. 291 | ctx->track += ctx->last_step_dir; 292 | if (ctx->track < 0) ctx->track = 0; 293 | if (ctx->track >= ctx->geom_tracks) { 294 | // Seek past end of disc... that'll be a Seek Error then. 295 | ctx->status = 0x10; 296 | ctx->track = ctx->geom_tracks - 1; 297 | } 298 | if (cmd & 0x10){ 299 | ctx->track_reg = ctx->track; 300 | } 301 | 302 | is_type1 = true; 303 | break; 304 | 305 | default: 306 | break; 307 | } 308 | 309 | if (is_type1) { 310 | // Terminate any sector reads or writes 311 | ctx->data_len = ctx->data_pos = 0; 312 | 313 | // No DRQ bit for these commands. 314 | ctx->cmd_has_drq = false; 315 | 316 | // Type1 status byte... 317 | ctx->status = 0; 318 | // S7 = Not Ready. Command executed, therefore the drive was ready... :) 319 | // S6 = Write Protect. TODO: add this 320 | // S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded... 321 | ctx->status |= 0x20; 322 | // S4 = Seek Error. Not bloody likely if we got down here...! 323 | // S3 = CRC Error. Not gonna happen on a disc image! 324 | // S2 = Track 0 325 | ctx->status |= (ctx->track == 0) ? 0x04 : 0x00; 326 | // S1 = Index Pulse. TODO -- need periodics to emulate this 327 | // S0 = Busy. We just exec'd the command, thus we're not busy. 328 | // TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that. 329 | 330 | // Set IRQ 331 | ctx->irq = true; 332 | return; 333 | } 334 | 335 | // That's the Type 1 (seek) commands sorted. Now for the others. 336 | 337 | // All these commands return the DRQ bit... 338 | ctx->cmd_has_drq = true; 339 | 340 | // If drive isn't ready, then set status B7 and exit 341 | if (ctx->disc_image == NULL) { 342 | ctx->status = 0x80; 343 | return; 344 | } 345 | 346 | // If this is a Write command, check write protect status too 347 | if (!ctx->writeable) { 348 | // Write protected disc... 349 | if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) { 350 | // Set Write Protect bit and bail. 351 | ctx->status = 0x40; 352 | 353 | // Set IRQ 354 | ctx->irq = true; 355 | 356 | return; 357 | } 358 | } 359 | 360 | // Disc is ready to go. Parse the command word. 361 | switch (cmd) { 362 | case CMD_READ_ADDRESS: 363 | // Read Address 364 | ctx->head = (val & 0x02) ? 1 : 0; 365 | 366 | // reset data pointers 367 | ctx->data_pos = ctx->data_len = 0; 368 | 369 | // load data buffer 370 | ctx->data[ctx->data_len++] = ctx->track; 371 | ctx->data[ctx->data_len++] = ctx->head; 372 | ctx->data[ctx->data_len++] = ctx->sector; 373 | switch (ctx->geom_secsz) { 374 | case 128: ctx->data[ctx->data_len++] = 0; break; 375 | case 256: ctx->data[ctx->data_len++] = 1; break; 376 | case 512: ctx->data[ctx->data_len++] = 2; break; 377 | case 1024: ctx->data[ctx->data_len++] = 3; break; 378 | default: ctx->data[ctx->data_len++] = 0xFF; break; // TODO: deal with invalid values better 379 | } 380 | ctx->data[ctx->data_len++] = 0; // TODO: IDAM CRC! 381 | ctx->data[ctx->data_len++] = 0; 382 | 383 | ctx->status = 0; 384 | // B6, B5 = 0 385 | // B4 = Record Not Found. We're not going to see this... FIXME-not emulated 386 | // B3 = CRC Error. Not possible. 387 | // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 388 | // B1 = DRQ. Data request. 389 | ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 390 | break; 391 | 392 | case CMD_READ_SECTOR: 393 | case CMD_READ_SECTOR_MULTI: 394 | ctx->head = (val & 0x02) ? 1 : 0; 395 | LOG("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector); 396 | // Read Sector or Read Sector Multiple 397 | 398 | // Check to see if the cyl, hd and sec are valid 399 | if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt) || (ctx->sector == 0)) { 400 | LOG("*** WD2797 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d", 401 | ctx->track, ctx->head, ctx->sector, 402 | ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt); 403 | // CHS parameters exceed limits 404 | ctx->status = 0x10; // Record Not Found 405 | // Set IRQ 406 | ctx->irq = true; 407 | break; 408 | } 409 | 410 | // reset data pointers 411 | ctx->data_pos = ctx->data_len = 0; 412 | 413 | // Calculate number of sectors to read from disc 414 | if (cmd == CMD_READ_SECTOR_MULTI) 415 | temp = ctx->geom_spt; 416 | else 417 | temp = 1; 418 | 419 | for (int i=0; itrack, ctx->head, ctx->sector+i); 421 | 422 | // Read the sector from the file 423 | ctx->data_len += ctx->dif->read_sector(ctx->dif, ctx->track, ctx->head, ctx->sector+i, &ctx->data[ctx->data_len]); 424 | // TODO: check read_sector return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr) 425 | LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz); 426 | } 427 | 428 | ctx->status = 0; 429 | // B6 = 0 430 | // B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks. 431 | // B4 = Record Not Found. Basically, the CHS parameters are bullcrap. 432 | // B3 = CRC Error. Not possible. 433 | // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 434 | // B1 = DRQ. Data request. 435 | ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 436 | break; 437 | 438 | case CMD_READ_TRACK: 439 | // Read Track 440 | // TODO! implement this 441 | // ctx->head = (val & 0x02) ? 1 : 0; 442 | // ctx->status = 0; 443 | // B6, B5, B4, B3 = 0 444 | // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 445 | // B1 = DRQ. Data request. 446 | // ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 447 | ctx->irq = true; 448 | ctx->status = 0x10; 449 | break; 450 | 451 | case CMD_WRITE_SECTOR: 452 | case CMD_WRITE_SECTOR_MULTI: 453 | // Write Sector or Write Sector Multiple 454 | 455 | ctx->head = (val & 0x02) ? 1 : 0; 456 | // reset data pointers 457 | ctx->data_pos = 0; 458 | 459 | // Calculate number of sectors to write to disc 460 | if (cmd == CMD_WRITE_SECTOR_MULTI) 461 | /*XXX: is this the correct value?*/ 462 | temp = ctx->geom_spt; 463 | else 464 | temp = 1; 465 | ctx->data_len = temp * ctx->geom_secsz; 466 | 467 | lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector)) - 1; 468 | ctx->write_pos = lba; 469 | 470 | ctx->status = 0; 471 | // B6 = Write Protect. This would have been set earlier. 472 | // B5 = 0 473 | // B4 = Record Not Found. We're not going to see this... FIXME-not emulated 474 | // B3 = CRC Error. Not possible. 475 | // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 476 | // B1 = DRQ. Data request. 477 | ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 478 | break; 479 | 480 | case CMD_FORMAT_TRACK: 481 | // Write Track (aka Format Track) 482 | ctx->head = (val & 0x02) ? 1 : 0; 483 | ctx->status = 0; 484 | // B6 = Write Protect. FIXME -- emulate this! 485 | // B5, B4, B3 = 0 486 | // B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated 487 | ctx->data_pos = 0; 488 | ctx->data_len = 7170; 489 | // B1 = DRQ. Data request. 490 | ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00; 491 | ctx->formatting = true; 492 | break; 493 | 494 | case CMD_FORCE_INTERRUPT: 495 | // Force Interrupt... 496 | // Terminates current operation and sends an interrupt 497 | // TODO! 498 | ctx->status = 0x20; 499 | if (!ctx->writeable){ 500 | ctx->status |= 0x40; 501 | } 502 | if (ctx->track == 0){ 503 | ctx->status = 0x04; 504 | } 505 | ctx->data_pos = ctx->data_len = 0; 506 | if (cmd & 8){ 507 | // Set IRQ 508 | ctx->irq = true; 509 | } 510 | break; 511 | } 512 | break; 513 | 514 | case WD2797_REG_TRACK: // Track register 515 | ctx->track = ctx->track_reg = val; 516 | break; 517 | 518 | case WD2797_REG_SECTOR: // Sector register 519 | ctx->sector = val; 520 | break; 521 | 522 | case WD2797_REG_DATA: // Data register 523 | // Save the value written into the data register 524 | ctx->data_reg = val; 525 | // If we're processing a write command, and there's space in the 526 | // buffer, allow the write. 527 | if (ctx->data_pos < ctx->data_len && (ctx->write_pos >= 0 || ctx->formatting)) { 528 | if (!ctx->formatting) ctx->data[ctx->data_pos] = val; 529 | // store data byte and increment pointer 530 | ctx->data_pos++; 531 | 532 | // set IRQ and write data if this is the last data byte 533 | if (ctx->data_pos == ctx->data_len) { 534 | if (!ctx->formatting){ 535 | if (ctx->data_len != 512) fprintf(stderr, "floppy sector write error: sector write size != 512"); 536 | // Convert LBA back to CHS 537 | int write_cyl = ctx->write_pos / (ctx->geom_heads * ctx->geom_spt); 538 | int write_head = (ctx->write_pos / ctx->geom_spt) % ctx->geom_heads; 539 | int write_sector = (ctx->write_pos % ctx->geom_spt) + 1; 540 | ctx->dif->write_sector(ctx->dif, write_cyl, write_head, write_sector, ctx->data); 541 | } 542 | // Set IRQ and reset write pointer 543 | ctx->irq = true; 544 | ctx->write_pos = -1; 545 | ctx->formatting = false; 546 | } 547 | 548 | } 549 | break; 550 | } 551 | } 552 | 553 | void wd2797_dma_miss(WD2797_CTX *ctx) 554 | { 555 | ctx->data_pos = ctx->data_len; 556 | ctx->write_pos = 0; 557 | ctx->status = 4; /* lost data */ 558 | ctx->irq = true; 559 | } 560 | -------------------------------------------------------------------------------- /src/wd279x.h: -------------------------------------------------------------------------------- 1 | #ifndef _WD279X_H 2 | #define _WD279X_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "diskimg.h" 9 | 10 | /// WD279x registers 11 | typedef enum { 12 | WD2797_REG_STATUS = 0, ///< Status register 13 | WD2797_REG_COMMAND = 0, ///< Command register 14 | WD2797_REG_TRACK = 1, ///< Track register 15 | WD2797_REG_SECTOR = 2, ///< Sector register 16 | WD2797_REG_DATA = 3 ///< Data register 17 | } WD2797_REG; 18 | 19 | /// WD279x emulator error codes 20 | typedef enum { 21 | WD2797_ERR_OK = 0, ///< Operation succeeded 22 | WD2797_ERR_BAD_GEOM = -1, ///< Bad geometry, or image file too small 23 | WD2797_ERR_NO_MEMORY = -2 ///< Out of memory 24 | } WD2797_ERR; 25 | 26 | typedef struct { 27 | // Current track, head and sector 28 | int track, head, sector; 29 | // Track and sector registers 30 | int track_reg, sector_reg; 31 | // Geometry of current disc 32 | int geom_secsz, geom_spt, geom_heads, geom_tracks; 33 | // IRQ status 34 | bool irq; 35 | // Status of last command 36 | uint8_t status; 37 | // Last command uses DRQ bit? 38 | bool cmd_has_drq; 39 | // The last value written to the data register 40 | uint8_t data_reg; 41 | // Last step direction. -1 for "towards zero", 1 for "away from zero" 42 | int last_step_dir; 43 | // Data buffer, current DRQ pointer and length 44 | uint8_t *data; 45 | size_t data_pos, data_len; 46 | // Current disc image file 47 | FILE *disc_image; 48 | // Write protect flag 49 | int writeable; 50 | // LBA at which to start writing 51 | int write_pos; 52 | // True if a format command is in progress 53 | int formatting; 54 | // Disc image format i/o 55 | DISK_IMAGE *dif; 56 | } WD2797_CTX; 57 | 58 | /** 59 | * @brief Initialise a WD2797 context. 60 | * @param ctx WD2797 context. 61 | * 62 | * This must be run once when the context is created. 63 | */ 64 | void wd2797_init(WD2797_CTX *ctx); 65 | 66 | /** 67 | * @brief Reset a WD2797 context. 68 | * @param ctx WD2797 context. 69 | * 70 | * This should be run if the WD2797 needs to be reset (nRST line toggled). 71 | */ 72 | void wd2797_reset(WD2797_CTX *ctx); 73 | 74 | /** 75 | * Deinitialise a WD2797 context. 76 | * @param ctx WD2797 context. 77 | */ 78 | void wd2797_done(WD2797_CTX *ctx); 79 | 80 | /** 81 | * @brief Read IRQ Rising Edge status. Clears Rising Edge status if it is set. 82 | * @note No more IRQs will be sent until the Status Register is read, or a new command is written to the CR. 83 | * @param ctx WD2797 context. 84 | */ 85 | bool wd2797_get_irq(WD2797_CTX *ctx); 86 | 87 | /** 88 | * @brief Read DRQ status. 89 | * @param ctx WD2797 context. 90 | */ 91 | bool wd2797_get_drq(WD2797_CTX *ctx); 92 | 93 | /** 94 | * @brief Assign a disc image to the WD2797. 95 | * @param ctx WD2797 context. 96 | * @param fp Disc image file, already opened in "r+b" mode. 97 | * @param secsz Sector size: either 128, 256, 512 or 1024. 98 | * @param heads Number of heads (1 or 2). 99 | * @param tracks Number of tracks (40). 100 | * @return Error code; WD279X_E_OK if everything worked OK. 101 | */ 102 | WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int heads, int tracks, int writeable); 103 | 104 | /** 105 | * @brief Deassign the current image file. 106 | * @param ctx WD2797 context. 107 | */ 108 | void wd2797_unload(WD2797_CTX *ctx); 109 | 110 | /** 111 | * @brief Read WD279x register. 112 | * @param ctx WD2797 context 113 | * @param addr Register address (0, 1, 2 or 3) 114 | */ 115 | uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr); 116 | 117 | /** 118 | * @brief Write WD279X register 119 | * @param ctx WD2797 context 120 | * @param addr Register address (0, 1, 2 or 3) 121 | * @param val Value to write 122 | */ 123 | void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val); 124 | 125 | void wd2797_dma_miss(WD2797_CTX *ctx); 126 | #endif 127 | -------------------------------------------------------------------------------- /tools/Makefile: -------------------------------------------------------------------------------- 1 | makehdimg: makehdimg.c 2 | cc -O makehdimg.c -o makehdimg 3 | -------------------------------------------------------------------------------- /tools/makehdimg.1: -------------------------------------------------------------------------------- 1 | .TH MAKEHDIMG 1 "Mar 18 2021" "" "Freebee Emulator Tools" 2 | .SH NAME 3 | makehdimg \- create an initial hard disk image file for the freebee emulator 4 | .SH SYNOPSIS 5 | .B makehdimg 6 | .RB [ \-H ] 7 | .BI \-h " numheads" 8 | .BI \-c " numcyls" 9 | .BI \-b " blocks_per_track" 10 | [\fB\-o\fP \fIimage\fR] 11 | .SH DESCRIPTION 12 | .I Makehdimg 13 | creates hard disk image files for the 14 | .I freebee 15 | AT&T UNIX PC / 3B1 emulator. 16 | It writes sizing information at the head of the file which 17 | the emulator uses to initialize the (emulated) WD2010 controller. 18 | You can then use the diagnostics disk to fully initialize the disk 19 | image, by selecting ``Other'' and entering the same sizing information 20 | as used to create the file. 21 | .PP 22 | The default image file is named 23 | .IR hd.img . 24 | You can change this with the 25 | .B \-o 26 | option. 27 | .SH OPTIONS 28 | .PP 29 | .I Makehdimg 30 | accepts the following options. 31 | .TP 32 | .B \-H 33 | Print a usage message and exit. 34 | .TP 35 | .BI \-h " numheads" 36 | Specify the number of heads. 37 | .TP 38 | .BI \-c " numcyls" 39 | Specify the number of cylinders. 40 | The UNIX PC operating system doesn't support more than 1,400 41 | cylinders, so you may not specify more than that. 42 | .TP 43 | .BI \-b " blocks_per_track" 44 | Specify the number of blocks per track. 45 | This should be either 16 or 17. 46 | .TP 47 | .BI \-o " image" 48 | Specify the name of the image file to write. 49 | .SH EXIT STATUS 50 | .I Makehdimg 51 | exits with zero if there were no problems. 52 | Otherwise it prints a descriptive error message and 53 | exits with a value of one. 54 | .SH BUGS 55 | None known. 56 | .SH SEE ALSO 57 | .BR https://github.com/philpem/freebee : 58 | The 59 | .I freebee 60 | 3B1 emulator. 61 | .SH AUTHOR 62 | Arnold Robbins 63 | .br 64 | .B arnold@skeeve.com 65 | -------------------------------------------------------------------------------- /tools/makehdimg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * makehdimg.c --- create an initial "disk" image for use with the 3 | * freebee 3B1 emulator. 4 | */ 5 | 6 | /* 7 | * Copyright (C) 2020, 8 | * Arnold David Robbins 9 | * 10 | * MAKEHDIMG is free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * MAKEHDIMG is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #define BLOCK_SIZE 512 // no other value makes sense, hardcode it 36 | #define MAX_CYLS 1400 // OS doesn't allow more than this 37 | 38 | /* usage --- print a usage message and exit */ 39 | 40 | void 41 | usage(const char *progname) 42 | { 43 | fprintf(stderr, "usage: %s [-H] -h numheads -c numcyls -b blocks_per_track [-o image]\n", 44 | progname); 45 | exit(EXIT_FAILURE); 46 | } 47 | 48 | /* main.c --- parse args, allocate and initialize memory, create the file */ 49 | 50 | int 51 | main(int argc, char **argv) 52 | { 53 | int c, fd; 54 | const char *outfile = "hd.img"; 55 | char *buffer; 56 | size_t buffer_size; 57 | ssize_t count; 58 | int numheads, numcyls, blocks_per_track; 59 | 60 | buffer_size = numheads = numcyls = blocks_per_track = 0; 61 | 62 | while ((c = getopt(argc, argv, "Hh:c:b:o:")) != EOF) { 63 | switch (c) { 64 | case 'h': 65 | numheads = strtol(optarg, NULL, 10); 66 | break; 67 | case 'c': 68 | numcyls = strtol(optarg, NULL, 10); 69 | break; 70 | case 'b': 71 | blocks_per_track = strtol(optarg, NULL, 10); 72 | break; 73 | case 'o': 74 | outfile = optarg; 75 | break; 76 | case 'H': 77 | default: 78 | usage(argv[0]); 79 | break; 80 | } 81 | } 82 | 83 | if (numheads <= 0 || numcyls <= 0 || blocks_per_track <= 0) { 84 | fprintf(stderr, "error: invalid value supplied or value missing for " 85 | "one or more parameters\n"); 86 | usage(argv[0]); 87 | } 88 | 89 | if (numcyls > MAX_CYLS) { 90 | fprintf(stderr, "error: number of cylinders cannot exceed %d\n", MAX_CYLS); 91 | exit(EXIT_FAILURE); 92 | } 93 | 94 | buffer_size = numheads * numcyls * blocks_per_track * BLOCK_SIZE; 95 | buffer = (char *) malloc(buffer_size); 96 | if (buffer == NULL) { 97 | fprintf(stderr, "error: could not allocate %lu bytes of memory: %s\n", 98 | buffer_size, strerror(errno)); 99 | exit(EXIT_FAILURE); 100 | } 101 | 102 | /* write info into the buffer */ 103 | memset(buffer, '\0', buffer_size); 104 | sprintf(buffer, "free\nheads: %d cyls: %d bpt: %d blksiz: %d\n", 105 | numheads, numcyls, blocks_per_track, BLOCK_SIZE); 106 | 107 | /* write to file */ 108 | if ((fd = open(outfile, O_CREAT|O_WRONLY|O_TRUNC, 0644)) < 0) { 109 | fprintf(stderr, "error: %s: cannot open for writing: %s\n", 110 | outfile, strerror(errno)); 111 | exit(EXIT_FAILURE); 112 | } 113 | 114 | if ((count = write(fd, buffer, buffer_size)) != buffer_size) { 115 | fprintf(stderr, "error: %s: cannot write data: %s\n", 116 | outfile, strerror(errno)); 117 | (void) close(fd); 118 | (void) unlink(outfile); 119 | exit(EXIT_FAILURE); 120 | } 121 | 122 | (void) close(fd); 123 | (void) free(buffer); 124 | 125 | return EXIT_SUCCESS; 126 | } 127 | --------------------------------------------------------------------------------