1// Copyright 2017 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 <assert.h>
6#include <unistd.h>
7
8#include <block-client/client.h>
9#include <zircon/compiler.h>
10#include <zircon/device/block.h>
11#include <zircon/syscalls.h>
12#include <lib/sync/completion.h>
13
14// Writes on a FIFO, repeating the write later if the FIFO is full.
15static zx_status_t do_write(zx_handle_t fifo, block_fifo_request_t* request, size_t count) {
16    zx_status_t status;
17    while (true) {
18        size_t actual;
19        status = zx_fifo_write(fifo, sizeof(block_fifo_request_t), request, count, &actual);
20        if (status == ZX_ERR_SHOULD_WAIT) {
21            zx_signals_t signals;
22            if ((status = zx_object_wait_one(fifo,
23                                             ZX_FIFO_WRITABLE | ZX_FIFO_PEER_CLOSED,
24                                             ZX_TIME_INFINITE, &signals)) != ZX_OK) {
25                return status;
26            } else if (signals & ZX_FIFO_PEER_CLOSED) {
27                return ZX_ERR_PEER_CLOSED;
28            }
29            // Try writing again...
30        } else if (status == ZX_OK) {
31            count -= actual;
32            request += actual;
33            if (count == 0) {
34                return ZX_OK;
35            }
36        } else {
37            return status;
38        }
39    }
40}
41
42static zx_status_t do_read(zx_handle_t fifo, block_fifo_response_t* response) {
43    zx_status_t status;
44    while (true) {
45        status = zx_fifo_read(fifo, sizeof(*response), response, 1, NULL);
46        if (status == ZX_ERR_SHOULD_WAIT) {
47            zx_signals_t signals;
48            if ((status = zx_object_wait_one(fifo,
49                                             ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED,
50                                             ZX_TIME_INFINITE, &signals)) != ZX_OK) {
51                return status;
52            } else if (signals & ZX_FIFO_PEER_CLOSED) {
53                return ZX_ERR_PEER_CLOSED;
54            }
55            // Try reading again...
56        } else {
57            return status;
58        }
59    }
60}
61
62typedef struct block_completion {
63    sync_completion_t completion;
64    zx_status_t status;
65} block_sync_completion_t;
66
67typedef struct fifo_client {
68    zx_handle_t fifo;
69    block_sync_completion_t groups[MAX_TXN_GROUP_COUNT];
70} fifo_client_t;
71
72zx_status_t block_fifo_create_client(zx_handle_t fifo, fifo_client_t** out) {
73    fifo_client_t* client = calloc(sizeof(fifo_client_t), 1);
74    if (client == NULL) {
75        zx_handle_close(fifo);
76        return ZX_ERR_NO_MEMORY;
77    }
78    client->fifo = fifo;
79    *out = client;
80    return ZX_OK;
81}
82
83void block_fifo_release_client(fifo_client_t* client) {
84    if (client == NULL) {
85        return;
86    }
87
88    zx_handle_close(client->fifo);
89    free(client);
90}
91
92zx_status_t block_fifo_txn(fifo_client_t* client, block_fifo_request_t* requests, size_t count) {
93    if (count == 0) {
94        return ZX_OK;
95    }
96
97    groupid_t group = requests[0].group;
98    assert(group < MAX_TXN_GROUP_COUNT);
99    sync_completion_reset(&client->groups[group].completion);
100    client->groups[group].status = ZX_ERR_IO;
101
102    zx_status_t status;
103    for (size_t i = 0; i < count; i++) {
104        assert(requests[i].group == group);
105        requests[i].opcode = (requests[i].opcode & BLOCKIO_OP_MASK) | BLOCKIO_GROUP_ITEM;
106    }
107
108    requests[0].opcode |= BLOCKIO_BARRIER_BEFORE;
109    requests[count - 1].opcode |= BLOCKIO_GROUP_LAST | BLOCKIO_BARRIER_AFTER;
110
111    if ((status = do_write(client->fifo, &requests[0], count)) != ZX_OK) {
112        return status;
113    }
114
115    // As expected by the protocol, when we send one "BLOCKIO_GROUP_LAST" message, we
116    // must read a reply message.
117    block_fifo_response_t response;
118    if ((status = do_read(client->fifo, &response)) != ZX_OK) {
119        return status;
120    }
121
122    // Wake up someone who is waiting (it might be ourselves)
123    groupid_t response_group = response.group;
124    client->groups[response_group].status = response.status;
125    sync_completion_signal(&client->groups[response_group].completion);
126
127    // Wait for someone to signal us.
128    sync_completion_wait(&client->groups[group].completion, ZX_TIME_INFINITE);
129
130    return client->groups[group].status;
131}
132