Implemented virtio-block
[akaros.git] / user / vmm / virtio_blk.c
1 #define _LARGEFILE64_SOURCE /* See feature_test_macros(7) */
2 #include <fcntl.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <unistd.h>
8 #include <vmm/virtio.h>
9 #include <vmm/virtio_blk.h>
10 #include <vmm/virtio_mmio.h>
11
12 int debug_virtio_blk;
13
14 #define DPRINTF(fmt, ...)                                                      \
15         do {                                                                       \
16         if (debug_virtio_blk) {                                                    \
17                 fprintf(stderr, "virtio_blk: " fmt, ##__VA_ARGS__);                    \
18         }                                                                          \
19         } while (0)
20
21 /* TODO(ganshun): multiple disks */
22 static int diskfd;
23
24 void blk_init_fn(struct virtio_vq_dev *vqdev, const char *filename)
25 {
26         struct virtio_blk_config *cfg = vqdev->cfg;
27         struct virtio_blk_config *cfg_d = vqdev->cfg_d;
28         uint64_t len;
29         struct stat stat_result;
30
31         diskfd = open(filename, O_RDWR);
32         if (diskfd < 0)
33                 VIRTIO_DEV_ERRX(vqdev, "Could not open disk image file %s", filename);
34
35         if (stat(filename, &stat_result) == -1)
36                 VIRTIO_DEV_ERRX(vqdev, "Could not stat file %s", filename);
37         len = stat_result.st_size / 512;
38
39         cfg->capacity = len;
40         cfg_d->capacity = len;
41 }
42
43 void blk_request(void *_vq)
44 {
45         struct virtio_vq *vq = _vq;
46
47         assert(vq != NULL);
48
49         struct virtio_mmio_dev *dev = vq->vqdev->transport_dev;
50         struct iovec *iov;
51         uint32_t head;
52         uint32_t olen, ilen;
53         struct virtio_blk_outhdr *out;
54         uint64_t offset;
55         int64_t ret;
56         size_t wlen;
57         uint8_t *status;
58         struct virtio_blk_config *cfg = vq->vqdev->cfg;
59
60         if (vq->qready != 0x1)
61                 VIRTIO_DEV_ERRX(vq->vqdev,
62                                 "The service function for queue '%s' was launched before the driver set QueueReady to 0x1.",
63                                  vq->name);
64
65         if (!dev->poke_guest)
66                 VIRTIO_DEV_ERRX(vq->vqdev,
67                                 "The 'poke_guest' function pointer was not set.");
68
69         iov = malloc(vq->qnum_max * sizeof(struct iovec));
70         if (iov == NULL)
71                 VIRTIO_DEV_ERRX(vq->vqdev,
72                                 "malloc returned null trying to allocate iov.\n");
73
74         for (;;) {
75                 head = virtio_next_avail_vq_desc(vq, iov, &olen, &ilen);
76                 /* There are always three iovecs.
77                  * The first is the header.
78                  * The second is the actual data.
79                  * The third contains just the status byte.
80                  */
81
82                 status = iov[2].iov_base;
83                 if (!status)
84                         VIRTIO_DEV_ERRX(vq->vqdev, "no room for status\n");
85
86                 out = iov[0].iov_base;
87                 if (out->type & VIRTIO_BLK_T_FLUSH)
88                         VIRTIO_DEV_ERRX(vq->vqdev, "Flush not supported.\n");
89
90                 offset = out->sector * 512;
91                 if (lseek64(diskfd, offset, SEEK_SET) != offset)
92                         VIRTIO_DEV_ERRX(vq->vqdev, "Bad seek at sector %llu\n",
93                                         out->sector);
94
95                 if (out->type & VIRTIO_BLK_T_OUT) {
96
97                         if ((offset + iov[1].iov_len) > (cfg->capacity * 512))
98                                 VIRTIO_DEV_ERRX(vq->vqdev, "write past end of file!\n");
99
100                         ret = writev(diskfd, &iov[1], 1);
101
102                         if (ret >= 0 && ret == iov[1].iov_len)
103                                 *status = VIRTIO_BLK_S_OK;
104                         else
105                                 *status = VIRTIO_BLK_S_IOERR;
106                         wlen = sizeof(*status);
107                 } else {
108                         ret = readv(diskfd, &iov[1], 1);
109                         if (ret >= 0) {
110                                 wlen = sizeof(*status) + ret;
111                                 *status = VIRTIO_BLK_S_OK;
112                         } else {
113                                 wlen = sizeof(*status);
114                                 *status = VIRTIO_BLK_S_IOERR;
115                         }
116
117                         // Hexdump for debugging.
118                         if (debug_virtio_blk && ret >= 0) {
119                                 char *pf = "";
120
121                                 for (int i = 0; i < iov[olen].iov_len; i += 2) {
122                                         uint8_t *p = (uint8_t *)iov[olen].iov_base + i;
123
124                                         fprintf(stderr, "%s%02x", pf, *(p + 1));
125                                         fprintf(stderr, "%02x", *p);
126                                         fprintf(stderr, " ");
127                                         pf = ((i + 2) % 16) ? " " : "\n";
128                                 }
129                         }
130                 }
131
132                 virtio_add_used_desc(vq, head, wlen);
133                 virtio_mmio_set_vring_irq(dev);
134                 dev->poke_guest(dev->vec);
135         }
136 }