// SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TAG "posix_uart" namespace { struct PosixUartInternal { Mutex mutex {}; UartConfig config {}; bool config_set = false; int fd = -1; PosixUartInternal() { mutex_construct(&mutex); } ~PosixUartInternal() { mutex_destruct(&mutex); } }; #define GET_CONFIG(device) ((const PosixUartConfig*)device->config) #define GET_DATA(device) ((PosixUartInternal*)device_get_driver_data(device)) #define lock(data) mutex_lock(&data->mutex) #define unlock(data) mutex_unlock(&data->mutex) using Clock = std::chrono::steady_clock; Clock::time_point to_deadline(TickType_t timeout) { if (timeout == portMAX_DELAY) return Clock::time_point::max(); return Clock::now() + std::chrono::milliseconds((uint64_t)timeout * portTICK_PERIOD_MS); } int remaining_poll_ms(Clock::time_point deadline) { if (deadline == Clock::time_point::max()) return -1; auto remaining = std::chrono::duration_cast(deadline - Clock::now()).count(); return remaining > 0 ? (int)remaining : 0; } /** @return true when the fd became ready before the deadline */ bool wait_for(int fd, short events, Clock::time_point deadline) { while (true) { pollfd pfd = { .fd = fd, .events = events, .revents = 0 }; int result = poll(&pfd, 1, remaining_poll_ms(deadline)); if (result > 0) return true; if (result == 0) return false; if (errno != EINTR) return false; } } bool to_speed(uint32_t baud_rate, speed_t* speed) { switch (baud_rate) { case 1200: *speed = B1200; return true; case 2400: *speed = B2400; return true; case 4800: *speed = B4800; return true; case 9600: *speed = B9600; return true; case 19200: *speed = B19200; return true; case 38400: *speed = B38400; return true; case 57600: *speed = B57600; return true; case 115200: *speed = B115200; return true; case 230400: *speed = B230400; return true; #ifdef B460800 case 460800: *speed = B460800; return true; #endif #ifdef B921600 case 921600: *speed = B921600; return true; #endif #ifdef B1000000 case 1000000: *speed = B1000000; return true; #endif #ifdef B2000000 case 2000000: *speed = B2000000; return true; #endif default: return false; } } error_t apply_termios(int fd, const UartConfig& config) { speed_t speed; if (!to_speed(config.baud_rate, &speed)) return ERROR_NOT_SUPPORTED; if (config.stop_bits == UART_CONTROLLER_STOP_BITS_1_5) return ERROR_NOT_SUPPORTED; termios tty {}; if (tcgetattr(fd, &tty) != 0) return ERROR_RESOURCE; cfmakeraw(&tty); tty.c_cflag |= CLOCAL | CREAD; tty.c_cflag &= ~CRTSCTS; tty.c_cflag &= ~CSIZE; switch (config.data_bits) { case UART_CONTROLLER_DATA_5_BITS: tty.c_cflag |= CS5; break; case UART_CONTROLLER_DATA_6_BITS: tty.c_cflag |= CS6; break; case UART_CONTROLLER_DATA_7_BITS: tty.c_cflag |= CS7; break; default: tty.c_cflag |= CS8; break; } switch (config.parity) { case UART_CONTROLLER_PARITY_EVEN: tty.c_cflag |= PARENB; tty.c_cflag &= ~PARODD; break; case UART_CONTROLLER_PARITY_ODD: tty.c_cflag |= PARENB | PARODD; break; default: tty.c_cflag &= ~PARENB; break; } if (config.stop_bits == UART_CONTROLLER_STOP_BITS_2) { tty.c_cflag |= CSTOPB; } else { tty.c_cflag &= ~CSTOPB; } tty.c_cc[VMIN] = 0; tty.c_cc[VTIME] = 0; if (cfsetispeed(&tty, speed) != 0 || cfsetospeed(&tty, speed) != 0) return ERROR_NOT_SUPPORTED; if (tcsetattr(fd, TCSANOW, &tty) != 0) return ERROR_RESOURCE; tcflush(fd, TCIOFLUSH); return ERROR_NONE; } error_t read_locked(PosixUartInternal* data, uint8_t* buffer, size_t buffer_size, TickType_t timeout) { auto deadline = to_deadline(timeout); size_t total = 0; while (total < buffer_size) { ssize_t result = read(data->fd, buffer + total, buffer_size - total); if (result > 0) { total += result; } else if (result < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { return ERROR_RESOURCE; } else if (!wait_for(data->fd, POLLIN, deadline)) { return ERROR_TIMEOUT; } } return ERROR_NONE; } error_t write_locked(PosixUartInternal* data, const uint8_t* buffer, size_t buffer_size, TickType_t timeout) { auto deadline = to_deadline(timeout); size_t total = 0; while (total < buffer_size) { ssize_t result = write(data->fd, buffer + total, buffer_size - total); if (result > 0) { total += result; } else if (result < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { return ERROR_RESOURCE; } else if (!wait_for(data->fd, POLLOUT, deadline)) { return ERROR_TIMEOUT; } } return ERROR_NONE; } // ---- UartControllerApi ---- error_t read_bytes(Device* device, uint8_t* buffer, size_t buffer_size, TickType_t timeout) { auto* data = GET_DATA(device); lock(data); if (data->fd < 0) { unlock(data); return ERROR_INVALID_STATE; } error_t error = read_locked(data, buffer, buffer_size, timeout); unlock(data); return error; } error_t read_byte(Device* device, uint8_t* out, TickType_t timeout) { return read_bytes(device, out, 1, timeout); } error_t write_bytes(Device* device, const uint8_t* buffer, size_t buffer_size, TickType_t timeout) { auto* data = GET_DATA(device); lock(data); if (data->fd < 0) { unlock(data); return ERROR_INVALID_STATE; } error_t error = write_locked(data, buffer, buffer_size, timeout); unlock(data); return error; } error_t write_byte(Device* device, uint8_t out, TickType_t timeout) { return write_bytes(device, &out, 1, timeout); } error_t get_available(Device* device, size_t* available) { auto* data = GET_DATA(device); lock(data); if (data->fd < 0) { unlock(data); return ERROR_INVALID_STATE; } int count = 0; int result = ioctl(data->fd, FIONREAD, &count); unlock(data); if (result != 0) return ERROR_RESOURCE; *available = (size_t)count; return ERROR_NONE; } error_t set_config(Device* device, const UartConfig* config) { auto* data = GET_DATA(device); lock(data); if (data->fd >= 0) { unlock(data); return ERROR_INVALID_STATE; } data->config = *config; data->config_set = true; unlock(data); return ERROR_NONE; } error_t get_config(Device* device, UartConfig* config) { auto* data = GET_DATA(device); lock(data); if (!data->config_set) { unlock(data); return ERROR_RESOURCE; } *config = data->config; unlock(data); return ERROR_NONE; } error_t open_device(Device* device) { auto* data = GET_DATA(device); const char* path = GET_CONFIG(device)->path; lock(data); if (data->fd >= 0) { unlock(data); LOG_W(TAG, "%s is already open", device->name); return ERROR_INVALID_STATE; } if (!data->config_set) { unlock(data); LOG_E(TAG, "%s open failed: config not set", device->name); return ERROR_INVALID_STATE; } int fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC); if (fd < 0) { unlock(data); LOG_E(TAG, "%s failed to open %s: %s", device->name, path, strerror(errno)); return ERROR_RESOURCE; } error_t error = apply_termios(fd, data->config); if (error != ERROR_NONE) { close(fd); unlock(data); LOG_E(TAG, "%s failed to configure %s: %s", device->name, path, error_to_string(error)); return error; } data->fd = fd; unlock(data); return ERROR_NONE; } error_t close_device(Device* device) { auto* data = GET_DATA(device); lock(data); if (data->fd < 0) { unlock(data); LOG_W(TAG, "%s is already closed", device->name); return ERROR_INVALID_STATE; } close(data->fd); data->fd = -1; unlock(data); return ERROR_NONE; } bool is_open(Device* device) { auto* data = GET_DATA(device); lock(data); bool status = data->fd >= 0; unlock(data); return status; } error_t flush_input(Device* device) { auto* data = GET_DATA(device); lock(data); if (data->fd < 0) { unlock(data); return ERROR_INVALID_STATE; } int result = tcflush(data->fd, TCIFLUSH); unlock(data); return result == 0 ? ERROR_NONE : ERROR_RESOURCE; } const UartControllerApi posix_uart_api = { .read_byte = read_byte, .write_byte = write_byte, .write_bytes = write_bytes, .read_bytes = read_bytes, .get_available = get_available, .set_config = set_config, .get_config = get_config, .open = open_device, .close = close_device, .is_open = is_open, .flush_input = flush_input }; // ---- Driver lifecycle ---- error_t start_device(Device* device) { auto* data = new(std::nothrow) PosixUartInternal(); if (data == nullptr) return ERROR_OUT_OF_MEMORY; device_set_driver_data(device, data); return ERROR_NONE; } error_t stop_device(Device* device) { auto* data = GET_DATA(device); lock(data); if (data->fd >= 0) { unlock(data); return ERROR_INVALID_STATE; } unlock(data); device_set_driver_data(device, nullptr); delete data; return ERROR_NONE; } } // namespace extern "C" { Driver posix_uart_driver = { .name = "posix_uart", .compatible = (const char*[]) { "posix,uart", nullptr }, .start_device = start_device, .stop_device = stop_device, .api = (const void*)&posix_uart_api, .device_type = &UART_CONTROLLER_TYPE, .internal = nullptr }; } // extern "C"