├── .gitignore
├── .vscode
├── c_cpp_properties.json
└── settings.json
├── LICENSE
├── Makefile
├── README.md
├── device.c
└── images
├── VSCode_on_linux.jpg
├── VSCode_on_windows_with_MSYS2.jpg
├── copy_driver_to_devs_with_dopus.jpg
├── disassembly_screenshot.jpg
├── mapping_device_name_in device_list.jpg
├── project_folder_mapped_as_drive_in_WinUAE.jpg
├── project_folder_shown_in_dopus.jpg
├── telnet_kprintf_output.jpg
└── telnet_to_WinUAE_emulated_serial_port.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | build-debug
2 | build-release
3 | Thumbs.db
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/.vscode/c_cpp_properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "Amiga",
5 | "includePath": [
6 | //paths for the IntelliSense engine to use while searching for included headers.
7 | "${workspaceFolder}/**","C:\\msys64\\opt\\amiga\\m68k-amigaos\\ndk-include"
8 | //"C:\\msys64\\opt\\amiga\\m68k-amigaos\\ndk13-include"
9 | //"C:\\Users\\Jorgen\\.vscode\\extensions\\bartmanabyss.amiga-debug-1.1.0-preview31\\bin\\opt\\m68k-amiga-elf\\sys-include"
10 | ],
11 | "defines": [
12 | //"__GNUC__=8",
13 | "__GNUC__=6",
14 | "_NO_INLINE"
15 | ],
16 | //Full path of the compiler being used to enable more accurate IntelliSense.
17 | "compilerPath": "C:\\msys64\\opt\\amiga\\bin\\m68k-amigaos-gcc.exe",
18 | //"compilerPath": "C:\\Users\\Jorgen\\.vscode\\extensions\\bartmanabyss.amiga-debug-1.1.0-preview31\\bin\\opt\\bin\\m68k-amiga-elf-gcc.exe",
19 | "cStandard": "c11",
20 | "cppStandard": "c++17",
21 | "intelliSenseMode": "gcc-x64"
22 | }
23 | ],
24 | "version": 4
25 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "telemetry.enableCrashReporter": false,
3 | "telemetry.enableTelemetry": false,
4 | "terminal.integrated.shell.windows": "C:\\msys64\\usr\\bin\\bash.exe",
5 | "terminal.integrated.shellArgs.windows": [
6 | "--login",
7 | ],
8 | "terminal.integrated.env.windows": {
9 | "CHERE_INVOKING": "1",
10 | "MSYSTEM": "MINGW64",
11 | "MSYS2_PATH_TYPE": "inherit",
12 | "PATH": "/c/msys64/opt/amiga/bin:/c/Users/Jorgen/.vscode/extensions/bartmanabyss.amiga-debug-1.1.0-preview31/bin:/c/Users/Jorgen/.vscode/extensions/bartmanabyss.amiga-debug-1.1.0-preview31/bin/opt/bin",
13 | },
14 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jorgen Bilander
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | FILENAME=simple.device
2 | RELDIR=build-release
3 | DEBUGDIR=build-debug
4 | OBJECTS=device.o
5 |
6 | SRCDIRS=.
7 | INCDIRS=.
8 |
9 | #TARGET = elf-toolchain
10 | TARGET = hunk-toolchain
11 |
12 | ifeq ($(MAKECMDGOALS), debug)
13 | DIR=$(DEBUGDIR)
14 | EXTRA_CFLAGS+= -g -O2
15 | EXTRA_ASFLAGS+= -g
16 | ifeq ($(TARGET), hunk-toolchain)
17 | EXTRA_CFLAGS+= -DDEBUG -mcrt=clib2
18 | EXTRA_LDFLAGS+= -ldebug
19 | endif
20 | else
21 | DIR=$(RELDIR)
22 | EXTRA_CFLAGS+= -s -O2
23 | endif
24 |
25 | ifeq ($(TARGET), elf-toolchain)
26 | CC=m68k-amiga-elf-gcc
27 | AS=m68k-amiga-elf-as
28 | ELF_SUFFIX=.elf
29 | EXTRA_LDFLAGS+= -Wl,--emit-relocs,-Ttext=0,-Map=$(DIR)/$(FILENAME)$(ELF_SUFFIX).map
30 | endif
31 |
32 | ifeq ($(TARGET), hunk-toolchain)
33 | CC=m68k-amigaos-gcc
34 | AS=m68k-amigaos-as
35 | EXTRA_LDFLAGS+= -Wl,-Map=$(DIR)/$(FILENAME).map
36 | endif
37 |
38 | CFLAGS+= -m68000 -Wall -Wextra -Wno-unused-parameter -fomit-frame-pointer
39 | ASFLAGS+= -m68000
40 | LDFLAGS+= -nostdlib -nostartfiles
41 |
42 | OBJS:=$(addprefix $(DIR)/,$(OBJECTS))
43 |
44 | CFLAGS+=$(addprefix -I,$(INCDIRS)) $(EXTRA_CFLAGS)
45 | ASFLAGS+=$(EXTRA_ASFLAGS)
46 | LDFLAGS+=$(EXTRA_LDFLAGS)
47 |
48 | # Search paths
49 | vpath %.c $(SRCDIRS)
50 | vpath %.s $(SRCDIRS)
51 |
52 | release: $(TARGET)
53 | debug: $(TARGET)
54 |
55 | elf-toolchain: $(DIR) $(OBJS) $(DIR)/$(FILENAME)
56 | elf2hunk $(DIR)/$(FILENAME)$(ELF_SUFFIX) $(DIR)/$(FILENAME)
57 | m68k-amiga-elf-objdump -D $(DIR)/$(FILENAME)$(ELF_SUFFIX) > $(DIR)/$(FILENAME)$(ELF_SUFFIX).s
58 | # m68k-amigaos-objdump -D $(DIR)/$(FILENAME) > $(DIR)/$(FILENAME).s
59 |
60 | $(DIR)/$(FILENAME): #ELF_SUFFIX is empty for hunk-toolchain so this below works for both targets
61 | $(CC) $(CFLAGS) -o $@$(ELF_SUFFIX) $(OBJS) $(LDFLAGS)
62 |
63 | hunk-toolchain: $(DIR) $(OBJS) $(DIR)/$(FILENAME)
64 | m68k-amigaos-objdump -D $(DIR)/$(FILENAME) > $(DIR)/$(FILENAME).s
65 |
66 | $(DIR):
67 | @mkdir $(DIR)
68 |
69 | $(DIR)/%.o: %.c
70 | $(CC) $(CFLAGS) -c -o $@ $<
71 | @$(DEL_EXE)
72 |
73 | $(DIR)/%.o: %.s
74 | $(AS) $(ASFLAGS) -o $@ $<
75 | @$(DEL_EXE)
76 |
77 |
78 | ifeq '$(findstring ;,$(PATH))' ';'
79 | UNAME := Windows_Native
80 | endif
81 |
82 | ifeq ($(UNAME),Windows_Native)
83 | RM= rmdir /s /q
84 | DEVNULL= 2>nul || ver>nul
85 | DEL_EXE=if exist $(DIR)\$(FILENAME) del /q $(DIR)\$(FILENAME)
86 | else
87 | RM= rm -rf
88 | DEL_EXE=test -f && $(RM) $(DIR)/$(FILENAME)
89 | endif
90 |
91 | .PHONY: clean
92 |
93 | clean: cleandebug
94 | clean: cleanrelease
95 |
96 | cleandebug:
97 | $(info Cleaning debug)
98 | @$(RM) $(DEBUGDIR) $(DEVNULL)
99 |
100 | cleanrelease:
101 | $(info Cleaning release)
102 | @$(RM) $(RELDIR) $(DEVNULL)
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Amiga SimpleDevice
2 | Amiga Simple Device driver skel in C using Bebbo's or Bartman's gcc.
3 | ***
4 | This example shows you how to build a simple device driver for the Amiga in C with a modern cross compile gcc-toolchain for the Amiga using VSCode as IDE. Driver should work with 1.3 and above. I haven't tested with older versions.
5 | ***
6 | The `Makefile` is made to work with both `Bebbo's amiga-gcc toolchain` as well as `Bartman^Abyss' amiga-elf gcc toolchain`. Currently it is set by default to Bebbo's toolchain, but you can easily switch by commenting out `hunk-toolchain` and uncomment `elf-toolchain` in the Makefile. Obviously you need to install "your" prefered toolchain for it to work and set the correct path to compiler and some settings in .vscode for intellisense and compilation to work. The Makefile should also work with Bartman's `gnumake.exe` running natively on Windows as well as `make` working on MSYS2 and on Linux. The `device.c` is only made to compile with Bebbo's toolchain since it is using register assignments to function parameters, no such thing exists in the elf-toolchain but you can work around it using inline asm should you want to try out building with the elf-toolchain instead. The `Makefile` is also set to automatically disassemble the build so that the assembly generated can easily be inspected. If you are using the elf-toolchain and have Bebbo's toolchain installed as well you can uncomment the `m68k-amigaos-objdump -D $(DIR)/$(FILENAME) > $(DIR)/$(FILENAME).s` line and you will have an disassembly of the executable hunk-file generated after the `elf2hunk` step as well.
7 | ***
8 | The Makfile is set to delete and recompile the executable if any source files `(%.c or %.s)` have been touched before running `make`. Just comment out the `@$(DEL_EXE)` on both `%.c and %.s` if you don't want it to work this way.
9 | ***
10 | ### Some screenshots:
11 | On Windows with MSYS2:
12 |
13 |
14 |
15 |
16 |
17 |
18 | On Linux Mint:
19 |
20 |
21 |
22 |
23 | ***
24 | `settings.json` in .vscode for linux would be something like this:
25 | ```
26 | {
27 | "terminal.integrated.env.linux": {
28 | "PATH": "/opt/amiga/bin:${env:PATH}"
29 | },
30 | }
31 | ```
32 | and `c_cpp_properties.json` something like this:
33 | ```
34 | "includePath": [
35 | "${workspaceFolder}/**","/opt/amiga/m68k-amigaos/ndk-include"
36 | ],
37 | "compilerPath": "/opt/amiga/bin/m68k-amigaos-gcc.exe",
38 | ```
39 | ***
40 |
41 | ### Make commands:
42 | * build debug: `make debug` (This creates a folder `build-debug` with all the build-files in it, compiles with debug-flags set)
43 | * build release: `make` (This creates a folder `build-release` with all the build-files in it, compiles with release-flags set)
44 | * clean debug: `make cleandebug`
45 | * clean release: `make cleanrelease`
46 | * clean both: `make clean`
47 |
48 | ***
49 |
50 | ### "Debug" with KPrintF in methods running in Forbidden mode by Exec:
51 | As you might know `Init_Device/Open/Close/Expunge` runs single threaded by Exec, no other task is allowed to run in this mode so no writing to a dos-console possible as it would mean deadlock to occur, however with the `debug.lib (libdebug.a)` linked into our build (with `-ldebug -mcrt=clib2`) it is possible to get `KPrintF`-statements out on the serial port terminal. Good thing is WinUAE has an option to emulate the serial terminal of the Amiga and then we can use telnet to connect to it with the command `telnet localhost 1234`
52 |
53 | Enable WinUAE serial port on 1234, and open a cmd-prompt and write the telnet command (obviously you'll need telnet installed first via `Control Panel->Program and Features->Turn windows features on/off)`.
54 |
55 |
56 |
57 |
58 |
59 | ***
60 | For conveniency, do also map the Project folder in WinUAE so we easily can copy our newly built device driver over to the emulated Amiga:
61 |
62 |
63 |
64 |
65 | ***
66 | With dopus on the Amiga you can copy the `simple.device` into `devs`.
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | ***
76 | To load the device we need a device list with the Device entry pointing to our name of the file `simple.device`, I'll use a `SD0`-file here made in `Devs` as a dummy to see if it will work to load the driver, Exec will scan for a romtag in our device driver:
77 |
78 |
79 |
80 |
81 | ***
82 | And with telnet connected and running by double-clicking the `SD0`-file it is indeed showing `KPrintF`-output on the serial console...yay!
83 |
84 |
85 |
86 |
87 | ***
88 | In the disassembly we can also se that the romtag is created correctly after `moveq #-1,d0` `rts` as it should be:
89 |
90 |
91 |
92 |
93 | ***
94 | That's all folks!, Happy Amiga Hackin'
95 |
--------------------------------------------------------------------------------
/device.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | #if DEBUG
7 | #include
8 | #endif
9 |
10 | #define STR(s) #s /* Turn s into a string literal without expanding macro definitions (however, \
11 | if invoked from a macro, macro arguments are expanded). */
12 | #define XSTR(s) STR(s) /* Turn s into a string literal after macro-expanding it. */
13 |
14 | #define DEVICE_NAME "simple.device"
15 | #define DEVICE_DATE "(1 Sep 2020)"
16 | #define DEVICE_ID_STRING "simple " XSTR(DEVICE_VERSION) "." XSTR(DEVICE_REVISION) " " DEVICE_DATE /* format is: 'name version.revision (d.m.yy)' */
17 | #define DEVICE_VERSION 1
18 | #define DEVICE_REVISION 0
19 | #define DEVICE_PRIORITY 0 /* Most people will not need a priority and should leave it at zero. */
20 |
21 | struct ExecBase *SysBase;
22 | BPTR saved_seg_list;
23 |
24 | BOOL is_open;
25 |
26 | /*-----------------------------------------------------------
27 | A library or device with a romtag should start with moveq #-1,d0 (to
28 | safely return an error if a user tries to execute the file), followed by a
29 | Resident structure.
30 | ------------------------------------------------------------*/
31 | int __attribute__((no_reorder)) _start()
32 | {
33 | return -1;
34 | }
35 |
36 | /*-----------------------------------------------------------
37 | A romtag structure. After your driver is brought in from disk, the
38 | disk image will be scanned for this structure to discover magic constants
39 | about you (such as where to start running you from...).
40 |
41 | endcode is a marker that shows the end of your code. Make sure it does not
42 | span hunks, and is not before the rom tag! It is ok to put it right after
43 | the rom tag -- that way you are always safe.
44 | Make sure your program has only a single code hunk if you put it at the
45 | end of your code.
46 | ------------------------------------------------------------*/
47 | asm("romtag: \n"
48 | " dc.w "XSTR(RTC_MATCHWORD)" \n"
49 | " dc.l romtag \n"
50 | " dc.l endcode \n"
51 | " dc.b "XSTR(RTF_AUTOINIT)" \n"
52 | " dc.b "XSTR(DEVICE_VERSION)" \n"
53 | " dc.b "XSTR(NT_DEVICE)" \n"
54 | " dc.b "XSTR(DEVICE_PRIORITY)" \n"
55 | " dc.l _device_name \n"
56 | " dc.l _device_id_string \n"
57 | " dc.l _auto_init_tables \n"
58 | "endcode: \n");
59 |
60 | char device_name[] = DEVICE_NAME;
61 | char device_id_string[] = DEVICE_ID_STRING;
62 |
63 | /*------- init_device ---------------------------------------
64 | FOR RTF_AUTOINIT:
65 | This routine gets called after the device has been allocated.
66 | The device pointer is in d0. The AmigaDOS segment list is in a0.
67 | If it returns the device pointer, then the device will be linked
68 | into the device list. If it returns NULL, then the device
69 | will be unloaded.
70 |
71 | IMPORTANT:
72 | If you don't use the "RTF_AUTOINIT" feature, there is an additional
73 | caveat. If you allocate memory in your Open function, remember that
74 | allocating memory can cause an Expunge... including an expunge of your
75 | device. This must not be fatal. The easy solution is don't add your
76 | device to the list until after it is ready for action.
77 |
78 | CAUTION:
79 | This function runs in a forbidden state !!!
80 | This call is single-threaded by Exec
81 | ------------------------------------------------------------*/
82 | static struct Library __attribute__((used)) * init_device(BPTR seg_list asm("a0"), struct Library *dev asm("d0"))
83 | {
84 | #if DEBUG
85 | KPrintF((CONST_STRPTR) "running init_device()\n");
86 | #endif
87 |
88 | /* !!! required !!! save a pointer to exec */
89 | SysBase = *(struct ExecBase **)4UL;
90 |
91 | /* save pointer to our loaded code (the SegList) */
92 | saved_seg_list = seg_list;
93 |
94 | dev->lib_Node.ln_Type = NT_DEVICE;
95 | dev->lib_Node.ln_Name = device_name;
96 | dev->lib_Flags = LIBF_SUMUSED | LIBF_CHANGED;
97 | dev->lib_Version = DEVICE_VERSION;
98 | dev->lib_Revision = DEVICE_REVISION;
99 | dev->lib_IdString = (APTR)device_id_string;
100 |
101 | is_open = FALSE;
102 |
103 | return dev;
104 | }
105 |
106 | /* device dependent expunge function
107 | !!! CAUTION: This function runs in a forbidden state !!!
108 | This call is guaranteed to be single-threaded; only one task
109 | will execute your Expunge at a time. */
110 | static BPTR __attribute__((used)) expunge(struct Library *dev asm("a6"))
111 | {
112 | #if DEBUG
113 | KPrintF((CONST_STRPTR) "running expunge()\n");
114 | #endif
115 |
116 | if (dev->lib_OpenCnt != 0)
117 | {
118 | dev->lib_Flags |= LIBF_DELEXP;
119 | return 0;
120 | }
121 |
122 | //xyz_shutdown();
123 |
124 | BPTR seg_list = saved_seg_list;
125 | Remove(&dev->lib_Node);
126 | FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize);
127 | return seg_list;
128 | }
129 |
130 | /* device dependent open function
131 | !!! CAUTION: This function runs in a forbidden state !!!
132 | This call is guaranteed to be single-threaded; only one task
133 | will execute your Open at a time. */
134 | static void __attribute__((used)) open(struct Library *dev asm("a6"), struct IORequest *ioreq asm("a1"), ULONG unitnum asm("d0"), ULONG flags asm("d1"))
135 | {
136 | #if DEBUG
137 | KPrintF((CONST_STRPTR) "running open()\n");
138 | #endif
139 |
140 | ioreq->io_Error = IOERR_OPENFAIL;
141 | ioreq->io_Message.mn_Node.ln_Type = NT_REPLYMSG;
142 |
143 | if (unitnum != 0)
144 | return;
145 |
146 | if (!is_open)
147 | {
148 | //initialize and open here
149 |
150 | //xyz_initialize();
151 | //if (xy_open() != 0)
152 | // return;
153 |
154 | is_open = TRUE;
155 | }
156 |
157 | dev->lib_OpenCnt++;
158 | ioreq->io_Error = 0; //Success
159 | }
160 |
161 | /* device dependent close function
162 | !!! CAUTION: This function runs in a forbidden state !!!
163 | This call is guaranteed to be single-threaded; only one task
164 | will execute your Close at a time. */
165 | static BPTR __attribute__((used)) close(struct Library *dev asm("a6"), struct IORequest *ioreq asm("a1"))
166 | {
167 | #if DEBUG
168 | KPrintF((CONST_STRPTR) "running close()\n");
169 | #endif
170 |
171 | ioreq->io_Device = NULL;
172 | ioreq->io_Unit = NULL;
173 |
174 | dev->lib_OpenCnt--;
175 |
176 | if (dev->lib_OpenCnt == 0 && (dev->lib_Flags & LIBF_DELEXP))
177 | return expunge(dev);
178 |
179 | return 0;
180 | }
181 |
182 | /* device dependent beginio function */
183 | static void __attribute__((used)) begin_io(struct Library *dev asm("a6"), struct IORequest *ioreq asm("a1"))
184 | {
185 | #if DEBUG
186 | KPrintF((CONST_STRPTR) "running begin_io()\n");
187 | #endif
188 | }
189 |
190 | /* device dependent abortio function */
191 | static ULONG __attribute__((used)) abort_io(struct Library *dev asm("a6"), struct IORequest *ioreq asm("a1"))
192 | {
193 | #if DEBUG
194 | KPrintF((CONST_STRPTR) "running abort_io()\n");
195 | #endif
196 |
197 | return IOERR_NOCMD;
198 | }
199 |
200 | static ULONG device_vectors[] =
201 | {
202 | (ULONG)open,
203 | (ULONG)close,
204 | (ULONG)expunge,
205 | 0, //extFunc not used here
206 | (ULONG)begin_io,
207 | (ULONG)abort_io,
208 | -1}; //function table end marker
209 |
210 | /*-----------------------------------------------------------
211 | The romtag specified that we were "RTF_AUTOINIT". This means
212 | that the RT_INIT structure member points to one of these
213 | tables below. If the AUTOINIT bit was not set then RT_INIT
214 | would point to a routine to run.
215 |
216 | MyDev_Sizeof data space size
217 | device_vectors pointer to function initializers
218 | dataTable pointer to data initializers
219 | init_device routine to run
220 | ------------------------------------------------------------*/
221 | const ULONG auto_init_tables[4] =
222 | {
223 | sizeof(struct Library),
224 | (ULONG)device_vectors,
225 | 0,
226 | (ULONG)init_device};
--------------------------------------------------------------------------------
/images/VSCode_on_linux.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbilander/SimpleDevice/37a3f0dcc42e02da48195d8860cad6794db3d797/images/VSCode_on_linux.jpg
--------------------------------------------------------------------------------
/images/VSCode_on_windows_with_MSYS2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbilander/SimpleDevice/37a3f0dcc42e02da48195d8860cad6794db3d797/images/VSCode_on_windows_with_MSYS2.jpg
--------------------------------------------------------------------------------
/images/copy_driver_to_devs_with_dopus.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbilander/SimpleDevice/37a3f0dcc42e02da48195d8860cad6794db3d797/images/copy_driver_to_devs_with_dopus.jpg
--------------------------------------------------------------------------------
/images/disassembly_screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbilander/SimpleDevice/37a3f0dcc42e02da48195d8860cad6794db3d797/images/disassembly_screenshot.jpg
--------------------------------------------------------------------------------
/images/mapping_device_name_in device_list.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbilander/SimpleDevice/37a3f0dcc42e02da48195d8860cad6794db3d797/images/mapping_device_name_in device_list.jpg
--------------------------------------------------------------------------------
/images/project_folder_mapped_as_drive_in_WinUAE.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbilander/SimpleDevice/37a3f0dcc42e02da48195d8860cad6794db3d797/images/project_folder_mapped_as_drive_in_WinUAE.jpg
--------------------------------------------------------------------------------
/images/project_folder_shown_in_dopus.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbilander/SimpleDevice/37a3f0dcc42e02da48195d8860cad6794db3d797/images/project_folder_shown_in_dopus.jpg
--------------------------------------------------------------------------------
/images/telnet_kprintf_output.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbilander/SimpleDevice/37a3f0dcc42e02da48195d8860cad6794db3d797/images/telnet_kprintf_output.jpg
--------------------------------------------------------------------------------
/images/telnet_to_WinUAE_emulated_serial_port.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbilander/SimpleDevice/37a3f0dcc42e02da48195d8860cad6794db3d797/images/telnet_to_WinUAE_emulated_serial_port.jpg
--------------------------------------------------------------------------------