blob: 8f0c8fe9d11fcdf33c356f6d8d25d844fc7f909e [file] [log] [blame]
// Copyright 2016 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/binding.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#define FIFOSIZE 256
#define FIFOMASK (FIFOSIZE - 1)
typedef struct console_ctx {
zx_device_t* zxdev;
} console_device_t;
static struct {
uint8_t data[FIFOSIZE];
uint32_t head;
uint32_t tail;
mtx_t lock;
} fifo = {
.lock = MTX_INIT,
};
static zx_status_t fifo_read(uint8_t* out) {
if (fifo.head == fifo.tail) {
return -1;
}
*out = fifo.data[fifo.tail];
fifo.tail = (fifo.tail + 1) & FIFOMASK;
return ZX_OK;
}
static void fifo_write(uint8_t x) {
uint32_t next = (fifo.head + 1) & FIFOMASK;
if (next != fifo.tail) {
fifo.data[fifo.head] = x;
fifo.head = next;
}
}
static int debug_reader(void* arg) {
zx_device_t* dev = arg;
char ch;
for (;;) {
size_t length = 1;
zx_status_t status = zx_debug_read(get_root_resource(), &ch, &length);
if (status == ZX_OK && length == 1) {
mtx_lock(&fifo.lock);
if (fifo.head == fifo.tail) {
device_state_set(dev, DEV_STATE_READABLE);
}
fifo_write(ch);
mtx_unlock(&fifo.lock);
} else if (status == ZX_ERR_NOT_SUPPORTED) {
// Silently exit
return 0;
} else {
printf("console: error %d, length %zu from zx_debug_read syscall, exiting.\n",
status, length);
return status;
}
}
return 0;
}
static zx_status_t console_read(void* ctx, void* buf, size_t count, zx_off_t off, size_t* actual) {
console_device_t* console = ctx;
uint8_t* data = buf;
mtx_lock(&fifo.lock);
while (count-- > 0) {
if (fifo_read(data))
break;
data++;
}
if (fifo.head == fifo.tail) {
device_state_clr(console->zxdev, DEV_STATE_READABLE);
}
mtx_unlock(&fifo.lock);
ssize_t length = data - (uint8_t*)buf;
if (length == 0) {
return ZX_ERR_SHOULD_WAIT;
}
*actual = length;
return ZX_OK;
}
#define MAX_WRITE_SIZE 256
static zx_status_t console_write(void* ctx, const void* buf, size_t count, zx_off_t off, size_t* actual) {
const void* ptr = buf;
zx_status_t status = ZX_OK;
size_t total = 0;
while (count > 0) {
size_t xfer = (count > MAX_WRITE_SIZE) ? MAX_WRITE_SIZE : count;
if ((status = zx_debug_write(ptr, xfer)) < 0) {
break;
}
ptr += xfer;
count -= xfer;
total += xfer;
}
if (total > 0) {
*actual = total;
status = ZX_OK;
}
return status;
}
static void console_release(void* ctx) {
console_device_t* console = ctx;
free(console);
}
static zx_protocol_device_t console_device_proto = {
.version = DEVICE_OPS_VERSION,
.read = console_read,
.write = console_write,
.release = console_release,
};
static zx_status_t console_bind(void* ctx, zx_device_t* parent) {
// If we're in an isolated devmgr, we won't have the root resource. In that
// case, just don't bind this driver.
if (get_root_resource() == ZX_HANDLE_INVALID) {
return ZX_ERR_NOT_SUPPORTED;
}
console_device_t* console = calloc(1, sizeof(console_device_t));
if (!console) {
return ZX_ERR_NO_MEMORY;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "console",
.ctx = console,
.ops = &console_device_proto,
};
zx_status_t status = device_add(parent, &args, &console->zxdev);
if (status != ZX_OK) {
printf("console: device_add() failed\n");
free(console);
return status;
}
thrd_t t;
thrd_create_with_name(&t, debug_reader, console->zxdev, "debug-reader");
return ZX_OK;
}
static zx_driver_ops_t console_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = console_bind,
};
ZIRCON_DRIVER_BEGIN(console, console_driver_ops, "zircon", "0.1", 1)
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
ZIRCON_DRIVER_END(console)