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