source: mainline/uspace/drv/block/virtio-blk/virtio-blk.c@ 58168e0

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 58168e0 was e2f332c, checked in by Jakub Jermar <jakub@…>, 7 years ago

Fix ccheck

  • Property mode set to 100644
File size: 13.3 KB
Line 
1/*
2 * Copyright (c) 2019 Jakub Jermar
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "virtio-blk.h"
30
31#include <stdio.h>
32#include <stdint.h>
33
34#include <as.h>
35#include <ddf/driver.h>
36#include <ddf/interrupt.h>
37#include <ddf/log.h>
38#include <pci_dev_iface.h>
39#include <fibril_synch.h>
40
41#include <bd_srv.h>
42
43#include <virtio-pci.h>
44
45#define NAME "virtio-blk"
46
47#define VIRTIO_BLK_NUM_QUEUES 1
48
49#define RQ_QUEUE 0
50
51/*
52 * VIRTIO_BLK requests need at least two descriptors so that device-read-only
53 * buffers are separated from device-writable buffers. For convenience, we
54 * always use three descriptors for the request header, buffer and footer.
55 * We therefore organize the virtque so that first RQ_BUFFERS descriptors are
56 * used for request headers, the following RQ_BUFFERS descriptors are used
57 * for in/out buffers and the last RQ_BUFFERS descriptors are used for request
58 * footers.
59 */
60#define REQ_HEADER_DESC(descno) (0 * RQ_BUFFERS + (descno))
61#define REQ_BUFFER_DESC(descno) (1 * RQ_BUFFERS + (descno))
62#define REQ_FOOTER_DESC(descno) (2 * RQ_BUFFERS + (descno))
63
64static errno_t virtio_blk_dev_add(ddf_dev_t *dev);
65
66static driver_ops_t virtio_blk_driver_ops = {
67 .dev_add = virtio_blk_dev_add
68};
69
70static driver_t virtio_blk_driver = {
71 .name = NAME,
72 .driver_ops = &virtio_blk_driver_ops
73};
74
75static void virtio_blk_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
76{
77 virtio_blk_t *virtio_blk = (virtio_blk_t *) ddf_dev_data_get(dev);
78 virtio_dev_t *vdev = &virtio_blk->virtio_dev;
79
80 uint16_t descno;
81 uint32_t len;
82
83 while (virtio_virtq_consume_used(vdev, RQ_QUEUE, &descno, &len)) {
84 assert(descno < RQ_BUFFERS);
85 fibril_mutex_lock(&virtio_blk->completion_lock[descno]);
86 fibril_condvar_signal(&virtio_blk->completion_cv[descno]);
87 fibril_mutex_unlock(&virtio_blk->completion_lock[descno]);
88 }
89}
90
91static errno_t virtio_blk_register_interrupt(ddf_dev_t *dev)
92{
93 virtio_blk_t *virtio_blk = (virtio_blk_t *) ddf_dev_data_get(dev);
94 virtio_dev_t *vdev = &virtio_blk->virtio_dev;
95
96 async_sess_t *parent_sess = ddf_dev_parent_sess_get(dev);
97 if (parent_sess == NULL)
98 return ENOMEM;
99
100 hw_res_list_parsed_t res;
101 hw_res_list_parsed_init(&res);
102
103 hw_res_list_parsed_init(&res);
104 errno_t rc = hw_res_get_list_parsed(parent_sess, &res, 0);
105 if (rc != EOK)
106 return rc;
107
108 if (res.irqs.count < 1) {
109 hw_res_list_parsed_clean(&res);
110 return EINVAL;
111 }
112
113 virtio_blk->irq = res.irqs.irqs[0];
114 hw_res_list_parsed_clean(&res);
115
116 irq_pio_range_t pio_ranges[] = {
117 {
118 .base = vdev->isr_phys,
119 .size = sizeof(vdev->isr_phys),
120 }
121 };
122
123 irq_cmd_t irq_commands[] = {
124 {
125 .cmd = CMD_PIO_READ_8,
126 .addr = (void *) vdev->isr_phys,
127 .dstarg = 2
128 },
129 {
130 .cmd = CMD_PREDICATE,
131 .value = 1,
132 .srcarg = 2
133 },
134 {
135 .cmd = CMD_ACCEPT
136 }
137 };
138
139 irq_code_t irq_code = {
140 .rangecount = sizeof(pio_ranges) / sizeof(irq_pio_range_t),
141 .ranges = pio_ranges,
142 .cmdcount = sizeof(irq_commands) / sizeof(irq_cmd_t),
143 .cmds = irq_commands
144 };
145
146 return register_interrupt_handler(dev, virtio_blk->irq,
147 virtio_blk_irq_handler, &irq_code, &virtio_blk->irq_handle);
148}
149
150static errno_t virtio_blk_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
151{
152 return EOK;
153}
154
155static errno_t virtio_blk_bd_close(bd_srv_t *bd)
156{
157 return EOK;
158}
159
160static errno_t virtio_blk_rw_block(virtio_blk_t *virtio_blk, bool read,
161 aoff64_t ba, void *buf)
162{
163 virtio_dev_t *vdev = &virtio_blk->virtio_dev;
164
165 /*
166 * Allocate a descriptor.
167 *
168 * The allocated descno will determine the header descriptor
169 * (REQ_HEADER_DESC), the buffer descriptor (REQ_BUFFER_DESC) and the
170 * footer (REQ_FOOTER_DESC) descriptor.
171 */
172 fibril_mutex_lock(&virtio_blk->free_lock);
173 uint16_t descno = virtio_alloc_desc(vdev, RQ_QUEUE,
174 &virtio_blk->rq_free_head);
175 while (descno == (uint16_t) -1U) {
176 fibril_condvar_wait(&virtio_blk->free_cv,
177 &virtio_blk->free_lock);
178 descno = virtio_alloc_desc(vdev, RQ_QUEUE,
179 &virtio_blk->rq_free_head);
180 }
181 fibril_mutex_unlock(&virtio_blk->free_lock);
182
183 assert(descno < RQ_BUFFERS);
184
185 /* Setup the request header */
186 virtio_blk_req_header_t *req_header =
187 (virtio_blk_req_header_t *) virtio_blk->rq_header[descno];
188 memset(req_header, 0, sizeof(virtio_blk_req_header_t));
189 pio_write_le32(&req_header->type,
190 read ? VIRTIO_BLK_T_IN : VIRTIO_BLK_T_OUT);
191 pio_write_le64(&req_header->sector, ba);
192
193 /* Copy write data to the request. */
194 if (!read)
195 memcpy(virtio_blk->rq_buf[descno], buf, VIRTIO_BLK_BLOCK_SIZE);
196
197 fibril_mutex_lock(&virtio_blk->completion_lock[descno]);
198
199 /*
200 * Set the descriptors, chain them in the virtqueue and notify the
201 * device.
202 */
203 virtio_virtq_desc_set(vdev, RQ_QUEUE, REQ_HEADER_DESC(descno),
204 virtio_blk->rq_header_p[descno], sizeof(virtio_blk_req_header_t),
205 VIRTQ_DESC_F_NEXT, REQ_BUFFER_DESC(descno));
206 virtio_virtq_desc_set(vdev, RQ_QUEUE, REQ_BUFFER_DESC(descno),
207 virtio_blk->rq_buf_p[descno], VIRTIO_BLK_BLOCK_SIZE,
208 VIRTQ_DESC_F_NEXT | (read ? VIRTQ_DESC_F_WRITE : 0),
209 REQ_FOOTER_DESC(descno));
210 virtio_virtq_desc_set(vdev, RQ_QUEUE, REQ_FOOTER_DESC(descno),
211 virtio_blk->rq_footer_p[descno], sizeof(virtio_blk_req_footer_t),
212 VIRTQ_DESC_F_WRITE, 0);
213 virtio_virtq_produce_available(vdev, RQ_QUEUE, descno);
214
215 /*
216 * Wait for the completion of the request.
217 */
218 fibril_condvar_wait(&virtio_blk->completion_cv[descno],
219 &virtio_blk->completion_lock[descno]);
220 fibril_mutex_unlock(&virtio_blk->completion_lock[descno]);
221
222 errno_t rc;
223 virtio_blk_req_footer_t *footer =
224 (virtio_blk_req_footer_t *) virtio_blk->rq_footer[descno];
225 switch (footer->status) {
226 case VIRTIO_BLK_S_OK:
227 rc = EOK;
228 break;
229 case VIRTIO_BLK_S_IOERR:
230 rc = EIO;
231 break;
232 case VIRTIO_BLK_S_UNSUPP:
233 rc = ENOTSUP;
234 break;
235 default:
236 ddf_msg(LVL_DEBUG, "device returned unknown status=%d\n",
237 (int) footer->status);
238 rc = EIO;
239 break;
240 }
241
242 /* Copy read data from the request */
243 if (rc == EOK && read)
244 memcpy(buf, virtio_blk->rq_buf[descno], VIRTIO_BLK_BLOCK_SIZE);
245
246 /* Free the descriptor and buffer */
247 fibril_mutex_lock(&virtio_blk->free_lock);
248 virtio_free_desc(vdev, RQ_QUEUE, &virtio_blk->rq_free_head, descno);
249 fibril_condvar_signal(&virtio_blk->free_cv);
250 fibril_mutex_unlock(&virtio_blk->free_lock);
251
252 return rc;
253}
254
255static errno_t virtio_blk_bd_rw_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
256 void *buf, size_t size, bool read)
257{
258 virtio_blk_t *virtio_blk = (virtio_blk_t *) bd->srvs->sarg;
259 aoff64_t i;
260 errno_t rc;
261
262 if (size != cnt * VIRTIO_BLK_BLOCK_SIZE)
263 return EINVAL;
264
265 for (i = 0; i < cnt; i++) {
266 rc = virtio_blk_rw_block(virtio_blk, read, ba + i,
267 buf + i * VIRTIO_BLK_BLOCK_SIZE);
268 if (rc != EOK)
269 return rc;
270 }
271
272 return EOK;
273}
274
275static errno_t virtio_blk_bd_read_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
276 void *buf, size_t size)
277{
278 return virtio_blk_bd_rw_blocks(bd, ba, cnt, buf, size, true);
279}
280
281static errno_t virtio_blk_bd_write_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
282 const void *buf, size_t size)
283{
284 return virtio_blk_bd_rw_blocks(bd, ba, cnt, (void *) buf, size, false);
285}
286
287static errno_t virtio_blk_bd_get_block_size(bd_srv_t *bd, size_t *size)
288{
289 *size = VIRTIO_BLK_BLOCK_SIZE;
290 return EOK;
291}
292
293static errno_t virtio_blk_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *nb)
294{
295 virtio_blk_t *virtio_blk = (virtio_blk_t *) bd->srvs->sarg;
296 virtio_blk_cfg_t *blkcfg = virtio_blk->virtio_dev.device_cfg;
297 *nb = pio_read_le64(&blkcfg->capacity);
298 return EOK;
299}
300
301bd_ops_t virtio_blk_bd_ops = {
302 .open = virtio_blk_bd_open,
303 .close = virtio_blk_bd_close,
304 .read_blocks = virtio_blk_bd_read_blocks,
305 .write_blocks = virtio_blk_bd_write_blocks,
306 .get_block_size = virtio_blk_bd_get_block_size,
307 .get_num_blocks = virtio_blk_bd_get_num_blocks,
308};
309
310static errno_t virtio_blk_initialize(ddf_dev_t *dev)
311{
312 virtio_blk_t *virtio_blk = ddf_dev_data_alloc(dev,
313 sizeof(virtio_blk_t));
314 if (!virtio_blk)
315 return ENOMEM;
316
317 fibril_mutex_initialize(&virtio_blk->free_lock);
318 fibril_condvar_initialize(&virtio_blk->free_cv);
319
320 for (unsigned i = 0; i < RQ_BUFFERS; i++) {
321 fibril_mutex_initialize(&virtio_blk->completion_lock[i]);
322 fibril_condvar_initialize(&virtio_blk->completion_cv[i]);
323 }
324
325 bd_srvs_init(&virtio_blk->bds);
326 virtio_blk->bds.ops = &virtio_blk_bd_ops;
327 virtio_blk->bds.sarg = virtio_blk;
328
329 errno_t rc = virtio_pci_dev_initialize(dev, &virtio_blk->virtio_dev);
330 if (rc != EOK)
331 return rc;
332
333 virtio_dev_t *vdev = &virtio_blk->virtio_dev;
334 virtio_pci_common_cfg_t *cfg = virtio_blk->virtio_dev.common_cfg;
335
336 /*
337 * Register IRQ
338 */
339 rc = virtio_blk_register_interrupt(dev);
340 if (rc != EOK)
341 goto fail;
342
343 /* Reset the device and negotiate the feature bits */
344 rc = virtio_device_setup_start(vdev, 0);
345 if (rc != EOK)
346 goto fail;
347
348 /* Perform device-specific setup */
349
350 /*
351 * Discover and configure the virtqueue
352 */
353 uint16_t num_queues = pio_read_le16(&cfg->num_queues);
354 if (num_queues != VIRTIO_BLK_NUM_QUEUES) {
355 ddf_msg(LVL_NOTE, "Unsupported number of virtqueues: %u",
356 num_queues);
357 rc = ELIMIT;
358 goto fail;
359 }
360
361 vdev->queues = calloc(sizeof(virtq_t), num_queues);
362 if (!vdev->queues) {
363 rc = ENOMEM;
364 goto fail;
365 }
366
367 /* For each in/out request we need 3 descriptors */
368 rc = virtio_virtq_setup(vdev, RQ_QUEUE, 3 * RQ_BUFFERS);
369 if (rc != EOK)
370 goto fail;
371
372 /*
373 * Setup DMA buffers
374 */
375 rc = virtio_setup_dma_bufs(RQ_BUFFERS, sizeof(virtio_blk_req_header_t),
376 true, virtio_blk->rq_header, virtio_blk->rq_header_p);
377 if (rc != EOK)
378 goto fail;
379 rc = virtio_setup_dma_bufs(RQ_BUFFERS, VIRTIO_BLK_BLOCK_SIZE,
380 true, virtio_blk->rq_buf, virtio_blk->rq_buf_p);
381 if (rc != EOK)
382 goto fail;
383 rc = virtio_setup_dma_bufs(RQ_BUFFERS, sizeof(virtio_blk_req_footer_t),
384 false, virtio_blk->rq_footer, virtio_blk->rq_footer_p);
385 if (rc != EOK)
386 goto fail;
387
388 /*
389 * Put all request descriptors on a free list. Because of the
390 * correspondence between the request, buffer and footer descriptors,
391 * we only need to manage allocations for one set: the request header
392 * descriptors.
393 */
394 virtio_create_desc_free_list(vdev, RQ_QUEUE, RQ_BUFFERS,
395 &virtio_blk->rq_free_head);
396
397 /*
398 * Enable IRQ
399 */
400 rc = hw_res_enable_interrupt(ddf_dev_parent_sess_get(dev),
401 virtio_blk->irq);
402 if (rc != EOK) {
403 ddf_msg(LVL_NOTE, "Failed to enable interrupt");
404 goto fail;
405 }
406
407 ddf_msg(LVL_NOTE, "Registered IRQ %d", virtio_blk->irq);
408
409 /* Go live */
410 virtio_device_setup_finalize(vdev);
411
412 return EOK;
413
414fail:
415 virtio_teardown_dma_bufs(virtio_blk->rq_header);
416 virtio_teardown_dma_bufs(virtio_blk->rq_buf);
417 virtio_teardown_dma_bufs(virtio_blk->rq_footer);
418
419 virtio_device_setup_fail(vdev);
420 virtio_pci_dev_cleanup(vdev);
421 return rc;
422}
423
424static void virtio_blk_uninitialize(ddf_dev_t *dev)
425{
426 virtio_blk_t *virtio_blk = (virtio_blk_t *) ddf_dev_data_get(dev);
427
428 virtio_teardown_dma_bufs(virtio_blk->rq_header);
429 virtio_teardown_dma_bufs(virtio_blk->rq_buf);
430 virtio_teardown_dma_bufs(virtio_blk->rq_footer);
431
432 virtio_device_setup_fail(&virtio_blk->virtio_dev);
433 virtio_pci_dev_cleanup(&virtio_blk->virtio_dev);
434}
435
436static void virtio_blk_bd_connection(ipc_call_t *icall, void *arg)
437{
438 virtio_blk_t *virtio_blk;
439 ddf_fun_t *fun = (ddf_fun_t *) arg;
440
441 virtio_blk = (virtio_blk_t *) ddf_dev_data_get(ddf_fun_get_dev(fun));
442 bd_conn(icall, &virtio_blk->bds);
443}
444
445static errno_t virtio_blk_dev_add(ddf_dev_t *dev)
446{
447 ddf_msg(LVL_NOTE, "%s %s (handle = %zu)", __func__,
448 ddf_dev_get_name(dev), ddf_dev_get_handle(dev));
449
450 errno_t rc = virtio_blk_initialize(dev);
451 if (rc != EOK)
452 return rc;
453
454 ddf_fun_t *fun = ddf_fun_create(dev, fun_exposed, "port0");
455 if (fun == NULL) {
456 rc = ENOMEM;
457 goto uninitialize;
458 }
459
460 ddf_fun_set_conn_handler(fun, virtio_blk_bd_connection);
461
462 rc = ddf_fun_bind(fun);
463 if (rc != EOK) {
464 ddf_msg(LVL_ERROR, "Failed binding device function");
465 goto destroy;
466 }
467
468 rc = ddf_fun_add_to_category(fun, "disk");
469 if (rc != EOK) {
470 ddf_msg(LVL_ERROR, "Failed adding function to category");
471 goto unbind;
472 }
473
474 ddf_msg(LVL_NOTE, "The %s device has been successfully initialized.",
475 ddf_dev_get_name(dev));
476
477 return EOK;
478
479unbind:
480 ddf_fun_unbind(fun);
481destroy:
482 ddf_fun_destroy(fun);
483uninitialize:
484 virtio_blk_uninitialize(dev);
485 return rc;
486}
487
488int main(void)
489{
490 printf("%s: HelenOS virtio-blk driver\n", NAME);
491
492 (void) ddf_log_init(NAME);
493 return ddf_driver_main(&virtio_blk_driver);
494}
Note: See TracBrowser for help on using the repository browser.