blob: 9488fe75e317c910851bfa983083e23771b35661 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <ddk/debug.h>
#include <ddk/protocol/serial.h>
#include <zircon/threads.h>
#include <stdlib.h>
#include <threads.h>
#include "platform-bus.h"
typedef struct serial_port {
serial_driver_protocol_t serial;
uint32_t port_num;
zx_handle_t socket; // socket used for communicating with our client
zx_handle_t event; // event for signaling serial driver state changes
thrd_t thread;
mtx_t lock;
} serial_port_t;
enum {
WAIT_ITEM_SOCKET,
WAIT_ITEM_EVENT,
};
#define UART_BUFFER_SIZE 1024
#define EVENT_READABLE_SIGNAL ZX_USER_SIGNAL_0
#define EVENT_WRITABLE_SIGNAL ZX_USER_SIGNAL_1
#define EVENT_CANCEL_SIGNAL ZX_USER_SIGNAL_2
// This thread handles data transfer in both directions
static int platform_serial_thread(void* arg) {
serial_port_t* port = arg;
uint8_t in_buffer[UART_BUFFER_SIZE];
uint8_t out_buffer[UART_BUFFER_SIZE];
size_t in_buffer_offset = 0; // offset of first byte in in_buffer (if any)
size_t out_buffer_offset = 0; // offset of first byte in out_buffer (if any)
size_t in_buffer_count = 0; // number of bytes in in_buffer
size_t out_buffer_count = 0; // number of bytes in out_buffer
zx_wait_item_t items[2];
items[WAIT_ITEM_SOCKET].handle = port->socket;
items[WAIT_ITEM_EVENT].handle = port->event;
bool peer_closed = false;
// loop until client socket is closed and we have no more data to write
while (!peer_closed || out_buffer_count > 0) {
// attempt pending socket write
if (in_buffer_count > 0) {
size_t actual;
zx_status_t status = zx_socket_write(port->socket, 0, in_buffer + in_buffer_offset,
in_buffer_count, &actual);
if (status == ZX_OK) {
in_buffer_count -= actual;
if (in_buffer_count > 0) {
in_buffer_offset += actual;
} else {
in_buffer_offset = 0;
}
} else if (status != ZX_ERR_SHOULD_WAIT && status != ZX_SOCKET_PEER_CLOSED) {
zxlogf(ERROR, "platform_serial_thread: zx_socket_write returned %d\n", status);
break;
}
}
// attempt pending serial write
if (out_buffer_count > 0) {
size_t actual;
zx_status_t status = serial_driver_write(&port->serial, port->port_num,
out_buffer + out_buffer_offset,
out_buffer_count, &actual);
if (status == ZX_OK) {
out_buffer_count -= actual;
if (out_buffer_count > 0) {
out_buffer_offset += actual;
} else {
// out_buffer empty now, reset to beginning
out_buffer_offset = 0;
}
} else if (status != ZX_ERR_SHOULD_WAIT && status != ZX_SOCKET_PEER_CLOSED) {
zxlogf(ERROR, "platform_serial_thread: serial_driver_write returned %d\n", status);
break;
}
}
// wait for serial or socket to be readable
items[WAIT_ITEM_SOCKET].waitfor = ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED;
items[WAIT_ITEM_EVENT].waitfor = EVENT_READABLE_SIGNAL | EVENT_CANCEL_SIGNAL;
// also wait for writability if we have pending data to write
if (in_buffer_count > 0) {
items[WAIT_ITEM_SOCKET].waitfor |= ZX_SOCKET_WRITABLE;
}
if (out_buffer_count > 0) {
items[WAIT_ITEM_EVENT].waitfor |= EVENT_WRITABLE_SIGNAL;
}
zx_status_t status = zx_object_wait_many(items, countof(items), ZX_TIME_INFINITE);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_serial_thread: zx_object_wait_many returned %d\n", status);
break;
}
if (items[WAIT_ITEM_EVENT].pending & EVENT_READABLE_SIGNAL) {
size_t length;
status = serial_driver_read(&port->serial, port->port_num, in_buffer + in_buffer_count,
sizeof(in_buffer) - in_buffer_count, &length);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_serial_thread: serial_driver_read returned %d\n", status);
break;
}
in_buffer_count += length;
}
if (items[WAIT_ITEM_SOCKET].pending & ZX_SOCKET_READABLE) {
size_t length;
status = zx_socket_read(port->socket, 0, out_buffer + out_buffer_count,
sizeof(out_buffer) - out_buffer_count, &length);
if (status != ZX_OK) {
zxlogf(ERROR, "serial_out_thread: zx_socket_read returned %d\n", status);
break;
}
out_buffer_count += length;
}
if (items[WAIT_ITEM_SOCKET].pending & ZX_SOCKET_PEER_CLOSED) {
peer_closed = true;
}
}
serial_driver_enable(&port->serial, port->port_num, false);
serial_driver_set_notify_callback(&port->serial, port->port_num, NULL, NULL);
zx_handle_close(port->event);
zx_handle_close(port->socket);
port->event = ZX_HANDLE_INVALID;
port->socket = ZX_HANDLE_INVALID;
return 0;
}
static void platform_serial_state_cb(uint32_t port_num, uint32_t state, void* cookie) {
serial_port_t* port = cookie;
// update our event handle signals with latest state from the serial driver
zx_signals_t set = 0;
zx_signals_t clear = 0;
if (state & SERIAL_STATE_READABLE) {
set |= EVENT_READABLE_SIGNAL;
} else {
clear |= EVENT_READABLE_SIGNAL;
}
if (state & SERIAL_STATE_WRITABLE) {
set |= EVENT_WRITABLE_SIGNAL;
} else {
clear |= EVENT_WRITABLE_SIGNAL;
}
zx_object_signal(port->event, clear, set);
}
zx_status_t platform_serial_init(platform_bus_t* bus, serial_driver_protocol_t* serial) {
uint32_t port_count = serial_driver_get_port_count(serial);
if (!port_count) {
return ZX_ERR_INVALID_ARGS;
}
if (bus->serial_ports) {
// already initialized
return ZX_ERR_BAD_STATE;
}
serial_port_t* ports = calloc(port_count, sizeof(serial_port_t));
if (!ports) {
return ZX_ERR_NO_MEMORY;
}
bus->serial_ports = ports;
bus->serial_port_count = port_count;
for (uint32_t i = 0; i < port_count; i++) {
serial_port_t* port = &ports[i];
mtx_init(&port->lock, mtx_plain);
memcpy(&port->serial, serial, sizeof(port->serial));
port->port_num = i;
}
return ZX_OK;
}
static void platform_serial_port_release(serial_port_t* port) {
serial_driver_enable(&port->serial, port->port_num, false);
serial_driver_set_notify_callback(&port->serial, port->port_num, NULL, NULL);
zx_handle_close(port->event);
zx_handle_close(port->socket);
port->event = ZX_HANDLE_INVALID;
port->socket = ZX_HANDLE_INVALID;
}
void platform_serial_release(platform_bus_t* bus) {
if (bus->serial_ports) {
for (unsigned i = 0; i < bus->serial_port_count; i++) {
platform_serial_port_release(&bus->serial_ports[i]);
}
}
free(bus->serial_ports);
}
zx_status_t platform_serial_config(platform_bus_t* bus, uint32_t port_num, uint32_t baud_rate,
uint32_t flags) {
if (port_num >= bus->serial_port_count) {
return ZX_ERR_NOT_FOUND;
}
// locking? flushing?
return serial_driver_config(&bus->serial, port_num, baud_rate, flags);
}
zx_status_t platform_serial_open_socket(platform_bus_t* bus, uint32_t port_num,
zx_handle_t* out_handle) {
if (port_num >= bus->serial_port_count) {
return ZX_ERR_NOT_FOUND;
}
serial_port_t* port = &bus->serial_ports[port_num];
mtx_lock(&port->lock);
if (port->socket != ZX_HANDLE_INVALID) {
mtx_unlock(&port->lock);
return ZX_ERR_ALREADY_BOUND;
}
zx_handle_t socket = ZX_HANDLE_INVALID;
zx_status_t status = zx_socket_create(ZX_SOCKET_STREAM, &port->socket, &socket);
if (status != ZX_OK) {
mtx_unlock(&port->lock);
return status;
}
status = zx_event_create(0, &port->event);
if (status != ZX_OK) {
goto fail;
}
serial_driver_set_notify_callback(&bus->serial, port_num, platform_serial_state_cb, port);
status = serial_driver_enable(&bus->serial, port_num, true);
if (status != ZX_OK) {
goto fail;
}
int thrd_rc = thrd_create_with_name(&port->thread, platform_serial_thread, port,
"platform_serial_thread");
if (thrd_rc != thrd_success) {
status = thrd_status_to_zx_status(thrd_rc);
goto fail;
}
*out_handle = socket;
mtx_unlock(&port->lock);
return ZX_OK;
fail:
zx_handle_close(socket);
platform_serial_port_release(port);
mtx_unlock(&port->lock);
return status;
}