1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <ddk/binding.h>
6#include <ddk/device.h>
7#include <ddk/driver.h>
8
9#include <zircon/syscalls.h>
10#include <zircon/types.h>
11
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <threads.h>
16
17#define FIFOSIZE 256
18#define FIFOMASK (FIFOSIZE - 1)
19
20typedef struct console_ctx {
21    zx_device_t* zxdev;
22} console_device_t;
23
24static struct {
25    uint8_t data[FIFOSIZE];
26    uint32_t head;
27    uint32_t tail;
28    mtx_t lock;
29} fifo = {
30    .lock = MTX_INIT,
31};
32
33static zx_status_t fifo_read(uint8_t* out) {
34    if (fifo.head == fifo.tail) {
35        return -1;
36    }
37    *out = fifo.data[fifo.tail];
38    fifo.tail = (fifo.tail + 1) & FIFOMASK;
39    return ZX_OK;
40}
41
42static void fifo_write(uint8_t x) {
43    uint32_t next = (fifo.head + 1) & FIFOMASK;
44    if (next != fifo.tail) {
45        fifo.data[fifo.head] = x;
46        fifo.head = next;
47    }
48}
49
50static int debug_reader(void* arg) {
51    zx_device_t* dev = arg;
52    char ch;
53    for (;;) {
54        size_t length = 1;
55        zx_status_t status = zx_debug_read(get_root_resource(), &ch, &length);
56        if (status == ZX_OK && length == 1) {
57            mtx_lock(&fifo.lock);
58            if (fifo.head == fifo.tail) {
59                device_state_set(dev, DEV_STATE_READABLE);
60            }
61            fifo_write(ch);
62            mtx_unlock(&fifo.lock);
63        } else if (status == ZX_ERR_NOT_SUPPORTED) {
64            // Silently exit
65            return 0;
66        } else {
67            printf("console: error %d, length %zu from zx_debug_read syscall, exiting.\n",
68                    status, length);
69
70            return status;
71        }
72    }
73    return 0;
74}
75
76static zx_status_t console_read(void* ctx, void* buf, size_t count, zx_off_t off, size_t* actual) {
77    console_device_t* console = ctx;
78
79    uint8_t* data = buf;
80    mtx_lock(&fifo.lock);
81    while (count-- > 0) {
82        if (fifo_read(data))
83            break;
84        data++;
85    }
86    if (fifo.head == fifo.tail) {
87        device_state_clr(console->zxdev, DEV_STATE_READABLE);
88    }
89    mtx_unlock(&fifo.lock);
90    ssize_t length = data - (uint8_t*)buf;
91    if (length == 0) {
92        return ZX_ERR_SHOULD_WAIT;
93    }
94    *actual = length;
95    return ZX_OK;
96}
97
98#define MAX_WRITE_SIZE 256
99
100static zx_status_t console_write(void* ctx, const void* buf, size_t count, zx_off_t off, size_t* actual) {
101    const void* ptr = buf;
102    zx_status_t status = ZX_OK;
103    size_t total = 0;
104    while (count > 0) {
105        size_t xfer = (count > MAX_WRITE_SIZE) ? MAX_WRITE_SIZE : count;
106        if ((status = zx_debug_write(ptr, xfer)) < 0) {
107            break;
108        }
109        ptr += xfer;
110        count -= xfer;
111        total += xfer;
112    }
113    if (total > 0) {
114        *actual = total;
115        status = ZX_OK;
116     }
117     return status;
118}
119
120static void console_release(void* ctx) {
121    console_device_t* console = ctx;
122    free(console);
123}
124
125static zx_protocol_device_t console_device_proto = {
126    .version = DEVICE_OPS_VERSION,
127    .read = console_read,
128    .write = console_write,
129    .release = console_release,
130};
131
132static zx_status_t console_bind(void* ctx, zx_device_t* parent) {
133    console_device_t* console = calloc(1, sizeof(console_device_t));
134    if (!console) {
135        return ZX_ERR_NO_MEMORY;
136    }
137    device_add_args_t args = {
138        .version = DEVICE_ADD_ARGS_VERSION,
139        .name = "console",
140        .ctx = console,
141        .ops = &console_device_proto,
142    };
143
144    zx_status_t status = device_add(parent, &args, &console->zxdev);
145    if (status != ZX_OK) {
146        printf("console: device_add() failed\n");
147        free(console);
148        return status;
149    }
150
151    thrd_t t;
152    thrd_create_with_name(&t, debug_reader, console->zxdev, "debug-reader");
153
154    return ZX_OK;
155}
156
157static zx_driver_ops_t console_driver_ops = {
158    .version = DRIVER_OPS_VERSION,
159    .bind = console_bind,
160};
161
162ZIRCON_DRIVER_BEGIN(console, console_driver_ops, "zircon", "0.1", 1)
163    BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
164ZIRCON_DRIVER_END(console)
165