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