xref: /haiku/src/add-ons/kernel/drivers/disk/virtual/virtio_block/virtio_block.cpp (revision 445d4fd926c569e7b9ae28017da86280aaecbae2)
1 /*
2  * Copyright 2013, Jérôme Duval, korli@users.berlios.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <virtio.h>
8 
9 #include "virtio_blk.h"
10 
11 
12 class DMAResource;
13 class IOScheduler;
14 
15 
16 static const uint8 kDriveIcon[] = {
17 	0x6e, 0x63, 0x69, 0x66, 0x08, 0x03, 0x01, 0x00, 0x00, 0x02, 0x00, 0x16,
18 	0x02, 0x3c, 0xc7, 0xee, 0x38, 0x9b, 0xc0, 0xba, 0x16, 0x57, 0x3e, 0x39,
19 	0xb0, 0x49, 0x77, 0xc8, 0x42, 0xad, 0xc7, 0x00, 0xff, 0xff, 0xd3, 0x02,
20 	0x00, 0x06, 0x02, 0x3c, 0x96, 0x32, 0x3a, 0x4d, 0x3f, 0xba, 0xfc, 0x01,
21 	0x3d, 0x5a, 0x97, 0x4b, 0x57, 0xa5, 0x49, 0x84, 0x4d, 0x00, 0x47, 0x47,
22 	0x47, 0xff, 0xa5, 0xa0, 0xa0, 0x02, 0x00, 0x16, 0x02, 0xbc, 0x59, 0x2f,
23 	0xbb, 0x29, 0xa7, 0x3c, 0x0c, 0xe4, 0xbd, 0x0b, 0x7c, 0x48, 0x92, 0xc0,
24 	0x4b, 0x79, 0x66, 0x00, 0x7d, 0xff, 0xd4, 0x02, 0x00, 0x06, 0x02, 0x38,
25 	0xdb, 0xb4, 0x39, 0x97, 0x33, 0xbc, 0x4a, 0x33, 0x3b, 0xa5, 0x42, 0x48,
26 	0x6e, 0x66, 0x49, 0xee, 0x7b, 0x00, 0x59, 0x67, 0x56, 0xff, 0xeb, 0xb2,
27 	0xb2, 0x03, 0xa7, 0xff, 0x00, 0x03, 0xff, 0x00, 0x00, 0x04, 0x01, 0x80,
28 	0x07, 0x0a, 0x06, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x5a, 0x3e, 0x5a,
29 	0x31, 0x39, 0x25, 0x0a, 0x04, 0x22, 0x3c, 0x44, 0x4b, 0x5a, 0x31, 0x39,
30 	0x25, 0x0a, 0x04, 0x44, 0x4b, 0x44, 0x5b, 0x5a, 0x3e, 0x5a, 0x31, 0x0a,
31 	0x04, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x44, 0x4b, 0x08, 0x02, 0x27,
32 	0x43, 0xb8, 0x14, 0xc1, 0xf1, 0x08, 0x02, 0x26, 0x43, 0x29, 0x44, 0x0a,
33 	0x05, 0x44, 0x5d, 0x49, 0x5d, 0x60, 0x3e, 0x5a, 0x3b, 0x5b, 0x3f, 0x08,
34 	0x0a, 0x07, 0x01, 0x06, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x10, 0x01, 0x17,
35 	0x84, 0x00, 0x04, 0x0a, 0x01, 0x01, 0x01, 0x00, 0x0a, 0x02, 0x01, 0x02,
36 	0x00, 0x0a, 0x03, 0x01, 0x03, 0x00, 0x0a, 0x04, 0x01, 0x04, 0x10, 0x01,
37 	0x17, 0x85, 0x20, 0x04, 0x0a, 0x06, 0x01, 0x05, 0x30, 0x24, 0xb3, 0x99,
38 	0x01, 0x17, 0x82, 0x00, 0x04, 0x0a, 0x05, 0x01, 0x05, 0x30, 0x20, 0xb2,
39 	0xe6, 0x01, 0x17, 0x82, 0x00, 0x04
40 };
41 
42 
43 #define VIRTIO_BLOCK_DRIVER_MODULE_NAME "drivers/disk/virtual/virtio_block/driver_v1"
44 #define VIRTIO_BLOCK_DEVICE_MODULE_NAME "drivers/disk/virtual/virtio_block/device_v1"
45 #define VIRTIO_BLOCK_DEVICE_ID_GENERATOR	"virtio_block/device_id"
46 
47 
48 typedef struct {
49 	device_node*			node;
50 	::virtio_device			virtio_device;
51 	virtio_device_interface*	virtio;
52 	::virtio_queue			virtio_queue;
53 	IOScheduler*			io_scheduler;
54 	DMAResource*			dma_resource;
55 
56 	struct virtio_blk_config	config;
57 
58 	uint32 					features;
59 	uint64					capacity;
60 	uint32					block_size;
61 	uint32					physical_block_size;
62 	status_t				media_status;
63 
64 	sem_id 	sem_cb;
65 } virtio_block_driver_info;
66 
67 
68 typedef struct {
69 	virtio_block_driver_info*		info;
70 } virtio_block_handle;
71 
72 
73 #include <stdio.h>
74 #include <string.h>
75 #include <stdlib.h>
76 
77 #include <fs/devfs.h>
78 
79 #include "dma_resources.h"
80 #include "IORequest.h"
81 #include "IOSchedulerSimple.h"
82 
83 
84 //#define TRACE_VIRTIO_BLOCK
85 #ifdef TRACE_VIRTIO_BLOCK
86 #	define TRACE(x...) dprintf("virtio_block: " x)
87 #else
88 #	define TRACE(x...) ;
89 #endif
90 #define ERROR(x...)			dprintf("\33[33mvirtio_block:\33[0m " x)
91 #define CALLED() 			TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
92 
93 
94 static device_manager_info* sDeviceManager;
95 
96 
97 bool virtio_block_set_capacity(virtio_block_driver_info* info);
98 
99 
100 const char *
101 get_feature_name(uint32 feature)
102 {
103 	switch (feature) {
104 		case VIRTIO_BLK_F_BARRIER:
105 			return "host barrier";
106 		case VIRTIO_BLK_F_SIZE_MAX:
107 			return "maximum segment size";
108 		case VIRTIO_BLK_F_SEG_MAX:
109 			return "maximum segment count";
110 		case VIRTIO_BLK_F_GEOMETRY:
111 			return "disk geometry";
112 		case VIRTIO_BLK_F_RO:
113 			return "read only";
114 		case VIRTIO_BLK_F_BLK_SIZE:
115 			return "block size";
116 		case VIRTIO_BLK_F_SCSI:
117 			return "scsi commands";
118 		case VIRTIO_BLK_F_FLUSH:
119 			return "flush command";
120 		case VIRTIO_BLK_F_TOPOLOGY:
121 			return "topology";
122 		case VIRTIO_BLK_F_CONFIG_WCE:
123 			return "config wce";
124 	}
125 	return NULL;
126 }
127 
128 
129 static status_t
130 get_geometry(virtio_block_handle* handle, device_geometry* geometry)
131 {
132 	virtio_block_driver_info* info = handle->info;
133 
134 	devfs_compute_geometry_size(geometry, info->capacity, info->block_size);
135 	geometry->bytes_per_physical_sector = info->physical_block_size;
136 
137 	geometry->device_type = B_DISK;
138 	geometry->removable = false;
139 
140 	geometry->read_only = ((info->features & VIRTIO_BLK_F_RO) != 0);
141 	geometry->write_once = false;
142 
143 	TRACE("virtio_block: get_geometry(): %" B_PRIu32 ", %" B_PRIu32 ", %" B_PRIu32 ", %" B_PRIu32
144 		", %d, %d, %d, %d\n", geometry->bytes_per_sector, geometry->sectors_per_track,
145 		geometry->cylinder_count, geometry->head_count, geometry->device_type,
146 		geometry->removable, geometry->read_only, geometry->write_once);
147 
148 	return B_OK;
149 }
150 
151 
152 static void
153 virtio_block_config_callback(void* driverCookie)
154 {
155 	virtio_block_driver_info* info = (virtio_block_driver_info*)driverCookie;
156 
157 	status_t status = info->virtio->read_device_config(info->virtio_device, 0,
158 		&info->config, sizeof(struct virtio_blk_config));
159 	if (status != B_OK)
160 		return;
161 
162 	if (virtio_block_set_capacity(info))
163 		info->media_status = B_DEV_MEDIA_CHANGED;
164 }
165 
166 
167 static void
168 virtio_block_callback(void* driverCookie, void* cookie)
169 {
170 	virtio_block_driver_info* info = (virtio_block_driver_info*)cookie;
171 
172 	// consume all queued elements
173 	while (info->virtio->queue_dequeue(info->virtio_queue, NULL, NULL))
174 		;
175 
176 	release_sem_etc(info->sem_cb, 1, B_DO_NOT_RESCHEDULE);
177 }
178 
179 
180 static status_t
181 do_io(void* cookie, IOOperation* operation)
182 {
183 	virtio_block_driver_info* info = (virtio_block_driver_info*)cookie;
184 
185 	size_t bytesTransferred = 0;
186 	status_t status = B_OK;
187 
188 	physical_entry entries[operation->VecCount() + 2];
189 
190 	void *buffer = malloc(sizeof(struct virtio_blk_outhdr) + sizeof(uint8));
191 	struct virtio_blk_outhdr *header = (struct virtio_blk_outhdr*)buffer;
192 	header->type = operation->IsWrite() ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
193 	header->sector = operation->Offset() / 512;
194 	header->ioprio = 1;
195 
196 	uint8* ack = (uint8*)buffer + sizeof(struct virtio_blk_outhdr);
197 	*ack = 0xff;
198 
199 	get_memory_map(buffer, sizeof(struct virtio_blk_outhdr) + sizeof(uint8),
200 		&entries[0], 1);
201 	entries[operation->VecCount() + 1].address = entries[0].address
202 		+ sizeof(struct virtio_blk_outhdr);
203 	entries[operation->VecCount() + 1].size = sizeof(uint8);
204 	entries[0].size = sizeof(struct virtio_blk_outhdr);
205 
206 	memcpy(entries + 1, operation->Vecs(), operation->VecCount()
207 		* sizeof(physical_entry));
208 
209 	info->virtio->queue_request_v(info->virtio_queue, entries,
210 		1 + (operation->IsWrite() ? operation->VecCount() : 0 ),
211 		1 + (operation->IsWrite() ? 0 : operation->VecCount()),
212 		info);
213 
214 	acquire_sem(info->sem_cb);
215 
216 	switch (*ack) {
217 		case VIRTIO_BLK_S_OK:
218 			status = B_OK;
219 			bytesTransferred = operation->Length();
220 			break;
221 		case VIRTIO_BLK_S_UNSUPP:
222 			status = ENOTSUP;
223 			break;
224 		default:
225 			status = EIO;
226 			break;
227 	}
228 	free(buffer);
229 
230 	info->io_scheduler->OperationCompleted(operation, status,
231 		bytesTransferred);
232 	return status;
233 }
234 
235 
236 //	#pragma mark - device module API
237 
238 
239 static status_t
240 virtio_block_init_device(void* _info, void** _cookie)
241 {
242 	CALLED();
243 	virtio_block_driver_info* info = (virtio_block_driver_info*)_info;
244 
245 	device_node* parent = sDeviceManager->get_parent_node(info->node);
246 	sDeviceManager->get_driver(parent, (driver_module_info **)&info->virtio,
247 		(void **)&info->virtio_device);
248 	sDeviceManager->put_node(parent);
249 
250 	info->virtio->negotiate_features(info->virtio_device,
251 		VIRTIO_BLK_F_BARRIER | VIRTIO_BLK_F_SIZE_MAX
252 			| VIRTIO_BLK_F_SEG_MAX | VIRTIO_BLK_F_GEOMETRY
253 			| VIRTIO_BLK_F_RO | VIRTIO_BLK_F_BLK_SIZE
254 			| VIRTIO_BLK_F_FLUSH | VIRTIO_BLK_F_TOPOLOGY
255 			| VIRTIO_FEATURE_RING_INDIRECT_DESC,
256 		&info->features, &get_feature_name);
257 
258 	status_t status = info->virtio->read_device_config(
259 		info->virtio_device, 0, &info->config,
260 		sizeof(struct virtio_blk_config));
261 	if (status != B_OK)
262 		return status;
263 
264 	virtio_block_set_capacity(info);
265 
266 	TRACE("virtio_block: capacity: %" B_PRIu64 ", block_size %" B_PRIu32 "\n",
267 		info->capacity, info->block_size);
268 
269 	status = info->virtio->alloc_queues(info->virtio_device, 1,
270 		&info->virtio_queue);
271 	if (status != B_OK) {
272 		ERROR("queue allocation failed (%s)\n", strerror(status));
273 		return status;
274 	}
275 	status = info->virtio->setup_interrupt(info->virtio_device,
276 		virtio_block_config_callback, info);
277 
278 	if (status == B_OK) {
279 		status = info->virtio->queue_setup_interrupt(info->virtio_queue,
280 			virtio_block_callback, info);
281 	}
282 
283 	*_cookie = info;
284 	return status;
285 }
286 
287 
288 static void
289 virtio_block_uninit_device(void* _cookie)
290 {
291 	CALLED();
292 	virtio_block_driver_info* info = (virtio_block_driver_info*)_cookie;
293 
294 	delete info->io_scheduler;
295 	delete info->dma_resource;
296 }
297 
298 
299 static status_t
300 virtio_block_open(void* _info, const char* path, int openMode, void** _cookie)
301 {
302 	CALLED();
303 	virtio_block_driver_info* info = (virtio_block_driver_info*)_info;
304 
305 	virtio_block_handle* handle = (virtio_block_handle*)malloc(
306 		sizeof(virtio_block_handle));
307 	if (handle == NULL)
308 		return B_NO_MEMORY;
309 
310 	handle->info = info;
311 
312 	*_cookie = handle;
313 	return B_OK;
314 }
315 
316 
317 static status_t
318 virtio_block_close(void* cookie)
319 {
320 	//virtio_block_handle* handle = (virtio_block_handle*)cookie;
321 	CALLED();
322 
323 	return B_OK;
324 }
325 
326 
327 static status_t
328 virtio_block_free(void* cookie)
329 {
330 	CALLED();
331 	virtio_block_handle* handle = (virtio_block_handle*)cookie;
332 
333 	free(handle);
334 	return B_OK;
335 }
336 
337 
338 static status_t
339 virtio_block_io(void *cookie, io_request *request)
340 {
341 	CALLED();
342 	virtio_block_handle* handle = (virtio_block_handle*)cookie;
343 
344 	return handle->info->io_scheduler->ScheduleRequest(request);
345 }
346 
347 
348 static status_t
349 virtio_block_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
350 {
351 	CALLED();
352 	virtio_block_handle* handle = (virtio_block_handle*)cookie;
353 	virtio_block_driver_info* info = handle->info;
354 
355 	TRACE("ioctl(op = %" B_PRIu32 ")\n", op);
356 
357 	switch (op) {
358 		case B_GET_MEDIA_STATUS:
359 		{
360 			*(status_t *)buffer = info->media_status;
361 			info->media_status = B_OK;
362 			TRACE("B_GET_MEDIA_STATUS: 0x%08" B_PRIx32 "\n", *(status_t *)buffer);
363 			return B_OK;
364 			break;
365 		}
366 
367 		case B_GET_DEVICE_SIZE:
368 		{
369 			size_t size = info->capacity * info->block_size;
370 			return user_memcpy(buffer, &size, sizeof(size_t));
371 		}
372 
373 		case B_GET_GEOMETRY:
374 		{
375 			if (buffer == NULL || length > sizeof(device_geometry))
376 				return B_BAD_VALUE;
377 
378 		 	device_geometry geometry;
379 			status_t status = get_geometry(handle, &geometry);
380 			if (status != B_OK)
381 				return status;
382 
383 			return user_memcpy(buffer, &geometry, length);
384 		}
385 
386 		case B_GET_ICON_NAME:
387 			return user_strlcpy((char*)buffer, "devices/drive-harddisk",
388 				B_FILE_NAME_LENGTH);
389 
390 		case B_GET_VECTOR_ICON:
391 		{
392 			// TODO: take device type into account!
393 			device_icon iconData;
394 			if (length != sizeof(device_icon))
395 				return B_BAD_VALUE;
396 			if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK)
397 				return B_BAD_ADDRESS;
398 
399 			if (iconData.icon_size >= (int32)sizeof(kDriveIcon)) {
400 				if (user_memcpy(iconData.icon_data, kDriveIcon,
401 						sizeof(kDriveIcon)) != B_OK)
402 					return B_BAD_ADDRESS;
403 			}
404 
405 			iconData.icon_size = sizeof(kDriveIcon);
406 			return user_memcpy(buffer, &iconData, sizeof(device_icon));
407 		}
408 
409 		/*case B_FLUSH_DRIVE_CACHE:
410 			return synchronize_cache(info);*/
411 	}
412 
413 	return B_DEV_INVALID_IOCTL;
414 }
415 
416 
417 bool
418 virtio_block_set_capacity(virtio_block_driver_info* info)
419 {
420 	// get capacity
421 	uint32 blockSize = 512;
422 	if ((info->features & VIRTIO_BLK_F_BLK_SIZE) != 0)
423 		blockSize = info->config.blk_size;
424 	uint64 capacity = info->config.capacity * 512 / blockSize;
425 	uint32 physicalBlockSize = blockSize;
426 
427 	if ((info->features & VIRTIO_BLK_F_TOPOLOGY) != 0
428 		&& info->config.topology.physical_block_exp > 0) {
429 		physicalBlockSize = blockSize * (1 << info->config.topology.physical_block_exp);
430 	}
431 
432 	TRACE("set_capacity(device = %p, capacity = %" B_PRIu64 ", blockSize = %" B_PRIu32 ")\n",
433 		info, capacity, blockSize);
434 
435 	if (info->block_size == blockSize && info->capacity == capacity)
436 		return false;
437 
438 	info->capacity = capacity;
439 
440 	if (info->block_size != 0) {
441 		ERROR("old %" B_PRId32 ", new %" B_PRId32 "\n", info->block_size,
442 			blockSize);
443 		panic("updating DMAResource not yet implemented...");
444 	}
445 
446 	dma_restrictions restrictions;
447 	memset(&restrictions, 0, sizeof(restrictions));
448 	if ((info->features & VIRTIO_BLK_F_SIZE_MAX) != 0)
449 		restrictions.max_segment_size = info->config.size_max;
450 	if ((info->features & VIRTIO_BLK_F_SEG_MAX) != 0)
451 		restrictions.max_segment_count = info->config.seg_max;
452 
453 	// TODO: we need to replace the DMAResource in our IOScheduler
454 	status_t status = info->dma_resource->Init(restrictions, blockSize,
455 		1024, 32);
456 	if (status != B_OK)
457 		panic("initializing DMAResource failed: %s", strerror(status));
458 
459 	info->io_scheduler = new(std::nothrow) IOSchedulerSimple(
460 		info->dma_resource);
461 	if (info->io_scheduler == NULL)
462 		panic("allocating IOScheduler failed.");
463 
464 	// TODO: use whole device name here
465 	status = info->io_scheduler->Init("virtio");
466 	if (status != B_OK)
467 		panic("initializing IOScheduler failed: %s", strerror(status));
468 
469 	info->io_scheduler->SetCallback(do_io, info);
470 
471 	info->block_size = blockSize;
472 	info->physical_block_size = physicalBlockSize;
473 	return true;
474 }
475 
476 
477 //	#pragma mark - driver module API
478 
479 
480 static float
481 virtio_block_supports_device(device_node *parent)
482 {
483 	CALLED();
484 	const char *bus;
485 	uint16 deviceType;
486 
487 	// make sure parent is really the Virtio bus manager
488 	if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
489 		return -1;
490 
491 	if (strcmp(bus, "virtio"))
492 		return 0.0;
493 
494 	// check whether it's really a Direct Access Device
495 	if (sDeviceManager->get_attr_uint16(parent, VIRTIO_DEVICE_TYPE_ITEM,
496 			&deviceType, true) != B_OK || deviceType != VIRTIO_DEVICE_ID_BLOCK)
497 		return 0.0;
498 
499 	TRACE("Virtio block device found!\n");
500 
501 	return 0.6;
502 }
503 
504 
505 static status_t
506 virtio_block_register_device(device_node *node)
507 {
508 	CALLED();
509 
510 	device_attr attrs[] = {
511 		{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "Virtio Block"} },
512 		{ NULL }
513 	};
514 
515 	return sDeviceManager->register_node(node, VIRTIO_BLOCK_DRIVER_MODULE_NAME,
516 		attrs, NULL, NULL);
517 }
518 
519 
520 static status_t
521 virtio_block_init_driver(device_node *node, void **cookie)
522 {
523 	CALLED();
524 
525 	virtio_block_driver_info* info = (virtio_block_driver_info*)malloc(
526 		sizeof(virtio_block_driver_info));
527 	if (info == NULL)
528 		return B_NO_MEMORY;
529 
530 	memset(info, 0, sizeof(*info));
531 
532 	info->media_status = B_OK;
533 	info->dma_resource = new(std::nothrow) DMAResource;
534 	if (info->dma_resource == NULL) {
535 		free(info);
536 		return B_NO_MEMORY;
537 	}
538 
539 	info->sem_cb = create_sem(0, "virtio_block_cb");
540 	if (info->sem_cb < 0) {
541 		delete info->dma_resource;
542 		status_t status = info->sem_cb;
543 		free(info);
544 		return status;
545 	}
546 	info->node = node;
547 
548 	*cookie = info;
549 	return B_OK;
550 }
551 
552 
553 static void
554 virtio_block_uninit_driver(void *_cookie)
555 {
556 	CALLED();
557 	virtio_block_driver_info* info = (virtio_block_driver_info*)_cookie;
558 	delete_sem(info->sem_cb);
559 	free(info);
560 }
561 
562 
563 static status_t
564 virtio_block_register_child_devices(void* _cookie)
565 {
566 	CALLED();
567 	virtio_block_driver_info* info = (virtio_block_driver_info*)_cookie;
568 	status_t status;
569 
570 	int32 id = sDeviceManager->create_id(VIRTIO_BLOCK_DEVICE_ID_GENERATOR);
571 	if (id < 0)
572 		return id;
573 
574 	char name[64];
575 	snprintf(name, sizeof(name), "disk/virtual/virtio_block/%" B_PRId32 "/raw",
576 		id);
577 
578 	status = sDeviceManager->publish_device(info->node, name,
579 		VIRTIO_BLOCK_DEVICE_MODULE_NAME);
580 
581 	return status;
582 }
583 
584 
585 //	#pragma mark -
586 
587 
588 module_dependency module_dependencies[] = {
589 	{ B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager },
590 	{ NULL }
591 };
592 
593 struct device_module_info sVirtioBlockDevice = {
594 	{
595 		VIRTIO_BLOCK_DEVICE_MODULE_NAME,
596 		0,
597 		NULL
598 	},
599 
600 	virtio_block_init_device,
601 	virtio_block_uninit_device,
602 	NULL, // remove,
603 
604 	virtio_block_open,
605 	virtio_block_close,
606 	virtio_block_free,
607 	NULL,	// read
608 	NULL,	// write
609 	virtio_block_io,
610 	virtio_block_ioctl,
611 
612 	NULL,	// select
613 	NULL,	// deselect
614 };
615 
616 struct driver_module_info sVirtioBlockDriver = {
617 	{
618 		VIRTIO_BLOCK_DRIVER_MODULE_NAME,
619 		0,
620 		NULL
621 	},
622 
623 	virtio_block_supports_device,
624 	virtio_block_register_device,
625 	virtio_block_init_driver,
626 	virtio_block_uninit_driver,
627 	virtio_block_register_child_devices,
628 	NULL,	// rescan
629 	NULL,	// removed
630 };
631 
632 module_info* modules[] = {
633 	(module_info*)&sVirtioBlockDriver,
634 	(module_info*)&sVirtioBlockDevice,
635 	NULL
636 };
637