| /* SPDX-License-Identifier: MIT */ |
| |
| // #define DEBUG_IODEV |
| |
| #include "iodev.h" |
| #include "memory.h" |
| #include "string.h" |
| |
| #ifdef DEBUG_IODEV |
| #define dprintf printf |
| #else |
| #define dprintf(...) \ |
| do { \ |
| } while (0) |
| #endif |
| |
| #define CONSOLE_BUFFER_SIZE 8192 |
| |
| extern struct iodev iodev_uart; |
| extern struct iodev iodev_fb; |
| extern struct iodev iodev_log; |
| extern struct iodev iodev_usb_vuart; |
| |
| struct iodev *iodevs[IODEV_NUM] = { |
| [IODEV_UART] = &iodev_uart, |
| [IODEV_FB] = &iodev_fb, |
| [IODEV_USB_VUART] = &iodev_usb_vuart, |
| [IODEV_LOG] = &iodev_log, |
| }; |
| |
| char con_buf[CONSOLE_BUFFER_SIZE]; |
| size_t con_wp; |
| size_t con_rp[IODEV_NUM]; |
| |
| void iodev_register_device(iodev_id_t id, struct iodev *dev) |
| { |
| if (id >= IODEV_MAX) |
| return; |
| iodevs[id] = dev; |
| } |
| |
| struct iodev *iodev_unregister_device(iodev_id_t id) |
| { |
| if (id < IODEV_USB0 || id >= IODEV_MAX) |
| return NULL; |
| |
| struct iodev *dev = iodevs[id]; |
| iodevs[id] = NULL; |
| return dev; |
| } |
| |
| ssize_t iodev_can_read(iodev_id_t id) |
| { |
| if (!iodevs[id] || !iodevs[id]->ops->can_read) |
| return 0; |
| |
| if (mmu_active()) |
| spin_lock(&iodevs[id]->lock); |
| ssize_t ret = iodevs[id]->ops->can_read(iodevs[id]->opaque); |
| if (mmu_active()) |
| spin_unlock(&iodevs[id]->lock); |
| return ret; |
| } |
| |
| bool iodev_can_write(iodev_id_t id) |
| { |
| if (!iodevs[id] || !iodevs[id]->ops->can_write) |
| return false; |
| |
| if (mmu_active()) |
| spin_lock(&iodevs[id]->lock); |
| bool ret = iodevs[id]->ops->can_write(iodevs[id]->opaque); |
| if (mmu_active()) |
| spin_unlock(&iodevs[id]->lock); |
| return ret; |
| } |
| |
| ssize_t iodev_read(iodev_id_t id, void *buf, size_t length) |
| { |
| if (!iodevs[id] || !iodevs[id]->ops->read) |
| return -1; |
| |
| if (mmu_active()) |
| spin_lock(&iodevs[id]->lock); |
| ssize_t ret = iodevs[id]->ops->read(iodevs[id]->opaque, buf, length); |
| if (mmu_active()) |
| spin_unlock(&iodevs[id]->lock); |
| return ret; |
| } |
| |
| ssize_t iodev_write(iodev_id_t id, const void *buf, size_t length) |
| { |
| if (!iodevs[id] || !iodevs[id]->ops->write) |
| return -1; |
| |
| if (mmu_active()) |
| spin_lock(&iodevs[id]->lock); |
| ssize_t ret = iodevs[id]->ops->write(iodevs[id]->opaque, buf, length); |
| if (mmu_active()) |
| spin_unlock(&iodevs[id]->lock); |
| return ret; |
| } |
| |
| ssize_t iodev_queue(iodev_id_t id, const void *buf, size_t length) |
| { |
| if (!iodevs[id] || !iodevs[id]->ops->queue) |
| return iodev_write(id, buf, length); |
| |
| if (mmu_active()) |
| spin_lock(&iodevs[id]->lock); |
| ssize_t ret = iodevs[id]->ops->queue(iodevs[id]->opaque, buf, length); |
| if (mmu_active()) |
| spin_unlock(&iodevs[id]->lock); |
| return ret; |
| } |
| |
| void iodev_flush(iodev_id_t id) |
| { |
| if (!iodevs[id] || !iodevs[id]->ops->flush) |
| return; |
| |
| if (mmu_active()) |
| spin_lock(&iodevs[id]->lock); |
| iodevs[id]->ops->flush(iodevs[id]->opaque); |
| if (mmu_active()) |
| spin_unlock(&iodevs[id]->lock); |
| } |
| |
| void iodev_lock(iodev_id_t id) |
| { |
| if (!iodevs[id]) |
| return; |
| |
| if (mmu_active()) |
| spin_lock(&iodevs[id]->lock); |
| } |
| |
| void iodev_unlock(iodev_id_t id) |
| { |
| if (!iodevs[id]) |
| return; |
| |
| if (mmu_active()) |
| spin_unlock(&iodevs[id]->lock); |
| } |
| |
| int in_iodev = 0; |
| |
| static DECLARE_SPINLOCK(console_lock); |
| |
| void iodev_console_write(const void *buf, size_t length) |
| { |
| bool do_lock = mmu_active(); |
| |
| if (!do_lock && !is_boot_cpu()) { |
| if (length && iodevs[IODEV_UART]->usage & USAGE_CONSOLE) { |
| iodevs[IODEV_UART]->ops->write(iodevs[IODEV_UART]->opaque, "*", 1); |
| iodevs[IODEV_UART]->ops->write(iodevs[IODEV_UART]->opaque, buf, length); |
| } |
| return; |
| } |
| |
| if (do_lock) |
| spin_lock(&console_lock); |
| |
| if (in_iodev) { |
| if (length && iodevs[IODEV_UART]->usage & USAGE_CONSOLE) { |
| iodevs[IODEV_UART]->ops->write(iodevs[IODEV_UART]->opaque, "+", 1); |
| iodevs[IODEV_UART]->ops->write(iodevs[IODEV_UART]->opaque, buf, length); |
| } |
| if (do_lock) |
| spin_unlock(&console_lock); |
| return; |
| } |
| in_iodev++; |
| |
| dprintf(" iodev_console_write() wp=%d\n", con_wp); |
| for (iodev_id_t id = 0; id < IODEV_NUM; id++) { |
| if (!iodevs[id]) |
| continue; |
| |
| if (!(iodevs[id]->usage & USAGE_CONSOLE)) { |
| /* Drop buffer */ |
| con_rp[id] = con_wp + length; |
| continue; |
| } |
| |
| if (!iodev_can_write(id)) |
| continue; |
| |
| if (con_wp > CONSOLE_BUFFER_SIZE) |
| con_rp[id] = max(con_wp - CONSOLE_BUFFER_SIZE, con_rp[id]); |
| |
| dprintf(" rp=%d\n", con_rp[id]); |
| // Flush existing buffer to device if possible |
| while (con_rp[id] < con_wp) { |
| size_t buf_rp = con_rp[id] % CONSOLE_BUFFER_SIZE; |
| size_t block = min(con_wp - con_rp[id], CONSOLE_BUFFER_SIZE - buf_rp); |
| |
| dprintf(" write buf %d\n", block); |
| ssize_t ret = iodev_write(id, &con_buf[buf_rp], block); |
| |
| if (ret <= 0) |
| goto next_dev; |
| |
| con_rp[id] += ret; |
| } |
| |
| const u8 *p = buf; |
| size_t wrote = 0; |
| |
| // Write the current buffer |
| while (wrote < length) { |
| ssize_t ret = iodev_write(id, p, length - wrote); |
| |
| if (ret <= 0) |
| goto next_dev; |
| |
| con_rp[id] += ret; |
| wrote += ret; |
| p += ret; |
| } |
| |
| next_dev:; |
| } |
| |
| // Update console buffer |
| |
| if (length > CONSOLE_BUFFER_SIZE) { |
| buf += (length - CONSOLE_BUFFER_SIZE); |
| con_wp += (length - CONSOLE_BUFFER_SIZE); |
| length = CONSOLE_BUFFER_SIZE; |
| } |
| |
| while (length) { |
| size_t buf_wp = con_wp % CONSOLE_BUFFER_SIZE; |
| size_t block = min(length, CONSOLE_BUFFER_SIZE - buf_wp); |
| memcpy(&con_buf[buf_wp], buf, block); |
| buf += block; |
| con_wp += block; |
| length -= block; |
| } |
| |
| in_iodev--; |
| if (do_lock) |
| spin_unlock(&console_lock); |
| } |
| |
| void iodev_handle_events(iodev_id_t id) |
| { |
| bool do_lock = mmu_active(); |
| |
| if (do_lock) |
| spin_lock(&console_lock); |
| |
| if (in_iodev) { |
| if (do_lock) |
| spin_unlock(&console_lock); |
| return; |
| } |
| |
| in_iodev++; |
| |
| if (iodevs[id]->ops->handle_events) |
| iodevs[id]->ops->handle_events(iodevs[id]->opaque); |
| |
| in_iodev--; |
| |
| if (iodev_can_write(id)) |
| iodev_console_write(NULL, 0); |
| |
| if (do_lock) |
| spin_unlock(&console_lock); |
| } |
| |
| void iodev_console_kick(void) |
| { |
| iodev_console_write(NULL, 0); |
| |
| for (iodev_id_t id = 0; id < IODEV_NUM; id++) { |
| if (!iodevs[id]) |
| continue; |
| if (!(iodevs[id]->usage & USAGE_CONSOLE)) |
| continue; |
| |
| iodev_handle_events(id); |
| } |
| } |
| |
| void iodev_console_flush(void) |
| { |
| for (iodev_id_t id = 0; id < IODEV_NUM; id++) { |
| if (!iodevs[id]) |
| continue; |
| if (!(iodevs[id]->usage & USAGE_CONSOLE)) |
| continue; |
| |
| iodev_flush(id); |
| } |
| } |
| |
| void iodev_set_usage(iodev_id_t id, iodev_usage_t usage) |
| { |
| if (iodevs[id]) |
| iodevs[id]->usage = usage; |
| } |
| |
| iodev_usage_t iodev_get_usage(iodev_id_t id) |
| { |
| if (iodevs[id]) |
| return iodevs[id]->usage; |
| return 0; |
| } |
| |
| void *iodev_get_opaque(iodev_id_t id) |
| { |
| if (id >= IODEV_MAX || !iodevs[id]) |
| return NULL; |
| |
| return iodevs[id]->opaque; |
| } |