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.
do_write(zx_handle_t fifo,block_fifo_request_t * request,size_t count)15 static 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 
do_read(zx_handle_t fifo,block_fifo_response_t * response)42 static 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 
62 typedef struct block_completion {
63     sync_completion_t completion;
64     zx_status_t status;
65 } block_sync_completion_t;
66 
67 typedef struct fifo_client {
68     zx_handle_t fifo;
69     block_sync_completion_t groups[MAX_TXN_GROUP_COUNT];
70 } fifo_client_t;
71 
block_fifo_create_client(zx_handle_t fifo,fifo_client_t ** out)72 zx_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 
block_fifo_release_client(fifo_client_t * client)83 void 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 
block_fifo_txn(fifo_client_t * client,block_fifo_request_t * requests,size_t count)92 zx_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