codegen: move common code to gen_frame_set_pointer_2
[ajla.git] / os_dos.c
blob2a52178f9ef5ddaf4716fbb857ab236bab76d2c0
1 /*
2 * Copyright (C) 2024 Mikulas Patocka
4 * This file is part of Ajla.
6 * Ajla is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
9 * version.
11 * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * Ajla. If not, see <https://www.gnu.org/licenses/>.
19 #include "ajla.h"
21 #ifdef OS_DOS
23 #include "mem_al.h"
24 #include "str.h"
26 #include "os.h"
28 #include <unistd.h>
29 #include <conio.h>
30 #include <bios.h>
31 #include <dpmi.h>
32 #include <process.h>
33 #include <dir.h>
34 /*#include <sys/farptr.h>*/
36 #ifndef wake_up_wait_list
37 void u_name(wake_up_wait_list)(struct list *wait_list, mutex_t *mutex_to_lock, bool can_allocate_memory);
38 void c_name(wake_up_wait_list)(struct list *wait_list, mutex_t *mutex_to_lock, bool can_allocate_memory);
39 #endif
41 static bool have_ntvdm;
42 static int dos_charset;
43 static struct console_read_packet pkt;
44 static bool have_pkt;
45 static struct list packet_wait_list;
46 static mutex_t packet_mutex;
48 static int8_t dos_mouse_initialized;
49 static uint8_t dos_mouse_n_buttons;
50 static unsigned short dos_mouse_last_x;
51 static unsigned short dos_mouse_last_y;
52 static unsigned short dos_mouse_last_buttons;
54 static struct console_read_packet *mouse_events;
55 size_t n_mouse_events;
57 static bool dos_mouse_init(void)
59 __dpmi_regs r;
61 if (dos_mouse_initialized)
62 return dos_mouse_initialized == 1;
64 memset(&r, 0, sizeof(__dpmi_regs));
65 __dpmi_int(0x33, &r);
66 if (r.x.ax != 0xffff) {
67 dos_mouse_initialized = -1;
68 return false;
70 dos_mouse_n_buttons = r.x.bx == 3 ? 3 : 2;
71 dos_mouse_initialized = 1;
73 memset(&r, 0, sizeof(__dpmi_regs));
74 r.x.ax = 7;
75 r.x.cx = 0;
76 r.x.dx = ScreenCols() * 8 - 1;
77 __dpmi_int(0x33, &r);
79 memset(&r, 0, sizeof(__dpmi_regs));
80 r.x.ax = 8;
81 r.x.cx = 0;
82 r.x.dx = ScreenRows() * 8 - 1;
83 __dpmi_int(0x33, &r);
85 r.x.ax = 3;
86 __dpmi_int(0x33, &r);
87 dos_mouse_last_x = r.x.cx / 8;
88 dos_mouse_last_y = r.x.dx / 8;
89 dos_mouse_last_buttons = r.x.bx;
91 return true;
94 static void dos_enqueue_mouse_packet(unsigned short x, unsigned short y, unsigned short buttons)
96 ajla_error_t sink;
97 struct console_read_packet mouse_packet;
98 void *errp;
100 memset(&mouse_packet, 0, sizeof(struct console_read_packet));
101 mouse_packet.type = 2;
102 mouse_packet.u.m.x = x;
103 mouse_packet.u.m.y = y;
104 mouse_packet.u.m.prev_buttons = dos_mouse_last_buttons;
105 mouse_packet.u.m.buttons = buttons;
107 if (n_mouse_events) {
108 struct console_read_packet *last = &mouse_events[n_mouse_events - 1];
109 if (last->u.m.buttons == last->u.m.prev_buttons)
110 n_mouse_events--;
113 if (unlikely(!array_add_mayfail(struct console_read_packet, &mouse_events, &n_mouse_events, mouse_packet, &errp, &sink))) {
114 mouse_events = errp;
115 return;
118 dos_mouse_last_x = x;
119 dos_mouse_last_y = y;
120 dos_mouse_last_buttons = buttons;
123 static void dos_mouse_poll(void)
125 size_t i;
126 unsigned short x, y;
127 __dpmi_regs r;
129 for (i = 0; i < dos_mouse_n_buttons; i++) {
130 memset(&r, 0, sizeof(__dpmi_regs));
131 r.x.ax = !(dos_mouse_last_buttons & (1 << i)) ? 5 : 6;
132 r.x.bx = i;
133 __dpmi_int(0x33, &r);
134 if (r.x.bx)
135 dos_enqueue_mouse_packet(r.x.cx / 8, r.x.dx / 8, dos_mouse_last_buttons ^ (1 << i));
138 memset(&r, 0, sizeof(__dpmi_regs));
139 r.x.ax = 3;
140 __dpmi_int(0x33, &r);
141 x = r.x.cx / 8;
142 y = r.x.dx / 8;
144 if (x != dos_mouse_last_x || y != dos_mouse_last_y)
145 dos_enqueue_mouse_packet(x, y, dos_mouse_last_buttons);
148 static void dos_mouse_set_visibility(bool visible)
150 if (dos_mouse_initialized == 1) {
151 __dpmi_regs r;
152 memset(&r, 0, sizeof(__dpmi_regs));
153 r.x.ax = visible ? 1 : 2;
154 __dpmi_int(0x33, &r);
158 bool dos_poll_devices(void)
160 if (dos_mouse_initialized == 1) {
161 dos_mouse_poll();
164 if (!list_is_empty(&packet_wait_list) && !have_pkt) {
165 int k;
167 if (dos_mouse_init() && n_mouse_events) {
168 mutex_lock(&packet_mutex);
169 memcpy(&pkt, mouse_events, sizeof(struct console_read_packet));
170 have_pkt = true;
171 memmove(mouse_events, mouse_events + 1, (n_mouse_events - 1) * sizeof(struct console_read_packet));
172 n_mouse_events--;
173 call(wake_up_wait_list)(&packet_wait_list, &packet_mutex, true);
174 return true;
177 if (!bioskey(0x11)) {
179 * This is very strange. It fixes some lockups in NTVDM
180 * if we are holding a key for a few minutes
182 if (have_ntvdm) {
183 mutex_lock(&packet_mutex);
184 call(wake_up_wait_list)(&packet_wait_list, &packet_mutex, true);
186 return false;
188 k = bioskey(0x10);
189 /*uint16_t head, tail;
190 __asm__("cli");
191 head = _farpeekw(0x40, 0x1a);
192 tail = _farpeekw(0x40, 0x1c);
193 if (head == tail) {
194 __asm__("sti");
195 mutex_lock(&packet_mutex);
196 call(wake_up_wait_list)(&packet_wait_list, &packet_mutex, true);
197 return false;
199 k = _farpeekw(0x40, head);
200 head += 2;
201 if (head == _farpeekw(0x40, 0x82))
202 head = _farpeekw(0x40, 0x80);
203 _farpokew(0x40, 0x1a, head);
204 __asm__("sti");*/
205 /*k = 0x50e0;*/
206 /*debug("bioskey: %04x", k);*/
207 mutex_lock(&packet_mutex);
208 memset(&pkt, 0, sizeof(struct console_read_packet));
209 pkt.type = 1;
210 pkt.u.k.vkey = (k >> 8) & 0xff;
211 pkt.u.k.key = k & 0xff;
212 pkt.u.k.cp = dos_charset;
213 have_pkt = true;
214 call(wake_up_wait_list)(&packet_wait_list, &packet_mutex, true);
215 return true;
217 return false;
220 void dos_yield(void)
222 __dpmi_yield();
225 int os_charset(void)
227 return dos_charset;
230 void dos_wait_on_packet(mutex_t **mutex_to_lock, struct list *list_entry)
232 *mutex_to_lock = &packet_mutex;
233 mutex_lock(&packet_mutex);
234 list_add(&packet_wait_list, list_entry);
235 if (unlikely(have_pkt)) {
236 goto wake_up;
238 mutex_unlock(&packet_mutex);
239 return;
241 wake_up:
242 call(wake_up_wait_list)(&packet_wait_list, &packet_mutex, true);
245 ssize_t os_read_console_packet(handle_t attr_unused h, struct console_read_packet *result, ajla_error_t attr_unused *err)
247 if (have_pkt) {
248 memcpy(result, &pkt, sizeof(struct console_read_packet));
249 have_pkt = 0;
250 return 1;
252 return OS_RW_WOULDBLOCK;
255 bool os_write_console_packet(handle_t attr_unused h, struct console_write_packet attr_unused *packet, ajla_error_t *err)
257 bool mouse_hidden = false;
258 uint8_t *buffer = NULL;
259 size_t buffer_chars = 0;
260 if (unlikely(!isatty(h))) {
261 fatal_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_INVALID_OPERATION), err, "attempting to use packet console on non-console");
262 goto fail;
265 next:
266 switch (packet->type) {
267 case 1: {
268 break;
270 case 2: {
271 int x, y;
272 unsigned n_chars, i;
273 if (!mouse_hidden) {
274 dos_mouse_set_visibility(false);
275 mouse_hidden = true;
277 x = packet->u.c.x;
278 y = packet->u.c.y;
279 n_chars = packet->u.c.n_chars;
280 if (!buffer || n_chars > buffer_chars) {
281 if (unlikely(buffer != NULL))
282 mem_free(buffer);
283 buffer_chars = maximum(maximum(n_chars, buffer_chars * 2), 80);
284 buffer = mem_alloc_mayfail(uint8_t *, buffer_chars * 2, err);
285 if (unlikely(!buffer))
286 goto fail;
288 for (i = 0; i < n_chars; i++) {
289 buffer[i * 2] = packet->u.c.data[i * 2];
290 buffer[i * 2 + 1] = packet->u.c.data[i * 2 + 1];
292 if (n_chars) {
293 if (unlikely(!puttext(x + 1, y + 1, x + n_chars, y + 1, buffer))) {
294 fatal_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_INVALID_OPERATION), err, "puttext failed");
295 goto fail;
298 packet = cast_ptr(struct console_write_packet *, &packet->u.c.data[packet->u.c.n_chars * 2]);
299 goto next;
301 case 3: {
302 gotoxy(packet->u.p.x + 1, packet->u.p.y + 1);
303 packet = cast_ptr(struct console_write_packet *, &packet->u.p.end);
304 goto next;
306 case 4: {
307 _setcursortype(packet->u.v.v ? _NORMALCURSOR : _NOCURSOR);
308 packet = cast_ptr(struct console_write_packet *, &packet->u.v.end);
309 goto next;
311 default: {
312 internal(file_line, "os_write_console_packet: invalid type %d", (int)packet->type);
313 break;
316 if (mouse_hidden)
317 dos_mouse_set_visibility(true);
318 if (unlikely(buffer != NULL))
319 mem_free(buffer);
320 return true;
322 fail:
323 if (mouse_hidden)
324 dos_mouse_set_visibility(true);
325 if (unlikely(buffer != NULL))
326 mem_free(buffer);
327 return false;
330 bool os_drives(char **drives, size_t *drives_l, ajla_error_t *err)
332 unsigned cd, n_drvs, i, j;
333 if (unlikely(!array_init_mayfail(char, drives, drives_l, err)))
334 return false;
335 _dos_getdrive(&cd);
336 for (i = 0; i < 26; i++) {
337 char str[4] = " :\\";
338 str[0] = 'A' + i;
339 _dos_setdrive(i + 1, &n_drvs);
340 _dos_getdrive(&j);
341 j--;
342 if (likely(j != i))
343 continue;
344 if (unlikely(!array_add_multiple_mayfail(char, drives, drives_l, str, 4, NULL, err))) {
345 _dos_setdrive(cd, &n_drvs);
346 return false;
349 _dos_setdrive(cd, &n_drvs);
350 return true;
353 struct proc_handle {
354 int status;
357 static void dos_revert_handles(int old_std_handles[3])
359 int i;
360 for (i = 0; i < 3; i++) {
361 if (old_std_handles[i] >= 0) {
362 int r;
363 if (unlikely(old_std_handles[i] == i))
364 continue;
365 EINTR_LOOP(r, dup2(old_std_handles[i], i));
366 if (r == -1) {
367 int er = errno;
368 fatal("can't copy file descriptor %d to %d: %s", old_std_handles[i], i, error_decode(error_from_errno(EC_SYSCALL, er)));
370 EINTR_LOOP(r, close(old_std_handles[i]));
375 struct proc_handle *os_proc_spawn(dir_handle_t wd, const char *path, size_t n_handles, handle_t *src, int *target, char * const args[], char *envc, ajla_error_t *err)
377 int r;
378 struct proc_handle *ph;
379 char **env;
380 size_t env_l;
381 int er;
382 int old_std_handles[3];
383 unsigned i;
385 if (unlikely(!array_init_mayfail(char *, &env, &env_l, err)))
386 return NULL;
387 while (*envc) {
388 if (unlikely(!array_add_mayfail(char *, &env, &env_l, envc, NULL, err)))
389 return NULL;
390 envc = strchr(envc, 0) + 1;
392 if (unlikely(!array_add_mayfail(char *, &env, &env_l, NULL, NULL, err)))
393 return NULL;
395 ph = mem_alloc_mayfail(struct proc_handle *, sizeof(struct proc_handle), err);
396 if (unlikely(!ph)) {
397 mem_free(env);
398 return NULL;
401 if (unlikely(!os_set_cwd(wd, err))) {
402 mem_free(env);
403 mem_free(ph);
404 return NULL;
407 old_std_handles[0] = old_std_handles[1] = old_std_handles[2] = -1;
408 for (i = 0; i < n_handles; i++) {
409 handle_t s = src[i];
410 int t = target[i];
411 int saved_t;
412 if (unlikely(t >= 3)) {
413 fatal_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_NOT_SUPPORTED), err, "only the first three handles can be redirected");
414 goto redir_error;
416 if (old_std_handles[t] != -1) {
417 fatal_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_NOT_SUPPORTED), err, "redirecting a handle multiple times");
418 goto redir_error;
420 if (likely(s == t)) {
421 old_std_handles[t] = s;
422 continue;
424 EINTR_LOOP(saved_t, dup(t));
425 if (unlikely(saved_t == -1)) {
426 ajla_error_t e = error_from_errno(EC_SYSCALL, errno);
427 fatal_mayfail(e, err, "can't save handle %d: %s", t, error_decode(e));
428 goto redir_error;
430 old_std_handles[t] = saved_t;
431 if (s < 3 && unlikely(old_std_handles[s] >= 0))
432 s = old_std_handles[s];
433 EINTR_LOOP(r, dup2(s, t));
434 if (unlikely(r == -1)) {
435 ajla_error_t e = error_from_errno(EC_SYSCALL, errno);
436 fatal_mayfail(e, err, "can't copy handle %d to %d: %s", s, t, error_decode(e));
437 redir_error:
438 os_set_original_cwd();
439 dos_revert_handles(old_std_handles);
440 mem_free(env);
441 mem_free(ph);
442 return NULL;
446 EINTR_LOOP(r, spawnve(P_WAIT, path, args, env));
447 er = errno;
448 ph->status = r;
450 os_set_original_cwd();
451 dos_revert_handles(old_std_handles);
452 mem_free(env);
454 if (r == -1) {
455 ajla_error_t e = error_from_errno(EC_SYSCALL, er);
456 fatal_mayfail(e, err, "can't spawn process '%s': %s", path, error_decode(e));
457 mem_free(ph);
458 return NULL;
461 return ph;
464 void os_proc_free_handle(struct proc_handle *ph)
466 mem_free(ph);
469 bool os_proc_register_wait(struct proc_handle *ph, mutex_t attr_unused **mutex_to_lock, struct list attr_unused *list_entry, int *status)
471 *status = ph->status;
472 return true;
475 void os_proc_check_all(void)
479 dir_handle_t os_dir_root(ajla_error_t *err)
481 char *d = str_dup(" :\\", -1, err);
482 if (unlikely(!d))
483 return NULL;
484 d[0] = getdisk() + 'A';
485 return d;
488 void dos_init(void)
490 __dpmi_regs r;
492 dos_mouse_initialized = 0;
493 array_init(struct console_read_packet, &mouse_events, &n_mouse_events);
495 memset(&r, 0, sizeof(__dpmi_regs));
496 r.x.ax = 0x3306;
497 __dpmi_int(0x21, &r);
498 have_ntvdm = r.h.bl == 0x5 && r.h.bh == 0x32;
500 memset(&r, 0, sizeof(__dpmi_regs));
501 r.x.ax = 0x6601;
502 __dpmi_int(0x21, &r);
503 if (!(r.x.flags & 1)) {
504 dos_charset = r.x.bx;
505 } else {
506 dos_charset = 437;
509 have_pkt = false;
510 list_init(&packet_wait_list);
511 mutex_init(&packet_mutex);
514 void dos_done(void)
516 ajla_assert_lo(list_is_empty(&packet_wait_list), (file_line, "dos_done: packet_wait_list is not empty"));
517 mutex_done(&packet_mutex);
518 mem_free(mouse_events);
521 #endif