1/*
2 * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 */
6#define __NEED_off_t
7
8#include <sel4vchan/vmm_manager.h>
9#include <sel4vchan/vchan_component.h>
10#include <sel4vchan/vchan_sharemem.h>
11
12static libvchan_t *vchan_init(int domain, int port, int server);
13
14/*
15    Set up the vchan connection interface
16    Currently, the number of vchan connection interfaces allowed is hardcoded to 1 per component
17*/
18libvchan_t *link_vchan_comp(libvchan_t *ctrl, camkes_vchan_con_t *vchan_com)
19{
20    if (ctrl == NULL) {
21        return NULL;
22    }
23
24    int res;
25    ctrl->con = vchan_com;
26    /* Perform vchan component initialisation */
27    vchan_connect_t t = {
28        .v.domain = vchan_com->source_dom_number,
29        .v.dest = ctrl->domain_num,
30        .v.port = ctrl->port_num,
31        .server = ctrl->is_server,
32    };
33
34    res = ctrl->con->connect(t);
35    if (res < 0) {
36        free(ctrl);
37        return NULL;
38    }
39    return ctrl;
40}
41
42/*
43    Create a new vchan server instance
44*/
45libvchan_t *libvchan_server_init(int domain, int port, size_t read_min, size_t write_min)
46{
47    return vchan_init(domain, port, 1);
48}
49
50/*
51    Create a new vchan client instance
52*/
53libvchan_t *libvchan_client_init(int domain, int port)
54{
55    return vchan_init(domain, port, 0);
56}
57
58/*
59    Create a new client/server instance
60*/
61libvchan_t *vchan_init(int domain, int port, int server)
62{
63    libvchan_t *new_connection = malloc(sizeof(libvchan_t));
64    if (new_connection == NULL) {
65        return NULL;
66    }
67
68    new_connection->is_server = server;
69    new_connection->server_persists = 1;
70    new_connection->blocking = 1;
71    new_connection->domain_num = domain;
72    new_connection->port_num = port;
73
74    return new_connection;
75}
76
77/*
78    Reading and writing to the vchan
79*/
80int libvchan_write(libvchan_t *ctrl, const void *data, size_t size)
81{
82    return libvchan_readwrite(ctrl, (void *) data, size, VCHAN_SEND, VCHAN_STREAM);
83}
84
85int libvchan_read(libvchan_t *ctrl, void *data, size_t size)
86{
87    return libvchan_readwrite(ctrl, data, size, VCHAN_RECV, VCHAN_STREAM);
88}
89
90int libvchan_send(libvchan_t *ctrl, const void *data, size_t size)
91{
92    return libvchan_readwrite(ctrl, (void *) data, size, VCHAN_SEND, VCHAN_NOSTREAM);
93}
94
95int libvchan_recv(libvchan_t *ctrl, void *data, size_t size)
96{
97    return libvchan_readwrite(ctrl, data, size, VCHAN_RECV, VCHAN_NOSTREAM);
98}
99
100/*
101    Return correct buffer for given vchan read/write action
102*/
103vchan_buf_t *get_vchan_buf(vchan_ctrl_t *args, camkes_vchan_con_t *c, int action)
104{
105    intptr_t buf_pos = c->get_buf(*args, action);
106    /* Check that a buffer was retrieved */
107    if (buf_pos == 0) {
108        return NULL;
109    }
110
111    if (c->data_buf == NULL) {
112        return NULL;
113    }
114
115    void *addr = c->data_buf;
116    addr += buf_pos;
117    vchan_buf_t *b = (vchan_buf_t *)(addr);
118
119    return b;
120}
121
122/*
123    Helper function
124    Allows connected components to get correct vchan buffer for read/write
125*/
126vchan_buf_t *get_vchan_ctrl_databuf(libvchan_t *ctrl, int action)
127{
128    vchan_ctrl_t args = {
129        .domain = ctrl->con->source_dom_number,
130        .dest = ctrl->domain_num,
131        .port = ctrl->port_num,
132    };
133
134    return get_vchan_buf(&args, ctrl->con, action);
135}
136
137static size_t get_actionsize(int type, size_t size, vchan_buf_t *buf)
138{
139    assert(buf != NULL);
140    size_t filled = abs(buf->write_pos - buf->read_pos);
141    if (type == VCHAN_SEND) {
142        return MIN(VCHAN_BUF_SIZE - filled, size);
143    } else {
144        return MIN(filled, size);
145    }
146}
147
148/*
149    Perform a vchan read/write action into a given buffer
150     This function is intended for non Init components, Init components have a different method
151*/
152int libvchan_readwrite(libvchan_t *ctrl, void *data, size_t size, int cmd, int stream)
153{
154    int *update;
155    vchan_buf_t *b = get_vchan_ctrl_databuf(ctrl, cmd);
156    if (b == NULL) {
157        return -1;
158    }
159
160    /*
161        How data is stored in a given vchan buffer
162
163        Position of data in buffer is given by
164            (either b->write_pos or b->read_pos) % VCHAN_BUF_SIZE
165            read_pos is incremented by x when x bytes are read from the buffer
166            write_pos is incremented by x when x bytes are read from the buffer
167
168        Amount of bytes in buffer is given by the difference between read_pos and write_pos
169            if write_pos > read_pos, there is data yet to be read
170    */
171    if (cmd == VCHAN_SEND) {
172        update = &b->write_pos;
173    } else {
174        update = &b->read_pos;
175    }
176
177
178    size_t data_sz = size;
179    while (data_sz > 0) {
180        size_t call_size = MIN(data_sz, VCHAN_BUF_SIZE);
181        size_t ssize = get_actionsize(cmd, call_size, b);
182        while (ssize == 0) {
183            ctrl->con->wait();
184            ssize = get_actionsize(cmd, call_size, b);
185        }
186
187        call_size = ssize;
188
189        /*
190            Because this buffer is circular,
191                Data may have to wrap around to the start of the buffer
192                This is achieved by doing two copies, one to buffer end
193                And one at start of buffer for remaining data
194        */
195        off_t start = (*update % VCHAN_BUF_SIZE);
196        off_t remain = 0;
197
198        if (start + call_size > VCHAN_BUF_SIZE) {
199            remain = (start + call_size) - VCHAN_BUF_SIZE;
200            call_size -= remain;
201        }
202
203        void *dbuf = &b->sync_data;
204
205        if (cmd == VCHAN_SEND) {
206            /*
207                src = address given by function caller
208                dest = vchan dataport
209            */
210            memcpy(dbuf + start, data, call_size);
211            memcpy(dbuf, data + call_size, remain);
212        } else {
213            /*
214                src = vchan dataport
215                dest = address given by function caller
216            */
217            memcpy(data, ((void *) dbuf) + start, call_size);
218            memcpy(data + call_size, dbuf, remain);
219        }
220        __sync_synchronize();
221
222        /*
223            Update either the read byte counter or the written byte counter
224                With how much was written or read
225        */
226        *update += (call_size + remain);
227        /*
228            If stream, we have written as much data as we can in one pass.
229                Otherwise, continue to write data and block block if the buffer is full
230        */
231        if (stream) {
232            ctrl->con->alert();
233            return (call_size + remain);
234        } else {
235            data_sz -= (call_size + remain);
236        }
237
238        data = data + (call_size + remain);
239        ctrl->con->alert();
240    }
241
242    return size;
243}
244
245
246/*
247    Wait for data to arrive to a component from a given vchan
248*/
249int libvchan_wait(libvchan_t *ctrl)
250{
251    vchan_buf_t *b = get_vchan_ctrl_databuf(ctrl, VCHAN_RECV);
252    assert(b != NULL);
253    size_t filled = abs(b->write_pos - b->read_pos);
254    while (filled == 0) {
255        ctrl->con->wait();
256        b = get_vchan_ctrl_databuf(ctrl, VCHAN_RECV);
257        filled = abs(b->write_pos - b->read_pos);
258    }
259
260    return 0;
261}
262
263void libvchan_close(libvchan_t *ctrl)
264{
265    vchan_connect_t t = {
266        .v.domain = ctrl->con->source_dom_number,
267        .v.dest = ctrl->domain_num,
268        .v.port = ctrl->port_num,
269        .server = ctrl->is_server,
270    };
271
272    ctrl->con->disconnect(t);
273}
274
275int libvchan_is_open(libvchan_t *ctrl)
276{
277    vchan_ctrl_t args = {
278        .domain = ctrl->con->source_dom_number,
279        .dest = ctrl->domain_num,
280        .port = ctrl->port_num,
281    };
282
283    return ctrl->con->status(args);
284}
285
286/* Returns nonzero if the peer has closed connection */
287int libvchan_is_eof(libvchan_t *ctrl)
288{
289    if (libvchan_is_open(ctrl) != 1) {
290        return 1;
291    }
292    return 0;
293}
294
295/*
296    How much data can be read from the vchan
297*/
298int libvchan_data_ready(libvchan_t *ctrl)
299{
300    vchan_buf_t *b = get_vchan_ctrl_databuf(ctrl, VCHAN_RECV);
301    assert(b != NULL);
302    size_t filled = abs(b->write_pos - b->read_pos);
303
304    return filled;
305}
306
307/*
308    How much data can be written to the vchan
309*/
310int libvchan_buffer_space(libvchan_t *ctrl)
311{
312    if (libvchan_is_open(ctrl) != 1) {
313        return 0;
314    }
315
316    vchan_buf_t *b = get_vchan_ctrl_databuf(ctrl, VCHAN_SEND);
317    assert(b != NULL);
318    size_t filled = abs(b->write_pos - b->read_pos);
319
320    return VCHAN_BUF_SIZE - filled;
321}
322