root / hw / virtio-blk.c @ 9b32d5a5
History | View | Annotate | Download (6.9 kB)
1 | 6e02c38d | aliguori | /*
|
---|---|---|---|
2 | 6e02c38d | aliguori | * Virtio Block Device
|
3 | 6e02c38d | aliguori | *
|
4 | 6e02c38d | aliguori | * Copyright IBM, Corp. 2007
|
5 | 6e02c38d | aliguori | *
|
6 | 6e02c38d | aliguori | * Authors:
|
7 | 6e02c38d | aliguori | * Anthony Liguori <aliguori@us.ibm.com>
|
8 | 6e02c38d | aliguori | *
|
9 | 6e02c38d | aliguori | * This work is licensed under the terms of the GNU GPL, version 2. See
|
10 | 6e02c38d | aliguori | * the COPYING file in the top-level directory.
|
11 | 6e02c38d | aliguori | *
|
12 | 6e02c38d | aliguori | */
|
13 | 6e02c38d | aliguori | |
14 | 6e02c38d | aliguori | #include "virtio-blk.h" |
15 | 6e02c38d | aliguori | #include "block_int.h" |
16 | 6e02c38d | aliguori | |
17 | 6e02c38d | aliguori | typedef struct VirtIOBlock |
18 | 6e02c38d | aliguori | { |
19 | 6e02c38d | aliguori | VirtIODevice vdev; |
20 | 6e02c38d | aliguori | BlockDriverState *bs; |
21 | 6e02c38d | aliguori | VirtQueue *vq; |
22 | 6e02c38d | aliguori | } VirtIOBlock; |
23 | 6e02c38d | aliguori | |
24 | 6e02c38d | aliguori | static VirtIOBlock *to_virtio_blk(VirtIODevice *vdev)
|
25 | 6e02c38d | aliguori | { |
26 | 6e02c38d | aliguori | return (VirtIOBlock *)vdev;
|
27 | 6e02c38d | aliguori | } |
28 | 6e02c38d | aliguori | |
29 | 6e02c38d | aliguori | typedef struct VirtIOBlockReq |
30 | 6e02c38d | aliguori | { |
31 | 6e02c38d | aliguori | VirtIOBlock *dev; |
32 | 6e02c38d | aliguori | VirtQueueElement elem; |
33 | 6e02c38d | aliguori | struct virtio_blk_inhdr *in;
|
34 | 6e02c38d | aliguori | struct virtio_blk_outhdr *out;
|
35 | 6e02c38d | aliguori | size_t size; |
36 | 6e02c38d | aliguori | uint8_t *buffer; |
37 | 6e02c38d | aliguori | } VirtIOBlockReq; |
38 | 6e02c38d | aliguori | |
39 | 6e02c38d | aliguori | static void virtio_blk_rw_complete(void *opaque, int ret) |
40 | 6e02c38d | aliguori | { |
41 | 6e02c38d | aliguori | VirtIOBlockReq *req = opaque; |
42 | 6e02c38d | aliguori | VirtIOBlock *s = req->dev; |
43 | 6e02c38d | aliguori | |
44 | 6e02c38d | aliguori | /* Copy read data to the guest */
|
45 | 6e02c38d | aliguori | if (!ret && !(req->out->type & VIRTIO_BLK_T_OUT)) {
|
46 | 6e02c38d | aliguori | size_t offset = 0;
|
47 | 6e02c38d | aliguori | int i;
|
48 | 6e02c38d | aliguori | |
49 | 6e02c38d | aliguori | for (i = 0; i < req->elem.in_num - 1; i++) { |
50 | 6e02c38d | aliguori | size_t len; |
51 | 6e02c38d | aliguori | |
52 | 6e02c38d | aliguori | /* Be pretty defensive wrt malicious guests */
|
53 | 6e02c38d | aliguori | len = MIN(req->elem.in_sg[i].iov_len, |
54 | 6e02c38d | aliguori | req->size - offset); |
55 | 6e02c38d | aliguori | |
56 | 6e02c38d | aliguori | memcpy(req->elem.in_sg[i].iov_base, |
57 | 6e02c38d | aliguori | req->buffer + offset, |
58 | 6e02c38d | aliguori | len); |
59 | 6e02c38d | aliguori | offset += len; |
60 | 6e02c38d | aliguori | } |
61 | 6e02c38d | aliguori | } |
62 | 6e02c38d | aliguori | |
63 | 6e02c38d | aliguori | req->in->status = ret ? VIRTIO_BLK_S_IOERR : VIRTIO_BLK_S_OK; |
64 | 6e02c38d | aliguori | virtqueue_push(s->vq, &req->elem, req->size + sizeof(*req->in));
|
65 | 6e02c38d | aliguori | virtio_notify(&s->vdev, s->vq); |
66 | 6e02c38d | aliguori | |
67 | 6e02c38d | aliguori | qemu_free(req->buffer); |
68 | 6e02c38d | aliguori | qemu_free(req); |
69 | 6e02c38d | aliguori | } |
70 | 6e02c38d | aliguori | |
71 | 6e02c38d | aliguori | static VirtIOBlockReq *virtio_blk_get_request(VirtIOBlock *s)
|
72 | 6e02c38d | aliguori | { |
73 | 6e02c38d | aliguori | VirtIOBlockReq *req; |
74 | 6e02c38d | aliguori | |
75 | 6e02c38d | aliguori | req = qemu_mallocz(sizeof(*req));
|
76 | 6e02c38d | aliguori | if (req == NULL) |
77 | 6e02c38d | aliguori | return NULL; |
78 | 6e02c38d | aliguori | |
79 | 6e02c38d | aliguori | req->dev = s; |
80 | 6e02c38d | aliguori | if (!virtqueue_pop(s->vq, &req->elem)) {
|
81 | 6e02c38d | aliguori | qemu_free(req); |
82 | 6e02c38d | aliguori | return NULL; |
83 | 6e02c38d | aliguori | } |
84 | 6e02c38d | aliguori | |
85 | 6e02c38d | aliguori | return req;
|
86 | 6e02c38d | aliguori | } |
87 | 6e02c38d | aliguori | |
88 | 6e02c38d | aliguori | static void virtio_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq) |
89 | 6e02c38d | aliguori | { |
90 | 6e02c38d | aliguori | VirtIOBlock *s = to_virtio_blk(vdev); |
91 | 6e02c38d | aliguori | VirtIOBlockReq *req; |
92 | 6e02c38d | aliguori | |
93 | 6e02c38d | aliguori | while ((req = virtio_blk_get_request(s))) {
|
94 | 6e02c38d | aliguori | int i;
|
95 | 6e02c38d | aliguori | |
96 | 6e02c38d | aliguori | if (req->elem.out_num < 1 || req->elem.in_num < 1) { |
97 | 6e02c38d | aliguori | fprintf(stderr, "virtio-blk missing headers\n");
|
98 | 6e02c38d | aliguori | exit(1);
|
99 | 6e02c38d | aliguori | } |
100 | 6e02c38d | aliguori | |
101 | 6e02c38d | aliguori | if (req->elem.out_sg[0].iov_len < sizeof(*req->out) || |
102 | 6e02c38d | aliguori | req->elem.in_sg[req->elem.in_num - 1].iov_len < sizeof(*req->in)) { |
103 | 6e02c38d | aliguori | fprintf(stderr, "virtio-blk header not in correct element\n");
|
104 | 6e02c38d | aliguori | exit(1);
|
105 | 6e02c38d | aliguori | } |
106 | 6e02c38d | aliguori | |
107 | 6e02c38d | aliguori | req->out = (void *)req->elem.out_sg[0].iov_base; |
108 | 6e02c38d | aliguori | req->in = (void *)req->elem.in_sg[req->elem.in_num - 1].iov_base; |
109 | 6e02c38d | aliguori | |
110 | 6e02c38d | aliguori | if (req->out->type & VIRTIO_BLK_T_SCSI_CMD) {
|
111 | 6e02c38d | aliguori | unsigned int len = sizeof(*req->in); |
112 | 6e02c38d | aliguori | |
113 | 6e02c38d | aliguori | req->in->status = VIRTIO_BLK_S_UNSUPP; |
114 | 6e02c38d | aliguori | virtqueue_push(vq, &req->elem, len); |
115 | 6e02c38d | aliguori | virtio_notify(vdev, vq); |
116 | 6e02c38d | aliguori | qemu_free(req); |
117 | 6e02c38d | aliguori | } else if (req->out->type & VIRTIO_BLK_T_OUT) { |
118 | 6e02c38d | aliguori | size_t offset; |
119 | 6e02c38d | aliguori | |
120 | 6e02c38d | aliguori | for (i = 1; i < req->elem.out_num; i++) |
121 | 6e02c38d | aliguori | req->size += req->elem.out_sg[i].iov_len; |
122 | 6e02c38d | aliguori | |
123 | 6e02c38d | aliguori | req->buffer = qemu_memalign(512, req->size);
|
124 | 6e02c38d | aliguori | if (req->buffer == NULL) { |
125 | 6e02c38d | aliguori | qemu_free(req); |
126 | 6e02c38d | aliguori | break;
|
127 | 6e02c38d | aliguori | } |
128 | 6e02c38d | aliguori | |
129 | 6e02c38d | aliguori | /* We copy the data from the SG list to avoid splitting up the request. This helps
|
130 | 6e02c38d | aliguori | performance a lot until we can pass full sg lists as AIO operations */
|
131 | 6e02c38d | aliguori | offset = 0;
|
132 | 6e02c38d | aliguori | for (i = 1; i < req->elem.out_num; i++) { |
133 | 6e02c38d | aliguori | size_t len; |
134 | 6e02c38d | aliguori | |
135 | 6e02c38d | aliguori | len = MIN(req->elem.out_sg[i].iov_len, |
136 | 6e02c38d | aliguori | req->size - offset); |
137 | 6e02c38d | aliguori | memcpy(req->buffer + offset, |
138 | 6e02c38d | aliguori | req->elem.out_sg[i].iov_base, |
139 | 6e02c38d | aliguori | len); |
140 | 6e02c38d | aliguori | offset += len; |
141 | 6e02c38d | aliguori | } |
142 | 6e02c38d | aliguori | |
143 | 6e02c38d | aliguori | bdrv_aio_write(s->bs, req->out->sector, |
144 | 6e02c38d | aliguori | req->buffer, |
145 | 6e02c38d | aliguori | req->size / 512,
|
146 | 6e02c38d | aliguori | virtio_blk_rw_complete, |
147 | 6e02c38d | aliguori | req); |
148 | 6e02c38d | aliguori | } else {
|
149 | 6e02c38d | aliguori | for (i = 0; i < req->elem.in_num - 1; i++) |
150 | 6e02c38d | aliguori | req->size += req->elem.in_sg[i].iov_len; |
151 | 6e02c38d | aliguori | |
152 | 6e02c38d | aliguori | req->buffer = qemu_memalign(512, req->size);
|
153 | 6e02c38d | aliguori | if (req->buffer == NULL) { |
154 | 6e02c38d | aliguori | qemu_free(req); |
155 | 6e02c38d | aliguori | break;
|
156 | 6e02c38d | aliguori | } |
157 | 6e02c38d | aliguori | |
158 | 6e02c38d | aliguori | bdrv_aio_read(s->bs, req->out->sector, |
159 | 6e02c38d | aliguori | req->buffer, |
160 | 6e02c38d | aliguori | req->size / 512,
|
161 | 6e02c38d | aliguori | virtio_blk_rw_complete, |
162 | 6e02c38d | aliguori | req); |
163 | 6e02c38d | aliguori | } |
164 | 6e02c38d | aliguori | } |
165 | 6e02c38d | aliguori | /*
|
166 | 6e02c38d | aliguori | * FIXME: Want to check for completions before returning to guest mode,
|
167 | 6e02c38d | aliguori | * so cached reads and writes are reported as quickly as possible. But
|
168 | 6e02c38d | aliguori | * that should be done in the generic block layer.
|
169 | 6e02c38d | aliguori | */
|
170 | 6e02c38d | aliguori | } |
171 | 6e02c38d | aliguori | |
172 | 6e02c38d | aliguori | static void virtio_blk_reset(VirtIODevice *vdev) |
173 | 6e02c38d | aliguori | { |
174 | 6e02c38d | aliguori | /*
|
175 | 6e02c38d | aliguori | * This should cancel pending requests, but can't do nicely until there
|
176 | 6e02c38d | aliguori | * are per-device request lists.
|
177 | 6e02c38d | aliguori | */
|
178 | 6e02c38d | aliguori | qemu_aio_flush(); |
179 | 6e02c38d | aliguori | } |
180 | 6e02c38d | aliguori | |
181 | 6e02c38d | aliguori | static void virtio_blk_update_config(VirtIODevice *vdev, uint8_t *config) |
182 | 6e02c38d | aliguori | { |
183 | 6e02c38d | aliguori | VirtIOBlock *s = to_virtio_blk(vdev); |
184 | 6e02c38d | aliguori | struct virtio_blk_config blkcfg;
|
185 | 6e02c38d | aliguori | uint64_t capacity; |
186 | 6e02c38d | aliguori | int cylinders, heads, secs;
|
187 | 6e02c38d | aliguori | |
188 | 6e02c38d | aliguori | bdrv_get_geometry(s->bs, &capacity); |
189 | 6e02c38d | aliguori | bdrv_get_geometry_hint(s->bs, &cylinders, &heads, &secs); |
190 | 6e02c38d | aliguori | stq_raw(&blkcfg.capacity, capacity); |
191 | 6e02c38d | aliguori | stl_raw(&blkcfg.seg_max, 128 - 2); |
192 | 6e02c38d | aliguori | stw_raw(&blkcfg.cylinders, cylinders); |
193 | 6e02c38d | aliguori | blkcfg.heads = heads; |
194 | 6e02c38d | aliguori | blkcfg.sectors = secs; |
195 | 6e02c38d | aliguori | memcpy(config, &blkcfg, sizeof(blkcfg));
|
196 | 6e02c38d | aliguori | } |
197 | 6e02c38d | aliguori | |
198 | 6e02c38d | aliguori | static uint32_t virtio_blk_get_features(VirtIODevice *vdev)
|
199 | 6e02c38d | aliguori | { |
200 | 6e02c38d | aliguori | return (1 << VIRTIO_BLK_F_SEG_MAX | 1 << VIRTIO_BLK_F_GEOMETRY); |
201 | 6e02c38d | aliguori | } |
202 | 6e02c38d | aliguori | |
203 | 6e02c38d | aliguori | static void virtio_blk_save(QEMUFile *f, void *opaque) |
204 | 6e02c38d | aliguori | { |
205 | 6e02c38d | aliguori | VirtIOBlock *s = opaque; |
206 | 6e02c38d | aliguori | virtio_save(&s->vdev, f); |
207 | 6e02c38d | aliguori | } |
208 | 6e02c38d | aliguori | |
209 | 6e02c38d | aliguori | static int virtio_blk_load(QEMUFile *f, void *opaque, int version_id) |
210 | 6e02c38d | aliguori | { |
211 | 6e02c38d | aliguori | VirtIOBlock *s = opaque; |
212 | 6e02c38d | aliguori | |
213 | 6e02c38d | aliguori | if (version_id != 1) |
214 | 6e02c38d | aliguori | return -EINVAL;
|
215 | 6e02c38d | aliguori | |
216 | 6e02c38d | aliguori | virtio_load(&s->vdev, f); |
217 | 6e02c38d | aliguori | |
218 | 6e02c38d | aliguori | return 0; |
219 | 6e02c38d | aliguori | } |
220 | 6e02c38d | aliguori | |
221 | 9b32d5a5 | aliguori | void *virtio_blk_init(PCIBus *bus, BlockDriverState *bs)
|
222 | 6e02c38d | aliguori | { |
223 | 6e02c38d | aliguori | VirtIOBlock *s; |
224 | 6e02c38d | aliguori | int cylinders, heads, secs;
|
225 | 6e02c38d | aliguori | static int virtio_blk_id; |
226 | 6e02c38d | aliguori | |
227 | 9b32d5a5 | aliguori | s = (VirtIOBlock *)virtio_init_pci(bus, "virtio-blk",
|
228 | 9b32d5a5 | aliguori | PCI_VENDOR_ID_REDHAT_QUMRANET, |
229 | 9b32d5a5 | aliguori | PCI_DEVICE_ID_VIRTIO_BLOCK, |
230 | 6e02c38d | aliguori | 0, VIRTIO_ID_BLOCK,
|
231 | 6e02c38d | aliguori | 0x01, 0x80, 0x00, |
232 | 6e02c38d | aliguori | sizeof(struct virtio_blk_config), sizeof(VirtIOBlock)); |
233 | 6e02c38d | aliguori | if (!s)
|
234 | 6e02c38d | aliguori | return NULL; |
235 | 6e02c38d | aliguori | |
236 | 6e02c38d | aliguori | s->vdev.get_config = virtio_blk_update_config; |
237 | 6e02c38d | aliguori | s->vdev.get_features = virtio_blk_get_features; |
238 | 6e02c38d | aliguori | s->vdev.reset = virtio_blk_reset; |
239 | 6e02c38d | aliguori | s->bs = bs; |
240 | 6e02c38d | aliguori | bdrv_guess_geometry(s->bs, &cylinders, &heads, &secs); |
241 | 6e02c38d | aliguori | bdrv_set_geometry_hint(s->bs, cylinders, heads, secs); |
242 | 6e02c38d | aliguori | |
243 | 6e02c38d | aliguori | s->vq = virtio_add_queue(&s->vdev, 128, virtio_blk_handle_output);
|
244 | 6e02c38d | aliguori | |
245 | 6e02c38d | aliguori | register_savevm("virtio-blk", virtio_blk_id++, 1, |
246 | 6e02c38d | aliguori | virtio_blk_save, virtio_blk_load, s); |
247 | 6e02c38d | aliguori | |
248 | 6e02c38d | aliguori | return s;
|
249 | 6e02c38d | aliguori | } |