1 /*
2 * SPDX-License-Identifier: GPL-2.0-or-later
3 *
4 * Copyright (C) 2020-2021 The DOSBox Staging Team
5 * Copyright (C) 2002-2021 The DOSBox Team
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include "inout.h"
23
24 #include <cassert>
25 #include <limits>
26 #include <cstring>
27 #include <unordered_map>
28
29 #include "setup.h"
30 #include "cpu.h"
31 #include "../src/cpu/lazyflags.h"
32 #include "callback.h"
33
34 //#define ENABLE_PORTLOG
35
36 // type-sized IO handler containers
37 extern std::unordered_map<io_port_t, io_read_f> io_read_handlers[io_widths];
38 extern std::unordered_map<io_port_t, io_write_f> io_write_handlers[io_widths];
39
40 // type-sized IO handler API
41 uint8_t read_byte_from_port(const io_port_t port);
42 uint16_t read_word_from_port(const io_port_t port);
43 uint32_t read_dword_from_port(const io_port_t port);
44 void write_byte_to_port(const io_port_t port, const uint8_t val);
45 void write_word_to_port(const io_port_t port, const uint16_t val);
46 void write_dword_to_port(const io_port_t port, const uint32_t val);
47
48
49 struct IOF_Entry {
50 Bitu cs;
51 Bitu eip;
52 };
53
54 #define IOF_QUEUESIZE 16
55 static struct {
56 Bitu used;
57 IOF_Entry entries[IOF_QUEUESIZE];
58 } iof_queue;
59
IOFaultCore(void)60 static Bits IOFaultCore(void) {
61 CPU_CycleLeft+=CPU_Cycles;
62 CPU_Cycles=1;
63 Bits ret=CPU_Core_Full_Run();
64 CPU_CycleLeft+=CPU_Cycles;
65 if (ret<0) E_Exit("Got a dosbox close machine in IO-fault core?");
66 if (ret)
67 return ret;
68 if (!iof_queue.used) E_Exit("IO-faul Core without IO-faul");
69 IOF_Entry * entry=&iof_queue.entries[iof_queue.used-1];
70 if (entry->cs == SegValue(cs) && entry->eip==reg_eip)
71 return -1;
72 return 0;
73 }
74
75
76 /* Some code to make io operations take some virtual time. Helps certain
77 * games with their timing of certain operations
78 */
79
80 constexpr double IODELAY_READ_MICROS = 1.0;
81 constexpr double IODELAY_WRITE_MICROS = 0.75;
82
83 constexpr int32_t IODELAY_READ_MICROSk = static_cast<int32_t>(
84 1024 / IODELAY_READ_MICROS);
85 constexpr int32_t IODELAY_WRITE_MICROSk = static_cast<int32_t>(
86 1024 / IODELAY_WRITE_MICROS);
87
IO_USEC_read_delay()88 inline void IO_USEC_read_delay() {
89 auto delaycyc = CPU_CycleMax / IODELAY_READ_MICROSk;
90 if (GCC_UNLIKELY(delaycyc > CPU_Cycles))
91 delaycyc = CPU_Cycles;
92 CPU_Cycles -= delaycyc;
93 CPU_IODelayRemoved += delaycyc;
94 }
95
IO_USEC_write_delay()96 inline void IO_USEC_write_delay() {
97 auto delaycyc = CPU_CycleMax / IODELAY_WRITE_MICROSk;
98 if (GCC_UNLIKELY(delaycyc > CPU_Cycles))
99 delaycyc = CPU_Cycles;
100 CPU_Cycles -= delaycyc;
101 CPU_IODelayRemoved += delaycyc;
102 }
103
104 #ifdef ENABLE_PORTLOG
105 static Bit8u crtc_index = 0;
106
log_io(io_width_t width,bool write,io_port_t port,io_val_t val)107 void log_io(io_width_t width, bool write, io_port_t port, io_val_t val)
108 {
109 switch(width) {
110 case io_width_t::byte: val &= 0xff; break;
111 case io_width_t::word: val &= 0xffff; break;
112 }
113 if (write) {
114 // skip the video cursor position spam
115 if (port==0x3d4) {
116 if (width==io_width_t::byte) crtc_index = (Bit8u)val;
117 else if(width==io_width_t::word) crtc_index = (Bit8u)(val>>8);
118 }
119 if (crtc_index==0xe || crtc_index==0xf) {
120 if((width==io_width_t::byte && (port==0x3d4 || port==0x3d5))||(width==io_width_t::word && port==0x3d4))
121 return;
122 }
123
124 switch(port) {
125 //case 0x020: // interrupt command
126 //case 0x040: // timer 0
127 //case 0x042: // timer 2
128 //case 0x043: // timer control
129 //case 0x061: // speaker control
130 case 0x3c8: // VGA palette
131 case 0x3c9: // VGA palette
132 // case 0x3d4: // VGA crtc
133 // case 0x3d5: // VGA crtc
134 // case 0x3c4: // VGA seq
135 // case 0x3c5: // VGA seq
136 break;
137 default:
138 LOG_MSG("IOSBUS: write width=%u bytes, % 4x % 4x, cs:ip %04x:%04x",
139 static_cast<uint8_t>(width), port, val, SegValue(cs), reg_eip);
140 break;
141 }
142 } else {
143 switch(port) {
144 //case 0x021: // interrupt status
145 //case 0x040: // timer 0
146 //case 0x042: // timer 2
147 //case 0x061: // speaker control
148 case 0x201: // joystick status
149 case 0x3c9: // VGA palette
150 // case 0x3d4: // VGA crtc index
151 // case 0x3d5: // VGA crtc
152 case 0x3da: // display status - a real spammer
153 // don't log for the above cases
154 break;
155 default:
156 LOG_MSG("IOBUS: read width=%u bytes % 4x % 4x,\t\tcs:ip %04x:%04x",
157 static_cast<uint8_t>(width), port, val, SegValue(cs), reg_eip);
158 break;
159 }
160 }
161 }
162 #else
163 #define log_io(W, X, Y, Z)
164 #endif
165
IO_WriteB(io_port_t port,uint8_t val)166 void IO_WriteB(io_port_t port, uint8_t val)
167 {
168 log_io(io_width_t::byte, true, port, val);
169 if (GCC_UNLIKELY(GETFLAG(VM) && (CPU_IO_Exception(port,1)))) {
170 LazyFlags old_lflags;
171 memcpy(&old_lflags,&lflags,sizeof(LazyFlags));
172 CPU_Decoder * old_cpudecoder;
173 old_cpudecoder=cpudecoder;
174 cpudecoder=&IOFaultCore;
175 IOF_Entry * entry=&iof_queue.entries[iof_queue.used++];
176 entry->cs=SegValue(cs);
177 entry->eip=reg_eip;
178 CPU_Push16(SegValue(cs));
179 CPU_Push16(reg_ip);
180 Bit8u old_al = reg_al;
181 Bit16u old_dx = reg_dx;
182 reg_al = val;
183 reg_dx = port;
184 RealPt icb = CALLBACK_RealPointer(call_priv_io);
185 SegSet16(cs,RealSeg(icb));
186 reg_eip = RealOff(icb)+0x08;
187 CPU_Exception(cpu.exception.which,cpu.exception.error);
188
189 DOSBOX_RunMachine();
190 iof_queue.used--;
191
192 reg_al = old_al;
193 reg_dx = old_dx;
194 memcpy(&lflags,&old_lflags,sizeof(LazyFlags));
195 cpudecoder=old_cpudecoder;
196 }
197 else {
198 IO_USEC_write_delay();
199 write_byte_to_port(port, val);
200 }
201 }
202
IO_WriteW(io_port_t port,uint16_t val)203 void IO_WriteW(io_port_t port, uint16_t val)
204 {
205 log_io(io_width_t::word, true, port, val);
206 if (GCC_UNLIKELY(GETFLAG(VM) && (CPU_IO_Exception(port,2)))) {
207 LazyFlags old_lflags;
208 memcpy(&old_lflags,&lflags,sizeof(LazyFlags));
209 CPU_Decoder * old_cpudecoder;
210 old_cpudecoder=cpudecoder;
211 cpudecoder=&IOFaultCore;
212 IOF_Entry * entry=&iof_queue.entries[iof_queue.used++];
213 entry->cs=SegValue(cs);
214 entry->eip=reg_eip;
215 CPU_Push16(SegValue(cs));
216 CPU_Push16(reg_ip);
217 Bit16u old_ax = reg_ax;
218 Bit16u old_dx = reg_dx;
219 reg_ax = val;
220 reg_dx = port;
221 RealPt icb = CALLBACK_RealPointer(call_priv_io);
222 SegSet16(cs,RealSeg(icb));
223 reg_eip = RealOff(icb)+0x0a;
224 CPU_Exception(cpu.exception.which,cpu.exception.error);
225
226 DOSBOX_RunMachine();
227 iof_queue.used--;
228
229 reg_ax = old_ax;
230 reg_dx = old_dx;
231 memcpy(&lflags,&old_lflags,sizeof(LazyFlags));
232 cpudecoder=old_cpudecoder;
233 }
234 else {
235 IO_USEC_write_delay();
236 write_word_to_port(port, val);
237 }
238 }
239
IO_WriteD(io_port_t port,uint32_t val)240 void IO_WriteD(io_port_t port, uint32_t val)
241 {
242 log_io(io_width_t::dword, true, port, val);
243 if (GCC_UNLIKELY(GETFLAG(VM) && (CPU_IO_Exception(port,4)))) {
244 LazyFlags old_lflags;
245 memcpy(&old_lflags,&lflags,sizeof(LazyFlags));
246 CPU_Decoder * old_cpudecoder;
247 old_cpudecoder=cpudecoder;
248 cpudecoder=&IOFaultCore;
249 IOF_Entry * entry=&iof_queue.entries[iof_queue.used++];
250 entry->cs=SegValue(cs);
251 entry->eip=reg_eip;
252 CPU_Push16(SegValue(cs));
253 CPU_Push16(reg_ip);
254 Bit32u old_eax = reg_eax;
255 Bit16u old_dx = reg_dx;
256 reg_eax = val;
257 reg_dx = port;
258 RealPt icb = CALLBACK_RealPointer(call_priv_io);
259 SegSet16(cs,RealSeg(icb));
260 reg_eip = RealOff(icb)+0x0c;
261 CPU_Exception(cpu.exception.which,cpu.exception.error);
262
263 DOSBOX_RunMachine();
264 iof_queue.used--;
265
266 reg_eax = old_eax;
267 reg_dx = old_dx;
268 memcpy(&lflags,&old_lflags,sizeof(LazyFlags));
269 cpudecoder=old_cpudecoder;
270 } else {
271 write_dword_to_port(port, val);
272 }
273 }
274
IO_ReadB(io_port_t port)275 uint8_t IO_ReadB(io_port_t port)
276 {
277 uint8_t retval;
278 if (GCC_UNLIKELY(GETFLAG(VM) && (CPU_IO_Exception(port,1)))) {
279 LazyFlags old_lflags;
280 memcpy(&old_lflags,&lflags,sizeof(LazyFlags));
281 CPU_Decoder * old_cpudecoder;
282 old_cpudecoder=cpudecoder;
283 cpudecoder=&IOFaultCore;
284 IOF_Entry * entry=&iof_queue.entries[iof_queue.used++];
285 entry->cs=SegValue(cs);
286 entry->eip=reg_eip;
287 CPU_Push16(SegValue(cs));
288 CPU_Push16(reg_ip);
289 Bit8u old_al = reg_al;
290 Bit16u old_dx = reg_dx;
291 reg_dx = port;
292 RealPt icb = CALLBACK_RealPointer(call_priv_io);
293 SegSet16(cs,RealSeg(icb));
294 reg_eip = RealOff(icb)+0x00;
295 CPU_Exception(cpu.exception.which,cpu.exception.error);
296
297 DOSBOX_RunMachine();
298 iof_queue.used--;
299
300 retval = reg_al;
301 reg_al = old_al;
302 reg_dx = old_dx;
303 memcpy(&lflags,&old_lflags,sizeof(LazyFlags));
304 cpudecoder=old_cpudecoder;
305 return retval;
306 }
307 else {
308 IO_USEC_read_delay();
309 retval = read_byte_from_port(port);
310 }
311 log_io(io_width_t::byte, false, port, retval);
312 return retval;
313 }
314
IO_ReadW(io_port_t port)315 uint16_t IO_ReadW(io_port_t port)
316 {
317 uint16_t retval;
318 if (GCC_UNLIKELY(GETFLAG(VM) && (CPU_IO_Exception(port,2)))) {
319 LazyFlags old_lflags;
320 memcpy(&old_lflags,&lflags,sizeof(LazyFlags));
321 CPU_Decoder * old_cpudecoder;
322 old_cpudecoder=cpudecoder;
323 cpudecoder=&IOFaultCore;
324 IOF_Entry * entry=&iof_queue.entries[iof_queue.used++];
325 entry->cs=SegValue(cs);
326 entry->eip=reg_eip;
327 CPU_Push16(SegValue(cs));
328 CPU_Push16(reg_ip);
329 Bit16u old_ax = reg_ax;
330 Bit16u old_dx = reg_dx;
331 reg_dx = port;
332 RealPt icb = CALLBACK_RealPointer(call_priv_io);
333 SegSet16(cs,RealSeg(icb));
334 reg_eip = RealOff(icb)+0x02;
335 CPU_Exception(cpu.exception.which,cpu.exception.error);
336
337 DOSBOX_RunMachine();
338 iof_queue.used--;
339
340 retval = reg_ax;
341 reg_ax = old_ax;
342 reg_dx = old_dx;
343 memcpy(&lflags,&old_lflags,sizeof(LazyFlags));
344 cpudecoder=old_cpudecoder;
345 }
346 else {
347 IO_USEC_read_delay();
348 retval = read_word_from_port(port);
349 }
350 log_io(io_width_t::word, false, port, retval);
351 return retval;
352 }
353
IO_ReadD(io_port_t port)354 uint32_t IO_ReadD(io_port_t port)
355 {
356 uint32_t retval;
357 if (GCC_UNLIKELY(GETFLAG(VM) && (CPU_IO_Exception(port,4)))) {
358 LazyFlags old_lflags;
359 memcpy(&old_lflags,&lflags,sizeof(LazyFlags));
360 CPU_Decoder * old_cpudecoder;
361 old_cpudecoder=cpudecoder;
362 cpudecoder=&IOFaultCore;
363 IOF_Entry * entry=&iof_queue.entries[iof_queue.used++];
364 entry->cs=SegValue(cs);
365 entry->eip=reg_eip;
366 CPU_Push16(SegValue(cs));
367 CPU_Push16(reg_ip);
368 Bit32u old_eax = reg_eax;
369 Bit16u old_dx = reg_dx;
370 reg_dx = port;
371 RealPt icb = CALLBACK_RealPointer(call_priv_io);
372 SegSet16(cs,RealSeg(icb));
373 reg_eip = RealOff(icb)+0x04;
374 CPU_Exception(cpu.exception.which,cpu.exception.error);
375
376 DOSBOX_RunMachine();
377 iof_queue.used--;
378
379 retval = reg_eax;
380 reg_eax = old_eax;
381 reg_dx = old_dx;
382 memcpy(&lflags,&old_lflags,sizeof(LazyFlags));
383 cpudecoder=old_cpudecoder;
384 } else {
385 retval = read_dword_from_port(port);
386 }
387
388 log_io(io_width_t::dword, false, port, retval);
389 return retval;
390 }
391
392
393 class IO final : public Module_base {
394 public:
IO(Section * configuration)395 IO(Section* configuration):Module_base(configuration){
396 iof_queue.used = 0;
397 }
~IO()398 ~IO()
399 {
400 [[maybe_unused]] size_t total_bytes = 0u;
401 for (uint8_t i = 0; i < io_widths; ++i) {
402 const auto readers = io_read_handlers[i].size();
403 const auto writers = io_write_handlers[i].size();
404 DEBUG_LOG_MSG("IOBUS: Releasing %d read and %d write %d-bit port handlers",
405 static_cast<int>(readers), static_cast<int>(writers), 8 << i);
406
407 total_bytes += readers * sizeof(io_read_f) + sizeof(io_read_handlers[i]);
408 total_bytes += writers * sizeof(io_write_f) + sizeof(io_write_handlers[i]);
409 io_read_handlers[i].clear();
410 io_write_handlers[i].clear();
411 }
412 DEBUG_LOG_MSG("IOBUS: Handlers consumed %d total bytes",
413 static_cast<int>(total_bytes));
414 }
415 };
416
417 static IO* test;
418
IO_Destroy(Section *)419 void IO_Destroy(Section*) {
420 delete test;
421 }
422
IO_Init(Section * sect)423 void IO_Init(Section * sect) {
424 test = new IO(sect);
425 sect->AddDestroyFunction(&IO_Destroy);
426 }
427