1227569Sphilip// Copyright 2018 The Fuchsia Authors. All rights reserved.
2300607Sarybchik// Use of this source code is governed by a BSD-style license that can be
3283514Sarybchik// found in the LICENSE file.
4227569Sphilip
5227569Sphilip#include <ddk/binding.h>
6283514Sarybchik#include <ddk/device.h>
7227569Sphilip#include <ddk/driver.h>
8283514Sarybchik
9283514Sarybchik#include <zircon/syscalls.h>
10283514Sarybchik#include <zircon/types.h>
11283514Sarybchik
12283514Sarybchik#include <stdio.h>
13228078Sphilip#include <stdlib.h>
14283514Sarybchik#include <string.h>
15283514Sarybchik#include <threads.h>
16283514Sarybchik
17283514Sarybchik// fifo must be a power of 2 for the math to work
18283514Sarybchik#define FIFOSIZE 32768
19283514Sarybchik#define FIFOMASK (FIFOSIZE - 1)
20283514Sarybchik
21283514Sarybchiktypedef struct {
22283514Sarybchik    zx_device_t* zxdev;
23283514Sarybchik    mtx_t lock;
24283514Sarybchik    uint32_t head;
25283514Sarybchik    uint32_t tail;
26283514Sarybchik    char data[FIFOSIZE];
27283514Sarybchik} fifodev_t;
28283514Sarybchik
29283514Sarybchikstatic size_t fifo_readable(fifodev_t* fifo) {
30228078Sphilip    return (fifo->head - fifo->tail) & FIFOMASK;
31227569Sphilip}
32227569Sphilip
33227569Sphilipstatic size_t fifo_writable(fifodev_t* fifo) {
34227569Sphilip    return FIFOMASK - ((fifo->head - fifo->tail) & FIFOMASK);
35227569Sphilip}
36227569Sphilip
37227569Sphilipstatic size_t fifo_put(fifodev_t* fifo, const void* buf, size_t len) {
38227569Sphilip    size_t count = fifo_writable(fifo);
39227569Sphilip    uint32_t pos = fifo->head & FIFOMASK;
40227569Sphilip    size_t space = FIFOSIZE - pos;
41227569Sphilip    if (count > space) { // don't wrap around (single copy)
42279183Sarybchik        count = space;
43279183Sarybchik    }
44279183Sarybchik    if (count > len) { // limit to requested count
45279183Sarybchik        count = len;
46279183Sarybchik    }
47279183Sarybchik    memcpy(fifo->data + pos, buf, count);
48279183Sarybchik    fifo->head += count;
49227569Sphilip    return count;
50227569Sphilip}
51227569Sphilip
52227569Sphilipstatic size_t fifo_get(fifodev_t* fifo, void* buf, size_t len) {
53227569Sphilip    size_t count = fifo_readable(fifo);
54227569Sphilip    uint32_t pos = fifo->tail & FIFOMASK;
55227569Sphilip    size_t space = FIFOSIZE - pos;
56227569Sphilip    if (count > space) { // don't wrap around (single copy)
57227569Sphilip        count = space;
58227569Sphilip    }
59227569Sphilip    if (count > len) { // limit to requested count
60227569Sphilip        count = len;
61227569Sphilip    }
62227569Sphilip    memcpy(buf, fifo->data + pos, count);
63227569Sphilip    fifo->tail += count;
64227569Sphilip    return count;
65227569Sphilip}
66227569Sphilip
67227569Sphilip
68227569Sphilipstatic zx_status_t fifo_read(void* ctx, void* buf, size_t len,
69227569Sphilip                             zx_off_t off, size_t* actual) {
70227569Sphilip    fifodev_t* fifo = ctx;
71227569Sphilip
72227569Sphilip    mtx_lock(&fifo->lock);
73227569Sphilip    size_t n = 0;
74227569Sphilip    size_t count;
75227569Sphilip    while ((count = fifo_get(fifo, buf, len)) > 0) {
76227569Sphilip        len -= count;
77227569Sphilip        buf += count;
78227569Sphilip        n += count;
79227569Sphilip    }
80227569Sphilip    if (n == 0) {
81227569Sphilip        device_state_clr(fifo->zxdev, DEV_STATE_READABLE);
82227569Sphilip    } else {
83227569Sphilip        device_state_set(fifo->zxdev, DEV_STATE_WRITABLE);
84227569Sphilip    }
85227569Sphilip    mtx_unlock(&fifo->lock);
86227569Sphilip    *actual = n;
87227569Sphilip
88227569Sphilip    return (n == 0) ? ZX_ERR_SHOULD_WAIT : ZX_OK;
89227569Sphilip}
90227569Sphilip
91227569Sphilipstatic zx_status_t fifo_write(void* ctx, const void* buf, size_t len,
92227569Sphilip                              zx_off_t off, size_t* actual) {
93227569Sphilip
94227569Sphilip    fifodev_t* fifo = ctx;
95227569Sphilip
96227569Sphilip    mtx_lock(&fifo->lock);
97227569Sphilip    size_t n = 0;
98227569Sphilip    size_t count;
99227569Sphilip    while ((count = fifo_put(fifo, buf, len)) > 0) {
100227569Sphilip        len -= count;
101227569Sphilip        buf += count;
102227569Sphilip        n += count;
103227569Sphilip    }
104227569Sphilip    if (n == 0) {
105227569Sphilip        device_state_clr(fifo->zxdev, DEV_STATE_WRITABLE);
106227569Sphilip    } else {
107227569Sphilip        device_state_set(fifo->zxdev, DEV_STATE_READABLE);
108227569Sphilip    }
109227569Sphilip    mtx_unlock(&fifo->lock);
110227569Sphilip    *actual = n;
111227569Sphilip
112227569Sphilip    return (n == 0) ? ZX_ERR_SHOULD_WAIT : ZX_OK;
113227569Sphilip}
114227569Sphilip
115227569Sphilipstatic void fifo_release(void* ctx) {
116227569Sphilip    fifodev_t* fifo = ctx;
117227569Sphilip    free(fifo);
118227569Sphilip}
119227569Sphilip
120227569Sphilipstatic zx_protocol_device_t fifo_ops = {
121227569Sphilip    .version = DEVICE_OPS_VERSION,
122227569Sphilip    .read = fifo_read,
123227569Sphilip    .write = fifo_write,
124227569Sphilip    .release = fifo_release,
125227569Sphilip};
126227569Sphilip
127227569Sphilipstatic zx_status_t fifo_bind(void* ctx, zx_device_t* parent) {
128227569Sphilip    fifodev_t* fifo = calloc(1, sizeof(fifodev_t));
129227569Sphilip    if (fifo == NULL) {
130227569Sphilip        return ZX_ERR_NO_MEMORY;
131227569Sphilip    }
132227569Sphilip    mtx_init(&fifo->lock, mtx_plain);
133227569Sphilip
134227569Sphilip    device_add_args_t args = {
135227569Sphilip        .version = DEVICE_ADD_ARGS_VERSION,
136227569Sphilip        .name = "demo-fifo",
137227569Sphilip        .ctx = fifo,
138227569Sphilip        .ops = &fifo_ops,
139227569Sphilip    };
140227569Sphilip    zx_status_t status = device_add(parent, &args, &fifo->zxdev);
141227569Sphilip    if (status != ZX_OK) {
142227569Sphilip        free(fifo);
143227569Sphilip        return status;
144227569Sphilip    }
145227569Sphilip
146227569Sphilip    // initially we're empty, so writable but not readable
147227569Sphilip    device_state_set(fifo->zxdev, DEV_STATE_WRITABLE);
148227569Sphilip
149227569Sphilip    return ZX_OK;
150227569Sphilip}
151227569Sphilip
152227569Sphilipstatic zx_driver_ops_t fifo_driver_ops = {
153227569Sphilip    .version = DRIVER_OPS_VERSION,
154227569Sphilip    .bind = fifo_bind,
155227569Sphilip};
156227569Sphilip
157227569SphilipZIRCON_DRIVER_BEGIN(demo_fifo, fifo_driver_ops, "zircon", "0.1", 1)
158227569Sphilip    BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
159227569SphilipZIRCON_DRIVER_END(demo_fifo)
160227569Sphilip