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