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