aboutsummaryrefslogtreecommitdiff
path: root/src/drv/ps1/cd
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2025-07-07 13:22:53 +0200
committerXavier Del Campo Romero <xavi92@disroot.org>2025-11-11 00:08:15 +0100
commit7861a52adf92a083bb2aed4c35f98d8035dce032 (patch)
tree28cd3c40e4c878f730f5df3c1d93bdf91af490c3 /src/drv/ps1/cd
parent7fc48e9216ff809da5f8055a50b0be17628ef1df (diff)
downloadwnix-7861a52adf92a083bb2aed4c35f98d8035dce032.tar.gz
Setup project skeleton
Diffstat (limited to 'src/drv/ps1/cd')
-rw-r--r--src/drv/ps1/cd/CMakeLists.txt27
-rw-r--r--src/drv/ps1/cd/include/drv/ps1/cd.h28
-rw-r--r--src/drv/ps1/cd/private_include/drv/ps1/cd/regs.h66
-rw-r--r--src/drv/ps1/cd/private_include/drv/ps1/cd/routines.h37
-rw-r--r--src/drv/ps1/cd/private_include/drv/ps1/cd/types.h111
-rw-r--r--src/drv/ps1/cd/private_include/drv/ps1/cd/virt/types.h36
-rw-r--r--src/drv/ps1/cd/src/CMakeLists.txt33
-rw-r--r--src/drv/ps1/cd/src/ensure_event.c37
-rw-r--r--src/drv/ps1/cd/src/free.c23
-rw-r--r--src/drv/ps1/cd/src/getstat.c70
-rw-r--r--src/drv/ps1/cd/src/init.c42
-rw-r--r--src/drv/ps1/cd/src/next.c37
-rw-r--r--src/drv/ps1/cd/src/prv.c22
-rw-r--r--src/drv/ps1/cd/src/read.c239
-rw-r--r--src/drv/ps1/cd/src/readdebug253
-rw-r--r--src/drv/ps1/cd/src/send.c101
-rw-r--r--src/drv/ps1/cd/src/toseekl.c54
-rw-r--r--src/drv/ps1/cd/src/update.c47
-rw-r--r--src/drv/ps1/cd/src/virt/CMakeLists.txt25
-rw-r--r--src/drv/ps1/cd/src/virt/init.c41
-rw-r--r--src/drv/ps1/cd/src/write.c31
21 files changed, 1360 insertions, 0 deletions
diff --git a/src/drv/ps1/cd/CMakeLists.txt b/src/drv/ps1/cd/CMakeLists.txt
new file mode 100644
index 0000000..9b84541
--- /dev/null
+++ b/src/drv/ps1/cd/CMakeLists.txt
@@ -0,0 +1,27 @@
+# wnix, a Unix-like operating system for WebAssembly applications.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+add_library(drv_ps1_cd)
+add_subdirectory(src)
+target_include_directories(drv_ps1_cd PUBLIC include PRIVATE private_include)
+target_link_libraries(drv_ps1_cd
+ PUBLIC
+ c
+ drv_event
+ PRIVATE
+ drv_ps1_bios
+ drv_ps1_event
+)
diff --git a/src/drv/ps1/cd/include/drv/ps1/cd.h b/src/drv/ps1/cd/include/drv/ps1/cd.h
new file mode 100644
index 0000000..ff57622
--- /dev/null
+++ b/src/drv/ps1/cd/include/drv/ps1/cd.h
@@ -0,0 +1,28 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DRV_PS1_CD_H
+#define DRV_PS1_CD_H
+
+#include <drv/event.h>
+
+struct drv_ps1_cd *drv_ps1_cd_init(const struct drv_event *ev);
+int drv_ps1_cd_update(struct drv_ps1_cd *cd);
+void drv_ps1_cd_free(struct drv_ps1_cd *cd);
+
+#endif
diff --git a/src/drv/ps1/cd/private_include/drv/ps1/cd/regs.h b/src/drv/ps1/cd/private_include/drv/ps1/cd/regs.h
new file mode 100644
index 0000000..37b2d03
--- /dev/null
+++ b/src/drv/ps1/cd/private_include/drv/ps1/cd/regs.h
@@ -0,0 +1,66 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DRV_PS1_CD_REGS_H
+#define DRV_PS1_CD_REGS_H
+
+#include <stdint.h>
+
+struct cd_reg_status
+{
+ uint8_t index :2;
+ const uint8_t ADPBUSY :1, PRMEMPT :1, PRMWRDY :1, RSLRRDY :1, DRQSTS :1,
+ BUSYSTS :1;
+};
+
+struct cd_reg_cmd
+{
+ uint8_t cmd;
+};
+
+struct cd_reg_rsp
+{
+ uint8_t rsp;
+};
+
+struct cd_reg_param
+{
+ uint8_t param;
+};
+
+union cd_reg_if
+{
+ const struct
+ {
+ uint8_t response :3, :1, cmd_start :1, :3;
+ } r;
+
+ struct
+ {
+ uint8_t ack: 5, :1, CLRPRM :1, :1;
+ } w;
+};
+
+#define CD_REG(x) (0x1f801800 + (x))
+#define CD_REG_STATUS ((volatile struct cd_reg_status *)CD_REG(0))
+#define CD_REG_CMD ((volatile struct cd_reg_cmd *)CD_REG(1))
+#define CD_REG_RSP ((const volatile struct cd_reg_rsp *)CD_REG(1))
+#define CD_REG_PARAM ((volatile struct cd_reg_param *)CD_REG(2))
+#define CD_REG_IF ((volatile union cd_reg_if *)CD_REG(3))
+
+#endif
diff --git a/src/drv/ps1/cd/private_include/drv/ps1/cd/routines.h b/src/drv/ps1/cd/private_include/drv/ps1/cd/routines.h
new file mode 100644
index 0000000..0b756de
--- /dev/null
+++ b/src/drv/ps1/cd/private_include/drv/ps1/cd/routines.h
@@ -0,0 +1,37 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DRV_PS1_CD_ROUTINES_H
+#define DRV_PS1_CD_ROUTINES_H
+
+#include <drv/event.h>
+#include <drv/ps1/cd/types.h>
+#include <drv/ps1/bios.h>
+#include <sys/types.h>
+#include <stddef.h>
+
+int drv_ps1_cd_send(const struct cmd *cmd);
+int drv_ps1_cd_getstat(void);
+int drv_ps1_cd_read(void *buf, size_t n, off_t offset,
+ const struct drv_event_done *done, void *args);
+int drv_ps1_cd_write(const void *buf, size_t n,
+ const struct drv_event_done *done, void *args);
+int drv_ps1_cd_next(void);
+struct CdAsyncSeekL drv_ps1_cd_toseekl(unsigned i);
+
+#endif
diff --git a/src/drv/ps1/cd/private_include/drv/ps1/cd/types.h b/src/drv/ps1/cd/private_include/drv/ps1/cd/types.h
new file mode 100644
index 0000000..2d4ae51
--- /dev/null
+++ b/src/drv/ps1/cd/private_include/drv/ps1/cd/types.h
@@ -0,0 +1,111 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DRV_PS1_CD_TYPES_H
+#define DRV_PS1_CD_TYPES_H
+
+#include <drv/event.h>
+#include <sys/types.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct drv_ps1_cd
+{
+ bool available;
+ struct drv_event ev;
+};
+
+struct cmd
+{
+ enum
+ {
+ CMD_SYNC,
+ CMD_GETSTAT,
+ CMD_SETLOC,
+ } cmd;
+
+ union cmd_param
+ {
+ struct cmd_setloc
+ {
+ uint8_t min, sec, sect;
+ } setloc;
+ } params;
+};
+
+struct cd_prv_getstat
+{
+ union
+ {
+ struct
+ {
+ uint8_t invalid :1, motor :1, seek_error :1, id_error :1,
+ shell_open :1, reading :1, seeking :1, playing_cdda :1;
+ } bits;
+
+ uint8_t byte;
+ } status;
+};
+
+struct cd_prv_read
+{
+ int endevent, errevent;
+};
+
+struct cd_req_read
+{
+ void *buf;
+ size_t n;
+ off_t offset;
+};
+
+struct cd_req
+{
+ union
+ {
+ struct cd_req_read read;
+ } u;
+
+ int (*f)(void);
+ struct drv_event_done done;
+ struct cd_req *next;
+};
+
+enum {CD_SECTOR_SZ = 2048};
+
+struct cd_prv
+{
+ bool available, sector_read;
+ int (*next)(void);
+ int event;
+ struct cmd cmd;
+ struct cd_req *head, *tail;
+ char sector[CD_SECTOR_SZ];
+ off_t offset;
+
+ union
+ {
+ struct cd_prv_getstat getstat;
+ struct cd_prv_read read;
+ } u;
+};
+
+extern struct cd_prv drv_ps1_cd_prv;
+
+#endif
diff --git a/src/drv/ps1/cd/private_include/drv/ps1/cd/virt/types.h b/src/drv/ps1/cd/private_include/drv/ps1/cd/virt/types.h
new file mode 100644
index 0000000..59437c0
--- /dev/null
+++ b/src/drv/ps1/cd/private_include/drv/ps1/cd/virt/types.h
@@ -0,0 +1,36 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DRV_PS1_CD_VIRT_TYPES_H
+#define DRV_PS1_CD_VIRT_TYPES_H
+
+#include <drv/event.h>
+
+struct drv_ps1_cd
+{
+ struct drv_event ev;
+};
+
+struct cd_prv
+{
+ int (*next)(void);
+};
+
+extern struct cd_prv drv_ps1_cd_prv;
+
+#endif
diff --git a/src/drv/ps1/cd/src/CMakeLists.txt b/src/drv/ps1/cd/src/CMakeLists.txt
new file mode 100644
index 0000000..df5ecca
--- /dev/null
+++ b/src/drv/ps1/cd/src/CMakeLists.txt
@@ -0,0 +1,33 @@
+# wnix, a Unix-like operating system for WebAssembly applications.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+if(WNIX_VIRT_CD)
+ add_subdirectory(virt)
+else()
+ target_sources(drv_ps1_cd PRIVATE
+ free.c
+ getstat.c
+ init.c
+ next.c
+ prv.c
+ read.c
+ toseekl.c
+ update.c
+ write.c
+ )
+endif()
+
+target_link_libraries(drv_ps1_cd PRIVATE kprintf)
diff --git a/src/drv/ps1/cd/src/ensure_event.c b/src/drv/ps1/cd/src/ensure_event.c
new file mode 100644
index 0000000..592184a
--- /dev/null
+++ b/src/drv/ps1/cd/src/ensure_event.c
@@ -0,0 +1,37 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/bios.h>
+#include <drv/ps1/cd/types.h>
+#include <drv/ps1/event.h>
+#include <stddef.h>
+
+int drv_ps1_cd_ensure_event(struct cd_prv *const p)
+{
+ if (p->event)
+ return 0;
+
+ const int event = drv_ps1_event_open(CLASS_CDROM, SPEC_COMMAND_COMPLETE,
+ MODE_READY, NULL);
+
+ if (event == -1)
+ return -1;
+
+ drv_ps1_event_enable(p->event = event);
+ return 0;
+}
diff --git a/src/drv/ps1/cd/src/free.c b/src/drv/ps1/cd/src/free.c
new file mode 100644
index 0000000..ed6f347
--- /dev/null
+++ b/src/drv/ps1/cd/src/free.c
@@ -0,0 +1,23 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd.h>
+
+void drv_ps1_cd_free(struct drv_ps1_cd *const cd)
+{
+}
diff --git a/src/drv/ps1/cd/src/getstat.c b/src/drv/ps1/cd/src/getstat.c
new file mode 100644
index 0000000..34bdaf5
--- /dev/null
+++ b/src/drv/ps1/cd/src/getstat.c
@@ -0,0 +1,70 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd/routines.h>
+#include <drv/ps1/bios.h>
+#include <drv/ps1/event.h>
+#include <kprintf.h>
+
+static int wait_event(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ struct cd_prv_getstat *const g = &p->u.getstat;
+
+ if (drv_ps1_event_test(p->event))
+ {
+ p->available = !g->status.bits.shell_open;
+
+ if (!p->head)
+ return drv_ps1_cd_getstat();
+
+ return p->head->f();
+ }
+
+ return 0;
+}
+
+static int ensure_event(struct cd_prv *const p)
+{
+ if (p->event)
+ return 0;
+ else if ((p->event = drv_ps1_event_open(CLASS_CDROM, SPEC_COMMAND_COMPLETE,
+ MODE_READY, NULL)) == -1)
+ return -1;
+
+ drv_ps1_event_enable(p->event);
+ return 0;
+}
+
+int drv_ps1_cd_getstat(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ struct cd_prv_getstat *const g = &p->u.getstat;
+
+ /* TODO: CdAsyncGetStatus is *very* slow (~2.5 seconds / 10k calls) */
+ if (ensure_event(p))
+ return -1;
+ else if (!CdAsyncGetStatus(&g->status.byte))
+ {
+ kprintf("%s: CdAsyncGetStatus failed\n", __func__);
+ return -1;
+ }
+
+ p->next = wait_event;
+ return 0;
+}
diff --git a/src/drv/ps1/cd/src/init.c b/src/drv/ps1/cd/src/init.c
new file mode 100644
index 0000000..b452e82
--- /dev/null
+++ b/src/drv/ps1/cd/src/init.c
@@ -0,0 +1,42 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd.h>
+#include <drv/ps1/cd/routines.h>
+#include <drv/ps1/cd/types.h>
+#include <drv/event.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+struct drv_ps1_cd *drv_ps1_cd_init(const struct drv_event *const ev)
+{
+ struct drv_ps1_cd *const ret = malloc(sizeof *ret);
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+
+ if (!ret)
+ return NULL;
+ else if (!p->next && drv_ps1_cd_getstat())
+ goto failure;
+
+ *ret = (const struct drv_ps1_cd){.ev = *ev};
+ return ret;
+
+failure:
+ free(ret);
+ return NULL;
+}
diff --git a/src/drv/ps1/cd/src/next.c b/src/drv/ps1/cd/src/next.c
new file mode 100644
index 0000000..b26633a
--- /dev/null
+++ b/src/drv/ps1/cd/src/next.c
@@ -0,0 +1,37 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd.h>
+#include <drv/ps1/cd/routines.h>
+#include <drv/ps1/cd/types.h>
+#include <stdlib.h>
+
+int drv_ps1_cd_next(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ struct cd_req *const next = p->head->next;
+
+ free(p->head);
+
+ if (next && next->f())
+ return -1;
+ else if (!(p->head = next))
+ return drv_ps1_cd_getstat();
+
+ return 0;
+}
diff --git a/src/drv/ps1/cd/src/prv.c b/src/drv/ps1/cd/src/prv.c
new file mode 100644
index 0000000..61ed4b4
--- /dev/null
+++ b/src/drv/ps1/cd/src/prv.c
@@ -0,0 +1,22 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd.h>
+#include <drv/ps1/cd/types.h>
+
+struct cd_prv drv_ps1_cd_prv;
diff --git a/src/drv/ps1/cd/src/read.c b/src/drv/ps1/cd/src/read.c
new file mode 100644
index 0000000..d21cbae
--- /dev/null
+++ b/src/drv/ps1/cd/src/read.c
@@ -0,0 +1,239 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd.h>
+#include <drv/ps1/cd/routines.h>
+#include <drv/ps1/cd/types.h>
+#include <drv/ps1/event.h>
+#include <drv/event.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int seek(void);
+
+static int deliver_event(struct cd_prv *const p, const int error)
+{
+ const struct cd_prv_read *const read = &p->u.read;
+ const struct cd_req *const req = p->head;
+ const struct drv_event_done *const d = &req->done;
+
+ drv_ps1_event_disable(read->endevent);
+ drv_ps1_event_close(read->endevent);
+ drv_ps1_event_disable(read->errevent);
+ drv_ps1_event_close(read->errevent);
+
+ if (!error)
+ {
+ const struct cd_req_read *const rr = &req->u.read;
+ const long offset = rr->offset % CD_SECTOR_SZ;
+
+ memcpy(rr->buf, p->sector + offset, rr->n);
+
+ if (!((p->offset += rr->n) % CD_SECTOR_SZ))
+ p->sector_read = false;
+ }
+
+ if (d->f(error, d->args) || drv_ps1_cd_next())
+ return -1;
+
+ return 0;
+}
+
+static int wait_read(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ const struct cd_prv_read *const read = &p->u.read;
+
+ if (drv_ps1_event_test(p->event))
+ {
+ p->sector_read = true;
+ return deliver_event(p, SUCCESS);
+ }
+ else if (drv_ps1_event_test(read->errevent)
+ || drv_ps1_event_test(read->endevent))
+ return deliver_event(p, EIO);
+
+ return 0;
+}
+
+static int uncached_read(struct cd_prv *const p)
+{
+ int endevent, errevent;
+ struct cd_prv_read *const r = &p->u.read;
+ const struct CdAsyncReadSector_mode mode =
+ {
+ .mode.bits.speed = 1,
+ };
+
+ endevent = drv_ps1_event_open(CLASS_CDROM, SPEC_DATA_END, MODE_READY, NULL);
+ errevent = drv_ps1_event_open(CLASS_CDROM, SPEC_ERROR, MODE_READY, NULL);
+
+ if (endevent == -1 || errevent == -1)
+ goto failure;
+
+ *r = (const struct cd_prv_read)
+ {
+ .endevent = endevent,
+ .errevent = errevent
+ };
+
+ drv_ps1_event_enable(endevent);
+ drv_ps1_event_enable(errevent);
+
+ if (!CdAsyncReadSector(1, p->sector, mode))
+ goto failure;
+
+ p->next = wait_read;
+ return 0;
+
+failure:
+
+ if (endevent != -1)
+ {
+ drv_ps1_event_disable(endevent);
+ drv_ps1_event_close(endevent);
+ }
+
+ if (errevent != -1)
+ {
+ drv_ps1_event_disable(errevent);
+ drv_ps1_event_close(errevent);
+ }
+
+ return -1;
+}
+
+static int cached_read(struct cd_prv *const p)
+{
+ struct cd_req *const req = p->head;
+ struct cd_req_read *const rr = &req->u.read;
+ const struct drv_event_done *const done = &req->done;
+ const off_t offset = rr->offset % CD_SECTOR_SZ,
+ rem = CD_SECTOR_SZ - offset,
+ n = rr->n > rem ? rem : rr->n;
+
+ memcpy(rr->buf, p->sector + offset, n);
+
+ if (!((p->offset += n) % CD_SECTOR_SZ))
+ p->sector_read = false;
+
+ if (done->f(SUCCESS, done->args) || drv_ps1_cd_next())
+ return -1;
+
+ return 0;
+}
+
+static int wait_seek(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ const struct cd_req *const req = p->head;
+ const struct cd_req_read *const rr = &req->u.read;
+ const struct cd_prv_read *const r = &p->u.read;
+
+ if (drv_ps1_event_test(p->event))
+ {
+ drv_ps1_event_disable(r->errevent);
+ drv_ps1_event_close(r->errevent);
+ p->offset = rr->offset;
+ return uncached_read(p);
+ }
+ else if (drv_ps1_event_test(r->errevent))
+ return deliver_event(p, EIO);
+
+ return 0;
+}
+
+static int seek(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ struct cd_prv_read *const r = &p->u.read;
+ const struct cd_req_read *rr = &p->head->u.read;
+ const unsigned sector = rr->offset / CD_SECTOR_SZ;
+ const struct CdAsyncSeekL seekl = drv_ps1_cd_toseekl(sector);
+ const int errevent = drv_ps1_event_open(CLASS_CDROM, SPEC_ERROR,
+ MODE_READY, NULL);
+
+ if (errevent == -1)
+ goto failure;
+
+ *r = (const struct cd_prv_read){.errevent = errevent};
+ p->sector_read = false;
+ drv_ps1_event_enable(errevent);
+
+ if (!CdAsyncSeekL(&seekl))
+ goto failure;
+
+ p->next = wait_seek;
+ return 0;
+
+failure:
+
+ if (errevent != -1)
+ {
+ drv_ps1_event_disable(errevent);
+ drv_ps1_event_close(errevent);
+ }
+
+ return -1;
+}
+
+static int start(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ const struct cd_req *const req = p->head;
+ const struct cd_req_read *const rr = &req->u.read;
+ const off_t cursect = p->offset / CD_SECTOR_SZ,
+ tgtsect = rr->offset / CD_SECTOR_SZ;
+
+ if (cursect != tgtsect || !p->sector_read)
+ return seek();
+
+ /* TODO: multi-sector reads, make sure cache can really be used */
+ return cached_read(p);
+}
+
+int drv_ps1_cd_read(void *const buf, const size_t n, const off_t offset,
+ const struct drv_event_done *const done, void *const args)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ struct cd_req *const r = malloc(sizeof *r);
+
+ if (!r)
+ return -1;
+
+ *r = (const struct cd_req)
+ {
+ .f = start,
+ .done = *done,
+ .u.read =
+ {
+ .offset = offset,
+ .buf = buf,
+ .n = n
+ }
+ };
+
+ if (!p->head)
+ p->head = r;
+ else
+ p->tail->next = r;
+
+ p->tail = r;
+ return 0;
+}
diff --git a/src/drv/ps1/cd/src/readdebug b/src/drv/ps1/cd/src/readdebug
new file mode 100644
index 0000000..b2c09a7
--- /dev/null
+++ b/src/drv/ps1/cd/src/readdebug
@@ -0,0 +1,253 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd.h>
+#include <drv/ps1/cd/routines.h>
+#include <drv/ps1/cd/types.h>
+#include <drv/ps1/event.h>
+#include <drv/event.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#include <drv/ps1/bios.h>
+
+static int seek(void);
+
+static int deliver_event(struct cd_prv *const p, const int error)
+{
+ const struct cd_prv_read *const read = &p->u.read;
+ const struct cd_req *const req = p->head;
+ const struct drv_event_done *const d = &req->done;
+
+ drv_ps1_event_disable(read->endevent);
+ drv_ps1_event_close(read->endevent);
+ drv_ps1_event_disable(read->errevent);
+ drv_ps1_event_close(read->errevent);
+
+ if (!error)
+ {
+ const struct cd_req_read *const rr = &req->u.read;
+ const long offset = rr->offset % CD_SECTOR_SZ;
+
+ memcpy(rr->buf, p->sector + offset, rr->n);
+
+ if (!((p->offset += rr->n) % CD_SECTOR_SZ))
+ p->sector_read = false;
+ }
+
+ if (d->f(error, d->args) || drv_ps1_cd_next())
+ return -1;
+
+ return 0;
+}
+
+static int wait_read(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ const struct cd_prv_read *const read = &p->u.read;
+
+ if (drv_ps1_event_test(p->event))
+ {
+ p->sector_read = true;
+ return deliver_event(p, SUCCESS);
+ }
+ else if (drv_ps1_event_test(read->errevent)
+ || drv_ps1_event_test(read->endevent))
+ return deliver_event(p, EIO);
+
+ return 0;
+}
+
+static int uncached_read(struct cd_prv *const p)
+{
+ int endevent, errevent;
+ struct cd_prv_read *const r = &p->u.read;
+ const struct CdAsyncReadSector_mode mode =
+ {
+ .mode.bits.speed = 1,
+ };
+
+ endevent = drv_ps1_event_open(CLASS_CDROM, SPEC_DATA_END, MODE_READY, NULL);
+ errevent = drv_ps1_event_open(CLASS_CDROM, SPEC_ERROR, MODE_READY, NULL);
+
+ if (endevent == -1 || errevent == -1)
+ goto failure;
+
+ *r = (const struct cd_prv_read)
+ {
+ .endevent = endevent,
+ .errevent = errevent
+ };
+
+ drv_ps1_event_enable(endevent);
+ drv_ps1_event_enable(errevent);
+
+ Printf("preparing async read\n");
+ if (!CdAsyncReadSector(1, p->sector, mode))
+ goto failure;
+
+ p->next = wait_read;
+ return 0;
+
+failure:
+
+ if (endevent != -1)
+ {
+ drv_ps1_event_disable(endevent);
+ drv_ps1_event_close(endevent);
+ }
+
+ if (errevent != -1)
+ {
+ drv_ps1_event_disable(errevent);
+ drv_ps1_event_close(errevent);
+ }
+
+ return -1;
+}
+
+static int cached_read(struct cd_prv *const p)
+{
+ struct cd_req *const req = p->head;
+ struct cd_req_read *const rr = &req->u.read;
+ const struct drv_event_done *const done = &req->done;
+ const off_t offset = rr->offset % CD_SECTOR_SZ,
+ rem = CD_SECTOR_SZ - offset,
+ n = rr->n > rem ? rem : rr->n;
+
+ Printf("Doing %lu-byte cached read from offset %lu to %p\n",
+ (unsigned long) n, (unsigned long) offset, (void *)rr->buf);
+ memcpy(rr->buf, p->sector + offset, n);
+
+ if (!((p->offset += n) % CD_SECTOR_SZ))
+ p->sector_read = false;
+
+ if (done->f(SUCCESS, done->args) || drv_ps1_cd_next())
+ return -1;
+
+ return 0;
+}
+
+static int wait_seek(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ const struct cd_req *const req = p->head;
+ const struct cd_req_read *const rr = &req->u.read;
+ const struct cd_prv_read *const r = &p->u.read;
+
+ if (drv_ps1_event_test(p->event))
+ {
+ Printf("finished seeking\n");
+ drv_ps1_event_disable(r->errevent);
+ drv_ps1_event_close(r->errevent);
+ p->offset = rr->offset;
+ return uncached_read(p);
+ }
+ else if (drv_ps1_event_test(r->errevent))
+ {
+ Printf("failed to seek\n");
+ return deliver_event(p, EIO);
+ }
+
+ return 0;
+}
+
+static int seek(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ struct cd_prv_read *const r = &p->u.read;
+ const struct cd_req_read *rr = &p->head->u.read;
+ const unsigned sector = rr->offset / CD_SECTOR_SZ;
+ const struct CdAsyncSeekL seekl = drv_ps1_cd_toseekl(sector);
+ const int errevent = drv_ps1_event_open(CLASS_CDROM, SPEC_ERROR,
+ MODE_READY, NULL);
+
+ if (errevent == -1)
+ goto failure;
+
+ *r = (const struct cd_prv_read){.errevent = errevent};
+ p->sector_read = false;
+ drv_ps1_event_enable(errevent);
+
+ Printf("Seeking to offset %lu\n", (unsigned long)rr->offset);
+
+ if (!CdAsyncSeekL(&seekl))
+ goto failure;
+
+ p->next = wait_seek;
+ return 0;
+
+failure:
+
+ if (errevent != -1)
+ {
+ drv_ps1_event_disable(errevent);
+ drv_ps1_event_close(errevent);
+ }
+
+ return -1;
+}
+
+static int start(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ const struct cd_req *const req = p->head;
+ const struct cd_req_read *const rr = &req->u.read;
+ const off_t cursect = p->offset / CD_SECTOR_SZ,
+ tgtsect = rr->offset / CD_SECTOR_SZ;
+
+ if (cursect != tgtsect || !p->sector_read)
+ return seek();
+
+ /* TODO: multi-sector reads, make sure cache can really be used */
+ return cached_read(p);
+}
+
+int drv_ps1_cd_read(void *const buf, const size_t n, const off_t offset,
+ const struct drv_event_done *const done, void *const args)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+ struct cd_req *const r = malloc(sizeof *r);
+
+ if (!r)
+ return -1;
+
+ *r = (const struct cd_req)
+ {
+ .f = start,
+ .done = *done,
+ .u.read =
+ {
+ .offset = offset,
+ .buf = buf,
+ .n = n
+ }
+ };
+
+ if (!p->head)
+ p->head = r;
+ else
+ p->tail->next = r;
+
+ p->tail = r;
+ Printf("Added %p {.buf=%p, .n=%lu} to read reqs\n", (void *)p->tail, buf,
+ (unsigned long)n);
+ return 0;
+}
diff --git a/src/drv/ps1/cd/src/send.c b/src/drv/ps1/cd/src/send.c
new file mode 100644
index 0000000..01aa5cb
--- /dev/null
+++ b/src/drv/ps1/cd/src/send.c
@@ -0,0 +1,101 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd.h>
+#include <drv/ps1/bios.h>
+#include <drv/ps1/cd/regs.h>
+#include <drv/ps1/cd/routines.h>
+#include <drv/ps1/cd/types.h>
+#include <drv/ps1/event.h>
+#include <stdint.h>
+
+static int wait_event(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+
+ if (drv_ps1_event_test(p->event))
+ {
+ const union cd_prv_getstat g = {.status = CD_REG_RSP->rsp};
+
+ p->available = !g.bits.shell_open;
+ }
+
+ return 0;
+}
+
+static void send_params(const struct cd_prv *const p)
+{
+ static const size_t n[] =
+ {
+ [CMD_SETLOC] = sizeof (struct cmd_setloc)
+ };
+
+ const uint8_t cmd = p->cmd.cmd, index = CD_REG_STATUS->index;
+
+ if (cmd < sizeof n / sizeof *n)
+ {
+ const void *const vbuf = &p->cmd.params;
+ const uint8_t *buf = vbuf;
+
+ CD_REG_STATUS->index = 0;
+
+ for (size_t i = 0; i < n[cmd]; i++)
+ CD_REG_PARAM->param = *buf++;
+
+ CD_REG_STATUS->index = index;
+ }
+}
+
+static int send_data(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+
+ if (CD_REG_STATUS->BUSYSTS)
+ return 0;
+
+ send_params(p);
+ CD_REG_CMD->cmd = p->cmd.cmd;
+ p->next = wait_event;
+ return 0;
+}
+
+static int reset_param_fifo(void)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+
+ if (CD_REG_STATUS->BUSYSTS)
+ return 0;
+
+ CD_REG_IF->w.ack = 0x1f;
+ CD_REG_STATUS->index = 1;
+ CD_REG_IF->w.CLRPRM = 1;
+ p->next = send_data;
+ return 0;
+}
+
+int drv_ps1_cd_send(const struct cmd *const cmd)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+
+ if (drv_ps1_cd_ensure_event(p))
+ return -1;
+
+ p->cmd = *cmd;
+ p->next = reset_param_fifo;
+ return 0;
+}
diff --git a/src/drv/ps1/cd/src/toseekl.c b/src/drv/ps1/cd/src/toseekl.c
new file mode 100644
index 0000000..ee776c9
--- /dev/null
+++ b/src/drv/ps1/cd/src/toseekl.c
@@ -0,0 +1,54 @@
+/* Original functions from psn00bsdk, commit 5d9aa2d3
+ *
+ * itob extracted from libpsn00b/include/psxcd.h
+ * CdIntToPos (here renamed to drv_ps1_cd_toseekl) extracted from
+ * libpsn00b/psxcd/misc.c
+ *
+ * Original copyright notice:
+ *
+ * PSn00bSDK CD-ROM library
+ * (C) 2020-2023 Lameguy64, spicyjpeg - MPL licensed
+ */
+
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd/routines.h>
+#include <drv/ps1/bios.h>
+
+/**
+ * @brief Translates a decimal value to BCD.
+ *
+ * @details Translates a decimal integer in 0-99 range into a BCD format value.
+ */
+static unsigned itob(const unsigned i)
+{
+ return ((i / 10) * 16) | (i % 10);
+}
+
+struct CdAsyncSeekL drv_ps1_cd_toseekl(unsigned i)
+{
+ i += 150;
+
+ return (const struct CdAsyncSeekL)
+ {
+ .minutes = itob(i / (75 * 60)),
+ .seconds = itob((i / 75) % 60),
+ .frames = itob(i % 75)
+ };
+}
diff --git a/src/drv/ps1/cd/src/update.c b/src/drv/ps1/cd/src/update.c
new file mode 100644
index 0000000..148dd87
--- /dev/null
+++ b/src/drv/ps1/cd/src/update.c
@@ -0,0 +1,47 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd.h>
+#include <drv/ps1/cd/types.h>
+#include <drv/ps1/cd/routines.h>
+#include <drv/event.h>
+#include <stdbool.h>
+
+int drv_ps1_cd_update(struct drv_ps1_cd *const cd)
+{
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+
+ if (p->next())
+ return -1;
+ else if (p->available ^ cd->available)
+ {
+ const struct drv_event *const ev = &cd->ev;
+ static const struct drv_event_ops ops =
+ {
+ .read = drv_ps1_cd_read,
+ .write = drv_ps1_cd_write
+ };
+
+ if (ev->status("cd0", &ops, p->available, 0440, ev->args))
+ return -1;
+
+ cd->available = p->available;
+ }
+
+ return 0;
+}
diff --git a/src/drv/ps1/cd/src/virt/CMakeLists.txt b/src/drv/ps1/cd/src/virt/CMakeLists.txt
new file mode 100644
index 0000000..0e5bd1b
--- /dev/null
+++ b/src/drv/ps1/cd/src/virt/CMakeLists.txt
@@ -0,0 +1,25 @@
+# wnix, a Unix-like operating system for WebAssembly applications.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+target_sources(drv_ps1_cd PRIVATE
+ # free.c
+ init.c
+ # next.c
+ # prv.c
+ # read.c
+ # update.c
+ # write.c
+)
diff --git a/src/drv/ps1/cd/src/virt/init.c b/src/drv/ps1/cd/src/virt/init.c
new file mode 100644
index 0000000..204f7fa
--- /dev/null
+++ b/src/drv/ps1/cd/src/virt/init.c
@@ -0,0 +1,41 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd.h>
+#include <drv/ps1/cd/virt/types.h>
+#include <drv/event.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+struct drv_ps1_cd *drv_ps1_cd_init(const struct drv_event *const ev)
+{
+ struct drv_ps1_cd *const ret = malloc(sizeof *ret);
+ struct cd_prv *const p = &drv_ps1_cd_prv;
+
+ if (!ret)
+ return NULL;
+ else if (!p->next && drv_ps1_cd_getstat())
+ goto failure;
+
+ *ret = (const struct drv_ps1_cd){.ev = *ev};
+ return ret;
+
+failure:
+ free(ret);
+ return NULL;
+}
diff --git a/src/drv/ps1/cd/src/write.c b/src/drv/ps1/cd/src/write.c
new file mode 100644
index 0000000..ba47993
--- /dev/null
+++ b/src/drv/ps1/cd/src/write.c
@@ -0,0 +1,31 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <drv/ps1/cd.h>
+#include <drv/ps1/cd/routines.h>
+#include <drv/ps1/cd/types.h>
+#include <drv/event.h>
+#include <errno.h>
+
+int drv_ps1_cd_write(const void *const buf, const size_t n,
+ const struct drv_event_done *const done, void *const args)
+{
+ /* TODO: write event callback returning EROFS */
+ errno = EROFS;
+ return -1;
+}