xref: /haiku/src/add-ons/kernel/drivers/disk/scsi/scsi_disk/scsi_disk.cpp (revision 25a7b01d15612846f332751841da3579db313082)
1 /*
2  * Copyright 2008-2012, Axel Dörfler, axeld@pinc-software.de.
3  * Copyright 2002/03, Thomas Kurschel. All rights reserved.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 /*!	Peripheral driver to handle any kind of SCSI disks,
8 	i.e. hard disk and floopy disks (ZIP etc.)
9 
10 	Much work is done by scsi_periph and block_io.
11 
12 	You'll find das_... all over the place. This stands for
13 	"Direct Access Storage" which is the official SCSI name for
14 	normal (floppy/hard/ZIP)-disk drives.
15 */
16 
17 
18 #include "scsi_disk.h"
19 
20 #include <string.h>
21 #include <stdlib.h>
22 
23 #include <fs/devfs.h>
24 
25 #include "dma_resources.h"
26 #include "IORequest.h"
27 #include "IOSchedulerSimple.h"
28 
29 
30 //#define TRACE_SCSI_DISK
31 #ifdef TRACE_SCSI_DISK
32 #	define TRACE(x...) dprintf("scsi_disk: " x)
33 #else
34 #	define TRACE(x...) ;
35 #endif
36 
37 
38 static const uint8 kDriveIcon[] = {
39 	0x6e, 0x63, 0x69, 0x66, 0x08, 0x03, 0x01, 0x00, 0x00, 0x02, 0x00, 0x16,
40 	0x02, 0x3c, 0xc7, 0xee, 0x38, 0x9b, 0xc0, 0xba, 0x16, 0x57, 0x3e, 0x39,
41 	0xb0, 0x49, 0x77, 0xc8, 0x42, 0xad, 0xc7, 0x00, 0xff, 0xff, 0xd3, 0x02,
42 	0x00, 0x06, 0x02, 0x3c, 0x96, 0x32, 0x3a, 0x4d, 0x3f, 0xba, 0xfc, 0x01,
43 	0x3d, 0x5a, 0x97, 0x4b, 0x57, 0xa5, 0x49, 0x84, 0x4d, 0x00, 0x47, 0x47,
44 	0x47, 0xff, 0xa5, 0xa0, 0xa0, 0x02, 0x00, 0x16, 0x02, 0xbc, 0x59, 0x2f,
45 	0xbb, 0x29, 0xa7, 0x3c, 0x0c, 0xe4, 0xbd, 0x0b, 0x7c, 0x48, 0x92, 0xc0,
46 	0x4b, 0x79, 0x66, 0x00, 0x7d, 0xff, 0xd4, 0x02, 0x00, 0x06, 0x02, 0x38,
47 	0xdb, 0xb4, 0x39, 0x97, 0x33, 0xbc, 0x4a, 0x33, 0x3b, 0xa5, 0x42, 0x48,
48 	0x6e, 0x66, 0x49, 0xee, 0x7b, 0x00, 0x59, 0x67, 0x56, 0xff, 0xeb, 0xb2,
49 	0xb2, 0x03, 0xa7, 0xff, 0x00, 0x03, 0xff, 0x00, 0x00, 0x04, 0x01, 0x80,
50 	0x07, 0x0a, 0x06, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x5a, 0x3e, 0x5a,
51 	0x31, 0x39, 0x25, 0x0a, 0x04, 0x22, 0x3c, 0x44, 0x4b, 0x5a, 0x31, 0x39,
52 	0x25, 0x0a, 0x04, 0x44, 0x4b, 0x44, 0x5b, 0x5a, 0x3e, 0x5a, 0x31, 0x0a,
53 	0x04, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x44, 0x4b, 0x08, 0x02, 0x27,
54 	0x43, 0xb8, 0x14, 0xc1, 0xf1, 0x08, 0x02, 0x26, 0x43, 0x29, 0x44, 0x0a,
55 	0x05, 0x44, 0x5d, 0x49, 0x5d, 0x60, 0x3e, 0x5a, 0x3b, 0x5b, 0x3f, 0x08,
56 	0x0a, 0x07, 0x01, 0x06, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x10, 0x01, 0x17,
57 	0x84, 0x00, 0x04, 0x0a, 0x01, 0x01, 0x01, 0x00, 0x0a, 0x02, 0x01, 0x02,
58 	0x00, 0x0a, 0x03, 0x01, 0x03, 0x00, 0x0a, 0x04, 0x01, 0x04, 0x10, 0x01,
59 	0x17, 0x85, 0x20, 0x04, 0x0a, 0x06, 0x01, 0x05, 0x30, 0x24, 0xb3, 0x99,
60 	0x01, 0x17, 0x82, 0x00, 0x04, 0x0a, 0x05, 0x01, 0x05, 0x30, 0x20, 0xb2,
61 	0xe6, 0x01, 0x17, 0x82, 0x00, 0x04
62 };
63 
64 
65 static scsi_periph_interface* sSCSIPeripheral;
66 static device_manager_info* sDeviceManager;
67 
68 
69 static status_t
70 update_capacity(das_driver_info* device)
71 {
72 	TRACE("update_capacity()\n");
73 
74 	scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device);
75 	if (ccb == NULL)
76 		return B_NO_MEMORY;
77 
78 	status_t status = sSCSIPeripheral->check_capacity(
79 		device->scsi_periph_device, ccb);
80 
81 	device->scsi->free_ccb(ccb);
82 
83 	return status;
84 }
85 
86 
87 static status_t
88 get_geometry(das_handle* handle, device_geometry* geometry)
89 {
90 	das_driver_info* info = handle->info;
91 
92 	status_t status = update_capacity(info);
93 	if (status != B_OK)
94 		return status;
95 
96 
97 	devfs_compute_geometry_size(geometry, info->capacity, info->block_size);
98 
99 	geometry->device_type = B_DISK;
100 	geometry->removable = info->removable;
101 
102 	// TBD: for all but CD-ROMs, read mode sense - medium type
103 	// (bit 7 of block device specific parameter for Optical Memory Block Device)
104 	// (same for Direct-Access Block Devices)
105 	// (same for write-once block devices)
106 	// (same for optical memory block devices)
107 	geometry->read_only = false;
108 	geometry->write_once = false;
109 
110 	TRACE("scsi_disk: get_geometry(): %ld, %ld, %ld, %ld, %d, %d, %d, %d\n",
111 		geometry->bytes_per_sector, geometry->sectors_per_track,
112 		geometry->cylinder_count, geometry->head_count, geometry->device_type,
113 		geometry->removable, geometry->read_only, geometry->write_once);
114 
115 	return B_OK;
116 }
117 
118 
119 static status_t
120 load_eject(das_driver_info *device, bool load)
121 {
122 	TRACE("load_eject()\n");
123 
124 	scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device);
125 	if (ccb == NULL)
126 		return B_NO_MEMORY;
127 
128 	err_res result = sSCSIPeripheral->send_start_stop(
129 		device->scsi_periph_device, ccb, load, true);
130 
131 	device->scsi->free_ccb(ccb);
132 
133 	return result.error_code;
134 }
135 
136 
137 static status_t
138 synchronize_cache(das_driver_info *device)
139 {
140 	TRACE("synchronize_cache()\n");
141 
142 	scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device);
143 	if (ccb == NULL)
144 		return B_NO_MEMORY;
145 
146 	err_res result = sSCSIPeripheral->synchronize_cache(
147 		device->scsi_periph_device, ccb);
148 
149 	device->scsi->free_ccb(ccb);
150 
151 	return result.error_code;
152 }
153 
154 
155 static int
156 log2(uint32 x)
157 {
158 	int y;
159 
160 	for (y = 31; y >= 0; --y) {
161 		if (x == ((uint32)1 << y))
162 			break;
163 	}
164 
165 	return y;
166 }
167 
168 
169 static status_t
170 do_io(void* cookie, IOOperation* operation)
171 {
172 	das_driver_info* info = (das_driver_info*)cookie;
173 
174 	// TODO: this can go away as soon as we pushed the IOOperation to the upper
175 	// layers - we can then set scsi_periph::io() as callback for the scheduler
176 	size_t bytesTransferred;
177 	status_t status = sSCSIPeripheral->io(info->scsi_periph_device, operation,
178 		&bytesTransferred);
179 
180 	info->io_scheduler->OperationCompleted(operation, status, bytesTransferred);
181 	return status;
182 }
183 
184 
185 //	#pragma mark - device module API
186 
187 
188 static status_t
189 das_init_device(void* _info, void** _cookie)
190 {
191 	das_driver_info* info = (das_driver_info*)_info;
192 
193 	// and get (initial) capacity
194 	scsi_ccb *request = info->scsi->alloc_ccb(info->scsi_device);
195 	if (request == NULL)
196 		return B_NO_MEMORY;
197 
198 	sSCSIPeripheral->check_capacity(info->scsi_periph_device, request);
199 	info->scsi->free_ccb(request);
200 
201 	*_cookie = info;
202 	return B_OK;
203 }
204 
205 
206 static void
207 das_uninit_device(void* _cookie)
208 {
209 	das_driver_info* info = (das_driver_info*)_cookie;
210 
211 	delete info->io_scheduler;
212 	delete info->dma_resource;
213 }
214 
215 
216 static status_t
217 das_open(void* _info, const char* path, int openMode, void** _cookie)
218 {
219 	das_driver_info* info = (das_driver_info*)_info;
220 
221 	das_handle* handle = (das_handle*)malloc(sizeof(das_handle));
222 	if (handle == NULL)
223 		return B_NO_MEMORY;
224 
225 	handle->info = info;
226 
227 	status_t status = sSCSIPeripheral->handle_open(info->scsi_periph_device,
228 		(periph_handle_cookie)handle, &handle->scsi_periph_handle);
229 	if (status < B_OK) {
230 		free(handle);
231 		return status;
232 	}
233 
234 	*_cookie = handle;
235 	return B_OK;
236 }
237 
238 
239 static status_t
240 das_close(void* cookie)
241 {
242 	das_handle* handle = (das_handle*)cookie;
243 	TRACE("close()\n");
244 
245 	sSCSIPeripheral->handle_close(handle->scsi_periph_handle);
246 	return B_OK;
247 }
248 
249 
250 static status_t
251 das_free(void* cookie)
252 {
253 	das_handle* handle = (das_handle*)cookie;
254 	TRACE("free()\n");
255 
256 	sSCSIPeripheral->handle_free(handle->scsi_periph_handle);
257 	free(handle);
258 	return B_OK;
259 }
260 
261 
262 static status_t
263 das_read(void* cookie, off_t pos, void* buffer, size_t* _length)
264 {
265 	das_handle* handle = (das_handle*)cookie;
266 	size_t length = *_length;
267 
268 	IORequest request;
269 	status_t status = request.Init(pos, (addr_t)buffer, length, false, 0);
270 	if (status != B_OK)
271 		return status;
272 
273 	status = handle->info->io_scheduler->ScheduleRequest(&request);
274 	if (status != B_OK)
275 		return status;
276 
277 	status = request.Wait(0, 0);
278 	if (status == B_OK)
279 		*_length = length;
280 	else
281 		dprintf("das_read(): request.Wait() returned: %s\n", strerror(status));
282 
283 	return status;
284 }
285 
286 
287 static status_t
288 das_write(void* cookie, off_t pos, const void* buffer, size_t* _length)
289 {
290 	das_handle* handle = (das_handle*)cookie;
291 	size_t length = *_length;
292 
293 	IORequest request;
294 	status_t status = request.Init(pos, (addr_t)buffer, length, true, 0);
295 	if (status != B_OK)
296 		return status;
297 
298 	status = handle->info->io_scheduler->ScheduleRequest(&request);
299 	if (status != B_OK)
300 		return status;
301 
302 	status = request.Wait(0, 0);
303 	if (status == B_OK)
304 		*_length = length;
305 	else
306 		dprintf("das_write(): request.Wait() returned: %s\n", strerror(status));
307 
308 	return status;
309 }
310 
311 
312 static status_t
313 das_io(void *cookie, io_request *request)
314 {
315 	das_handle* handle = (das_handle*)cookie;
316 
317 	return handle->info->io_scheduler->ScheduleRequest(request);
318 }
319 
320 
321 static status_t
322 das_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
323 {
324 	das_handle* handle = (das_handle*)cookie;
325 	das_driver_info* info = handle->info;
326 
327 	TRACE("ioctl(op = %ld)\n", op);
328 
329 	switch (op) {
330 		case B_GET_DEVICE_SIZE:
331 		{
332 			status_t status = update_capacity(info);
333 			if (status != B_OK)
334 				return status;
335 
336 			size_t size = info->capacity * info->block_size;
337 			return user_memcpy(buffer, &size, sizeof(size_t));
338 		}
339 
340 		case B_GET_GEOMETRY:
341 		{
342 			if (buffer == NULL /*|| length != sizeof(device_geometry)*/)
343 				return B_BAD_VALUE;
344 
345 		 	device_geometry geometry;
346 			status_t status = get_geometry(handle, &geometry);
347 			if (status != B_OK)
348 				return status;
349 
350 			return user_memcpy(buffer, &geometry, sizeof(device_geometry));
351 		}
352 
353 		case B_GET_ICON_NAME:
354 			// TODO: take device type into account!
355 			return user_strlcpy((char*)buffer, info->removable
356 				? "devices/drive-removable-media" : "devices/drive-harddisk",
357 				B_FILE_NAME_LENGTH);
358 
359 		case B_GET_VECTOR_ICON:
360 		{
361 			// TODO: take device type into account!
362 			device_icon iconData;
363 			if (length != sizeof(device_icon))
364 				return B_BAD_VALUE;
365 			if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK)
366 				return B_BAD_ADDRESS;
367 
368 			if (iconData.icon_size >= (int32)sizeof(kDriveIcon)) {
369 				if (user_memcpy(iconData.icon_data, kDriveIcon,
370 						sizeof(kDriveIcon)) != B_OK)
371 					return B_BAD_ADDRESS;
372 			}
373 
374 			iconData.icon_size = sizeof(kDriveIcon);
375 			return user_memcpy(buffer, &iconData, sizeof(device_icon));
376 		}
377 
378 		case B_EJECT_DEVICE:
379 		case B_SCSI_EJECT:
380 			return load_eject(info, false);
381 
382 		case B_LOAD_MEDIA:
383 			return load_eject(info, true);
384 
385 		case B_FLUSH_DRIVE_CACHE:
386 			return synchronize_cache(info);
387 
388 		default:
389 			return sSCSIPeripheral->ioctl(handle->scsi_periph_handle, op,
390 				buffer, length);
391 	}
392 }
393 
394 
395 //	#pragma mark - scsi_periph callbacks
396 
397 
398 static void
399 das_set_capacity(das_driver_info* info, uint64 capacity, uint32 blockSize)
400 {
401 	TRACE("das_set_capacity(device = %p, capacity = %Ld, blockSize = %ld)\n",
402 		info, capacity, blockSize);
403 
404 	// get log2, if possible
405 	uint32 blockShift = log2(blockSize);
406 
407 	if ((1UL << blockShift) != blockSize)
408 		blockShift = 0;
409 
410 	info->capacity = capacity;
411 
412 	if (info->block_size != blockSize) {
413 		if (info->block_size != 0) {
414 			dprintf("old %" B_PRId32 ", new %" B_PRId32 "\n", info->block_size,
415 				blockSize);
416 			panic("updating DMAResource not yet implemented...");
417 		}
418 
419 		// TODO: we need to replace the DMAResource in our IOScheduler
420 		status_t status = info->dma_resource->Init(info->node, blockSize, 1024,
421 			32);
422 		if (status != B_OK)
423 			panic("initializing DMAResource failed: %s", strerror(status));
424 
425 		info->io_scheduler = new(std::nothrow) IOSchedulerSimple(
426 			info->dma_resource);
427 		if (info->io_scheduler == NULL)
428 			panic("allocating IOScheduler failed.");
429 
430 		// TODO: use whole device name here
431 		status = info->io_scheduler->Init("scsi");
432 		if (status != B_OK)
433 			panic("initializing IOScheduler failed: %s", strerror(status));
434 
435 		info->io_scheduler->SetCallback(do_io, info);
436 	}
437 
438 	info->block_size = blockSize;
439 }
440 
441 
442 static void
443 das_media_changed(das_driver_info *device, scsi_ccb *request)
444 {
445 	// do a capacity check
446 	// TODO: is this a good idea (e.g. if this is an empty CD)?
447 	sSCSIPeripheral->check_capacity(device->scsi_periph_device, request);
448 }
449 
450 
451 scsi_periph_callbacks callbacks = {
452 	(void (*)(periph_device_cookie, uint64, uint32))das_set_capacity,
453 	(void (*)(periph_device_cookie, scsi_ccb *))das_media_changed
454 };
455 
456 
457 //	#pragma mark - driver module API
458 
459 
460 static float
461 das_supports_device(device_node *parent)
462 {
463 	const char *bus;
464 	uint8 deviceType;
465 
466 	// make sure parent is really the SCSI bus manager
467 	if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
468 		return -1;
469 
470 	if (strcmp(bus, "scsi"))
471 		return 0.0;
472 
473 	// check whether it's really a Direct Access Device
474 	if (sDeviceManager->get_attr_uint8(parent, SCSI_DEVICE_TYPE_ITEM,
475 			&deviceType, true) != B_OK || deviceType != scsi_dev_direct_access)
476 		return 0.0;
477 
478 	return 0.6;
479 }
480 
481 
482 /*!	Called whenever a new device was added to system;
483 	if we really support it, we create a new node that gets
484 	server by the block_io module
485 */
486 static status_t
487 das_register_device(device_node *node)
488 {
489 	const scsi_res_inquiry *deviceInquiry = NULL;
490 	size_t inquiryLength;
491 	uint32 maxBlocks;
492 
493 	// get inquiry data
494 	if (sDeviceManager->get_attr_raw(node, SCSI_DEVICE_INQUIRY_ITEM,
495 			(const void **)&deviceInquiry, &inquiryLength, true) != B_OK
496 		|| inquiryLength < sizeof(deviceInquiry))
497 		return B_ERROR;
498 
499 	// get block limit of underlying hardware to lower it (if necessary)
500 	if (sDeviceManager->get_attr_uint32(node, B_DMA_MAX_TRANSFER_BLOCKS,
501 			&maxBlocks, true) != B_OK)
502 		maxBlocks = INT_MAX;
503 
504 	// using 10 byte commands, at most 0xffff blocks can be transmitted at once
505 	// (sadly, we cannot update this value later on if only 6 byte commands
506 	//  are supported, but the block_io module can live with that)
507 	maxBlocks = min_c(maxBlocks, 0xffff);
508 
509 	// ready to register
510 	device_attr attrs[] = {
511 		// tell block_io whether the device is removable
512 		{"removable", B_UINT8_TYPE, {ui8: deviceInquiry->removable_medium}},
513 		// impose own max block restriction
514 		{B_DMA_MAX_TRANSFER_BLOCKS, B_UINT32_TYPE, {ui32: maxBlocks}},
515 		{ NULL }
516 	};
517 
518 	return sDeviceManager->register_node(node, SCSI_DISK_DRIVER_MODULE_NAME,
519 		attrs, NULL, NULL);
520 }
521 
522 
523 static status_t
524 das_init_driver(device_node *node, void **cookie)
525 {
526 	TRACE("das_init_driver");
527 
528 	uint8 removable;
529 	status_t status = sDeviceManager->get_attr_uint8(node, "removable",
530 		&removable, false);
531 	if (status != B_OK)
532 		return status;
533 
534 	das_driver_info* info = (das_driver_info*)malloc(sizeof(das_driver_info));
535 	if (info == NULL)
536 		return B_NO_MEMORY;
537 
538 	memset(info, 0, sizeof(*info));
539 
540 	info->dma_resource = new(std::nothrow) DMAResource;
541 	if (info->dma_resource == NULL) {
542 		free(info);
543 		return B_NO_MEMORY;
544 	}
545 
546 	info->node = node;
547 	info->removable = removable;
548 
549 	device_node* parent = sDeviceManager->get_parent_node(node);
550 	sDeviceManager->get_driver(parent, (driver_module_info **)&info->scsi,
551 		(void **)&info->scsi_device);
552 	sDeviceManager->put_node(parent);
553 
554 	status = sSCSIPeripheral->register_device((periph_device_cookie)info,
555 		&callbacks, info->scsi_device, info->scsi, info->node,
556 		info->removable, 10, &info->scsi_periph_device);
557 	if (status != B_OK) {
558 		free(info);
559 		return status;
560 	}
561 
562 	*cookie = info;
563 	return B_OK;
564 }
565 
566 
567 static void
568 das_uninit_driver(void *_cookie)
569 {
570 	das_driver_info* info = (das_driver_info*)_cookie;
571 
572 	sSCSIPeripheral->unregister_device(info->scsi_periph_device);
573 	free(info);
574 }
575 
576 
577 static status_t
578 das_register_child_devices(void* _cookie)
579 {
580 	das_driver_info* info = (das_driver_info*)_cookie;
581 	status_t status;
582 
583 	char* name = sSCSIPeripheral->compose_device_name(info->node,
584 		"disk/scsi");
585 	if (name == NULL)
586 		return B_ERROR;
587 
588 	status = sDeviceManager->publish_device(info->node, name,
589 		SCSI_DISK_DEVICE_MODULE_NAME);
590 
591 	free(name);
592 	return status;
593 }
594 
595 
596 module_dependency module_dependencies[] = {
597 	{SCSI_PERIPH_MODULE_NAME, (module_info**)&sSCSIPeripheral},
598 	{B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager},
599 	{}
600 };
601 
602 struct device_module_info sSCSIDiskDevice = {
603 	{
604 		SCSI_DISK_DEVICE_MODULE_NAME,
605 		0,
606 		NULL
607 	},
608 
609 	das_init_device,
610 	das_uninit_device,
611 	NULL, //das_remove,
612 
613 	das_open,
614 	das_close,
615 	das_free,
616 	das_read,
617 	das_write,
618 	das_io,
619 	das_ioctl,
620 
621 	NULL,	// select
622 	NULL,	// deselect
623 };
624 
625 struct driver_module_info sSCSIDiskDriver = {
626 	{
627 		SCSI_DISK_DRIVER_MODULE_NAME,
628 		0,
629 		NULL
630 	},
631 
632 	das_supports_device,
633 	das_register_device,
634 	das_init_driver,
635 	das_uninit_driver,
636 	das_register_child_devices,
637 	NULL,	// rescan
638 	NULL,	// removed
639 };
640 
641 module_info* modules[] = {
642 	(module_info*)&sSCSIDiskDriver,
643 	(module_info*)&sSCSIDiskDevice,
644 	NULL
645 };
646