xref: /haiku/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.cpp (revision 079eccf655ba39812b421ae1b87a727d41b50354)
1 /*
2  * Copyright 2004-2008, Haiku, Inc. All RightsReserved.
3  * Copyright 2002-2003, Thomas Kurschel. All rights reserved.
4  *
5  * Distributed under the terms of the MIT License.
6  */
7 
8 /*!	Peripheral driver to handle CD-ROM drives. To be more
9 	precisely, it supports CD-ROM and WORM drives (well -
10 	I've never _seen_ a WORM driver).
11 
12 	Much work is done by scsi_periph and block_io.
13 */
14 
15 
16 #include "scsi_cd.h"
17 
18 #include <stdlib.h>
19 #include <string.h>
20 
21 
22 //#define TRACE_CD_DISK
23 #ifdef TRACE_CD_DISK
24 #	define TRACE(x...) dprintf("scsi_cd: " x)
25 #else
26 #	define TRACE(x...) ;
27 #endif
28 
29 
30 static scsi_periph_interface *sSCSIPeripheral;
31 static device_manager_info *sDeviceManager;
32 static block_io_for_driver_interface *sBlockIO;
33 
34 
35 #define SCSI_CD_STD_TIMEOUT 10
36 
37 
38 static status_t
39 update_capacity(cd_device_info *device)
40 {
41 	TRACE("update_capacity()\n");
42 
43 	scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device);
44 	if (ccb == NULL)
45 		return B_NO_MEMORY;
46 
47 	status_t status = sSCSIPeripheral->check_capacity(
48 		device->scsi_periph_device, ccb);
49 
50 	device->scsi->free_ccb(ccb);
51 
52 	return status;
53 }
54 
55 
56 static status_t
57 get_geometry(cd_handle_info *handle, device_geometry *geometry)
58 {
59 	cd_device_info *device = handle->device;
60 
61 	status_t status = update_capacity(device);
62 
63 	// it seems that Be expects B_GET_GEOMETRY to always succeed unless
64 	// the medium has been changed; e.g. if we report B_DEV_NO_MEDIA, the
65 	// device is ignored by the CDPlayer and CDBurner
66 	if (status == B_DEV_MEDIA_CHANGED)
67 		return B_DEV_MEDIA_CHANGED;
68 
69 	geometry->bytes_per_sector = device->block_size;
70 	geometry->sectors_per_track = 1;
71 	geometry->cylinder_count = device->capacity;
72 	geometry->head_count = 1;
73 	geometry->device_type = device->device_type;
74 	geometry->removable = device->removable;
75 
76 	// TBD: for all but CD-ROMs, read mode sense - medium type
77 	// (bit 7 of block device specific parameter for Optical Memory Block Device)
78 	// (same for Direct-Access Block Devices)
79 	// (same for write-once block devices)
80 	// (same for optical memory block devices)
81 	geometry->read_only = true;
82 	geometry->write_once = device->device_type == scsi_dev_WORM;
83 
84 	TRACE("scsi_disk: get_geometry(): %ld, %ld, %ld, %ld, %d, %d, %d, %d\n",
85 		geometry->bytes_per_sector, geometry->sectors_per_track,
86 		geometry->cylinder_count, geometry->head_count, geometry->device_type,
87 		geometry->removable, geometry->read_only, geometry->write_once);
88 
89 	return B_OK;
90 }
91 
92 
93 static status_t
94 get_toc(cd_device_info *device, scsi_toc *toc)
95 {
96 	scsi_ccb *ccb;
97 	status_t res;
98 	scsi_cmd_read_toc *cmd;
99 	size_t dataLength;
100 	scsi_toc_general *short_response = (scsi_toc_general *)toc->toc_data;
101 
102 	TRACE("get_toc()\n");
103 
104 	ccb = device->scsi->alloc_ccb(device->scsi_device);
105 	if (ccb == NULL)
106 		return B_NO_MEMORY;
107 
108 	// first read number of tracks only
109 	ccb->flags = SCSI_DIR_IN;
110 
111 	cmd = (scsi_cmd_read_toc *)ccb->cdb;
112 
113 	memset(cmd, 0, sizeof(*cmd));
114 	cmd->opcode = SCSI_OP_READ_TOC;
115 	cmd->time = 1;
116 	cmd->format = SCSI_TOC_FORMAT_TOC;
117 	cmd->track = 1;
118 	cmd->allocation_length = B_HOST_TO_BENDIAN_INT16(sizeof(scsi_toc_general));
119 
120 	ccb->cdb_length = sizeof(*cmd);
121 
122 	ccb->sort = -1;
123 	ccb->timeout = SCSI_CD_STD_TIMEOUT;
124 
125 	ccb->data = toc->toc_data;
126 	ccb->sg_list = NULL;
127 	ccb->data_length = sizeof(toc->toc_data);
128 
129 	res = sSCSIPeripheral->safe_exec(device->scsi_periph_device, ccb);
130 	if (res != B_OK)
131 		goto err;
132 
133 	// then read all track infos
134 	// (little hint: number of tracks is last - first + 1;
135 	//  but scsi_toc_toc has already one track, so we get
136 	//  last - first extra tracks; finally, we want the lead-out as
137 	//  well, so we add an extra track)
138 	dataLength = (short_response->last - short_response->first + 1)
139 		* sizeof(scsi_toc_track) + sizeof(scsi_toc_toc);
140 	dataLength = min_c(dataLength, sizeof(toc->toc_data));
141 
142 	TRACE("  tracks: %d - %d, data length %d\n", short_response->first,
143 		short_response->last, (int)dataLength);
144 
145 	cmd->allocation_length = B_HOST_TO_BENDIAN_INT16(dataLength);
146 
147 	res = sSCSIPeripheral->safe_exec(device->scsi_periph_device, ccb);
148 
149 err:
150 	device->scsi->free_ccb(ccb);
151 
152 	return res;
153 }
154 
155 
156 static status_t
157 load_eject(cd_device_info *device, bool load)
158 {
159 	TRACE("load_eject()\n");
160 
161 	scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device);
162 	if (ccb == NULL)
163 		return B_NO_MEMORY;
164 
165 	err_res result = sSCSIPeripheral->send_start_stop(
166 		device->scsi_periph_device, ccb, load, true);
167 
168 	device->scsi->free_ccb(ccb);
169 
170 	return result.error_code;
171 }
172 
173 
174 static status_t
175 get_position(cd_device_info *device, scsi_position *position)
176 {
177 	scsi_cmd_read_subchannel cmd;
178 
179 	TRACE("get_position()\n");
180 
181 	memset(&cmd, 0, sizeof(cmd));
182 	cmd.opcode = SCSI_OP_READ_SUB_CHANNEL;
183 	cmd.time = 1;
184 	cmd.subq = 1;
185 	cmd.parameter_list = scsi_sub_channel_parameter_list_cd_pos;
186 	cmd.track = 0;
187 	cmd.allocation_length = B_HOST_TO_BENDIAN_INT16(sizeof(scsi_position));
188 
189 	return sSCSIPeripheral->simple_exec(device->scsi_periph_device,
190 		&cmd, sizeof(cmd), position, sizeof(*position), SCSI_DIR_IN);
191 }
192 
193 
194 static status_t
195 get_set_volume(cd_device_info *device, scsi_volume *volume, bool set)
196 {
197 	scsi_cmd_mode_sense_6 cmd;
198 	scsi_mode_param_header_6 header;
199 	size_t len;
200 	void *buffer;
201 	scsi_modepage_audio	*page;
202 	status_t res;
203 
204 	TRACE("get_set_volume()\n");
205 
206 	// determine size of block descriptor
207 	memset(&cmd, 0, sizeof(cmd));
208 	cmd.opcode = SCSI_OP_MODE_SENSE_6;
209 	cmd.page_code = SCSI_MODEPAGE_AUDIO;
210 	cmd.page_control = SCSI_MODE_SENSE_PC_CURRENT;
211 	cmd.allocation_length = sizeof(header);
212 
213 	memset(&header, -2, sizeof(header));
214 
215 	res = sSCSIPeripheral->simple_exec(device->scsi_periph_device, &cmd,
216 		sizeof(cmd), &header, sizeof(header), SCSI_DIR_IN);
217 	if (res != B_OK)
218 		return res;
219 
220 	TRACE("  block_desc_len=%d", header.block_desc_length);
221 #if 0
222 	// ToDo: why this??
223 	return B_ERROR;
224 #endif
225 
226 	// retrieve param header, block descriptor and actual codepage
227 	len = sizeof(header) + header.block_desc_length
228 		+ sizeof(scsi_modepage_audio);
229 
230 	buffer = malloc(len);
231 	if (buffer == NULL)
232 		return B_NO_MEMORY;
233 
234 	memset(buffer, -1, sizeof(buffer));
235 
236 	cmd.allocation_length = len;
237 
238 	res = sSCSIPeripheral->simple_exec(device->scsi_periph_device, &cmd,
239 		sizeof(cmd), buffer, len, SCSI_DIR_IN);
240 	if (res != B_OK) {
241 		free(buffer);
242 		return res;
243 	}
244 
245 	TRACE("  mode_data_len=%d, block_desc_len=%d",
246 		((scsi_mode_param_header_6 *)buffer)->mode_data_length,
247 		((scsi_mode_param_header_6 *)buffer)->block_desc_length);
248 
249 	// find control page and retrieve values
250 	page = (scsi_modepage_audio *)((char *)buffer + sizeof(header)
251 		+ header.block_desc_length);
252 
253 	TRACE("  page=%p, codepage=%d", page, page->header.page_code);
254 
255 	if (!set) {
256 		volume->port0_channel = page->ports[0].channel;
257 		volume->port0_volume  = page->ports[0].volume;
258 		volume->port1_channel = page->ports[1].channel;
259 		volume->port1_volume  = page->ports[1].volume;
260 		volume->port2_channel = page->ports[2].channel;
261 		volume->port2_volume  = page->ports[2].volume;
262 		volume->port3_channel = page->ports[3].channel;
263 		volume->port3_volume  = page->ports[3].volume;
264 
265 #if 0
266 		SHOW_FLOW(3, "1: %d - %d", volume->port0_channel, volume->port0_volume);
267 		SHOW_FLOW(3, "2: %d - %d", volume->port1_channel, volume->port1_volume);
268 		SHOW_FLOW(3, "3: %d - %d", volume->port2_channel, volume->port2_volume);
269 		SHOW_FLOW(3, "4: %d - %d", volume->port3_channel, volume->port3_volume);
270 #endif
271 		res = B_OK;
272 	} else {
273 		scsi_cmd_mode_select_6 cmd;
274 
275 		if (volume->flags & 0x01)
276 			page->ports[0].channel = volume->port0_channel;
277 		if (volume->flags & 0x02)
278 			page->ports[0].volume = volume->port0_volume;
279 		if (volume->flags & 0x04)
280 			page->ports[1].channel = volume->port1_channel;
281 		if (volume->flags & 0x08)
282 			page->ports[1].volume = volume->port1_volume;
283 		if (volume->flags & 0x10)
284 			page->ports[2].channel = volume->port2_channel;
285 		if (volume->flags & 0x20)
286 			page->ports[2].volume = volume->port2_volume;
287 		if (volume->flags & 0x40)
288 			page->ports[3].channel = volume->port3_channel;
289 		if (volume->flags & 0x80)
290 			page->ports[3].volume = volume->port3_volume;
291 
292 		memset(&cmd, 0, sizeof(cmd));
293 		cmd.opcode = SCSI_OP_MODE_SELECT_6;
294 		cmd.pf = 1;
295 		cmd.param_list_length = sizeof(header) + header.block_desc_length
296 			+ sizeof(*page);
297 
298 		res = sSCSIPeripheral->simple_exec(device->scsi_periph_device,
299 			&cmd, sizeof(cmd), buffer, len, SCSI_DIR_OUT);
300 	}
301 
302 	free(buffer);
303 	return res;
304 }
305 
306 
307 /*!	Play audio cd; time is in MSF */
308 static status_t
309 play_msf(cd_device_info *device, const scsi_play_position *position)
310 {
311 	scsi_cmd_play_msf cmd;
312 
313 	TRACE("play_msf(): %d:%d:%d-%d:%d:%d\n", position->start_m,
314 		position->start_s, position->start_f, position->end_m, position->end_s,
315 		position->end_f);
316 
317 	memset(&cmd, 0, sizeof(cmd));
318 
319 	cmd.opcode = SCSI_OP_PLAY_MSF;
320 	cmd.start_minute = position->start_m;
321 	cmd.start_second = position->start_s;
322 	cmd.start_frame = position->start_f;
323 	cmd.end_minute = position->end_m;
324 	cmd.end_second = position->end_s;
325 	cmd.end_frame = position->end_f;
326 
327 	return sSCSIPeripheral->simple_exec(device->scsi_periph_device,
328 		&cmd, sizeof(cmd), NULL, 0, 0);
329 }
330 
331 
332 /*! Play audio cd; time is in track/index */
333 static status_t
334 play_track_index(cd_device_info *device, const scsi_play_track *buf)
335 {
336 	scsi_toc generic_toc;
337 	scsi_toc_toc *toc;
338 	status_t res;
339 	int start_track, end_track;
340 	scsi_play_position position;
341 
342 	TRACE("play_track_index(): %d-%d\n", buf->start_track, buf->end_track);
343 
344 	// the corresponding command PLAY AUDIO TRACK/INDEX	is deprecated,
345 	// so we have to simulate it by converting track to time via TOC
346 	res = get_toc(device, &generic_toc);
347 	if (res != B_OK)
348 		return res;
349 
350 	toc = (scsi_toc_toc *)&generic_toc.toc_data[0];
351 
352 	start_track = buf->start_track;
353 	end_track = buf->end_track;
354 
355 	if (start_track > toc->last_track)
356 		return B_BAD_INDEX;
357 
358 	if (end_track > toc->last_track)
359 		end_track = toc->last_track + 1;
360 
361 	if (end_track < toc->last_track + 1)
362 		++end_track;
363 
364 	start_track -= toc->first_track;
365 	end_track -= toc->first_track;
366 
367 	if (start_track < 0 || end_track < 0)
368 		return B_BAD_INDEX;
369 
370 	position.start_m = toc->tracks[start_track].start.time.minute;
371 	position.start_s = toc->tracks[start_track].start.time.second;
372 	position.start_f = toc->tracks[start_track].start.time.frame;
373 
374 	position.end_m = toc->tracks[end_track].start.time.minute;
375 	position.end_s = toc->tracks[end_track].start.time.second;
376 	position.end_f = toc->tracks[end_track].start.time.frame;
377 
378 	return play_msf(device, &position);
379 }
380 
381 
382 static status_t
383 stop_audio(cd_device_info *device)
384 {
385 	scsi_cmd_stop_play cmd;
386 
387 	TRACE("stop_audio()\n");
388 
389 	memset( &cmd, 0, sizeof( cmd ));
390 	cmd.opcode = SCSI_OP_STOP_PLAY;
391 
392 	return sSCSIPeripheral->simple_exec(device->scsi_periph_device,
393 		&cmd, sizeof(cmd), NULL, 0, 0);
394 }
395 
396 
397 static status_t
398 pause_resume(cd_device_info *device, bool resume)
399 {
400 	scsi_cmd_pause_resume cmd;
401 
402 	TRACE("pause_resume()\n");
403 
404 	memset(&cmd, 0, sizeof(cmd));
405 	cmd.opcode = SCSI_OP_PAUSE_RESUME;
406 	cmd.resume = resume;
407 
408 	return sSCSIPeripheral->simple_exec(device->scsi_periph_device,
409 		&cmd, sizeof(cmd), NULL, 0, 0);
410 }
411 
412 
413 static status_t
414 scan(cd_device_info *device, const scsi_scan *buf)
415 {
416 	scsi_cmd_scan cmd;
417 	scsi_position curPos;
418 	scsi_cd_current_position *cdPos;
419 
420 	TRACE("scan(direction =% d)\n", buf->direction);
421 
422 	status_t res = get_position(device, &curPos);
423 	if (res != B_OK)
424 		return res;
425 
426 	cdPos = (scsi_cd_current_position *)((char *)&curPos
427 		+ sizeof(scsi_subchannel_data_header));
428 
429 	if (buf->direction == 0) {
430 		scsi_play_position playPos;
431 
432 		// to stop scan, we issue play command with "open end"
433 		playPos.start_m = cdPos->absolute_address.time.minute;
434 		playPos.start_s = cdPos->absolute_address.time.second;
435 		playPos.start_f = cdPos->absolute_address.time.frame;
436 		playPos.end_m = 99;
437 		playPos.end_s = 59;
438 		playPos.end_f = 24;
439 
440 		return play_msf(device, &playPos);
441 	}
442 
443 	memset(&cmd, 0, sizeof(cmd));
444 
445 	cmd.opcode = SCSI_OP_SCAN;
446 	cmd.direct = buf->direction < 0;
447 	cmd.start.time = cdPos->absolute_address.time;
448 	cmd.type = scsi_scan_msf;
449 
450 	/*
451 	tmp = (uint8 *)&cmd;
452 	dprintf("%d %d %d %d %d %d %d %d %d %d %d %d\n",
453 		tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5],
454 		tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11]);
455 	*/
456 
457 	return sSCSIPeripheral->simple_exec(device->scsi_periph_device,
458 		&cmd, sizeof(cmd), NULL, 0, 0);
459 }
460 
461 
462 static status_t
463 read_cd(cd_device_info *device, const scsi_read_cd *readCD)
464 {
465 	scsi_cmd_read_cd *cmd;
466 	uint32 lba, length;
467 	scsi_ccb *ccb;
468 	status_t res;
469 
470 	// we use safe_exec instead of simple_exec as we want to set
471 	// the sorting order manually (only makes much sense if you grab
472 	// multiple tracks at once, but we are prepared)
473 	ccb = device->scsi->alloc_ccb(device->scsi_device);
474 
475 	if (ccb == NULL)
476 		return B_NO_MEMORY;
477 
478 	cmd = (scsi_cmd_read_cd *)ccb->cdb;
479 	memset(cmd, 0, sizeof(*cmd));
480 	cmd->opcode = SCSI_OP_READ_CD;
481 	cmd->sector_type = 1;
482 
483 	// skip first two seconds, they are lead-in
484 	lba = (readCD->start_m * 60 + readCD->start_s) * 75 + readCD->start_f
485 		- 2 * 75;
486 	length = (readCD->length_m * 60 + readCD->length_s) * 75 + readCD->length_f;
487 
488 	cmd->lba = B_HOST_TO_BENDIAN_INT32(lba);
489 	cmd->high_length = (length >> 16) & 0xff;
490 	cmd->mid_length = (length >> 8) & 0xff;
491 	cmd->low_length = length & 0xff;
492 
493 	cmd->error_field = scsi_read_cd_error_none;
494 	cmd->edc_ecc = 0;
495 	cmd->user_data = 1;
496 	cmd->header_code = scsi_read_cd_header_none;
497 	cmd->sync = 0;
498 	cmd->sub_channel_selection = scsi_read_cd_sub_channel_none;
499 
500 	ccb->cdb_length = sizeof(*cmd);
501 
502 	ccb->flags = SCSI_DIR_IN | SCSI_DIS_DISCONNECT;
503 	ccb->sort = lba;
504 	// are 10 seconds enough for timeout?
505 	ccb->timeout = 10;
506 
507 	// TODO: we pass a user buffer here!
508 	ccb->data = (uint8 *)readCD->buffer;
509 	ccb->sg_list = NULL;
510 	ccb->data_length = readCD->buffer_length;
511 
512 	res = sSCSIPeripheral->safe_exec(device->scsi_periph_device, ccb);
513 
514 	device->scsi->free_ccb(ccb);
515 
516 	return res;
517 }
518 
519 
520 static int
521 log2(uint32 x)
522 {
523 	int y;
524 
525 	for (y = 31; y >= 0; --y) {
526 		if (x == (1UL << y))
527 			break;
528 	}
529 
530 	return y;
531 }
532 
533 
534 //	#pragma mark - block_io API
535 
536 
537 static void
538 cd_set_device(cd_device_info *info, block_io_device device)
539 {
540 	info->block_io_device = device;
541 }
542 
543 
544 static status_t
545 cd_open(cd_device_info *device, cd_handle_info **_cookie)
546 {
547 	TRACE("open()\n");
548 
549 	cd_handle_info *handle = (cd_handle_info *)malloc(sizeof(*handle));
550 	if (handle == NULL)
551 		return B_NO_MEMORY;
552 
553 	handle->device = device;
554 
555 	status_t status = sSCSIPeripheral->handle_open(device->scsi_periph_device,
556 		(periph_handle_cookie)handle, &handle->scsi_periph_handle);
557 	if (status < B_OK) {
558 		free(handle);
559 		return status;
560 	}
561 
562 	if (device->block_size == 0 || device->capacity == 0) {
563 		scsi_ccb *request = device->scsi->alloc_ccb(device->scsi_device);
564 		if (request != NULL) {
565 			// don't care if no test was possible
566 			sSCSIPeripheral->check_capacity(device->scsi_periph_device, request);
567 			device->scsi->free_ccb(request);
568 		}
569 	}
570 
571 	*_cookie = handle;
572 	return B_OK;
573 }
574 
575 
576 static status_t
577 cd_close(cd_handle_info *handle)
578 {
579 	TRACE("close()\n");
580 
581 	sSCSIPeripheral->handle_close(handle->scsi_periph_handle);
582 	return B_OK;
583 }
584 
585 
586 static status_t
587 cd_free(cd_handle_info *handle)
588 {
589 	TRACE("free()\n");
590 
591 	sSCSIPeripheral->handle_free(handle->scsi_periph_handle);
592 	free(handle);
593 	return B_OK;
594 }
595 
596 
597 static status_t
598 cd_read(cd_handle_info *handle, const phys_vecs *vecs, off_t pos,
599 	size_t num_blocks, uint32 block_size, size_t *bytes_transferred)
600 {
601 	return sSCSIPeripheral->read(handle->scsi_periph_handle, vecs, pos,
602 		num_blocks, block_size, bytes_transferred, 10);
603 }
604 
605 
606 static status_t
607 cd_write(cd_handle_info *handle, const phys_vecs *vecs, off_t pos,
608 	size_t num_blocks, uint32 block_size, size_t *bytes_transferred)
609 {
610 	return sSCSIPeripheral->write(handle->scsi_periph_handle, vecs, pos,
611 		num_blocks, block_size, bytes_transferred, 10);
612 }
613 
614 
615 static status_t
616 cd_ioctl(cd_handle_info *handle, int op, void *buffer, size_t length)
617 {
618 	cd_device_info *device = handle->device;
619 
620 	TRACE("ioctl(op = %d)\n", op);
621 
622 	switch (op) {
623 		case B_GET_DEVICE_SIZE:
624 		{
625 			status_t status = update_capacity(device);
626 			if (status != B_OK)
627 				return status;
628 
629 			size_t size = device->capacity * device->block_size;
630 			return user_memcpy(buffer, &size, sizeof(size_t));
631 		}
632 
633 		case B_GET_GEOMETRY:
634 		{
635 			if (buffer == NULL /*|| length != sizeof(device_geometry)*/)
636 				return B_BAD_VALUE;
637 
638 		 	device_geometry geometry;
639 			status_t status = get_geometry(handle, &geometry);
640 			if (status != B_OK)
641 				return status;
642 
643 			return user_memcpy(buffer, &geometry, sizeof(device_geometry));
644 		}
645 
646 		case B_GET_ICON:
647 			return sSCSIPeripheral->get_icon(icon_type_cd,
648 				(device_icon *)buffer);
649 
650 		case B_SCSI_GET_TOC:
651 			// TODO: we pass a user buffer here!
652 			return get_toc(device, (scsi_toc *)buffer);
653 
654 		case B_EJECT_DEVICE:
655 		case B_SCSI_EJECT:
656 			return load_eject(device, false);
657 
658 		case B_LOAD_MEDIA:
659 			return load_eject(device, true);
660 
661 		case B_SCSI_GET_POSITION:
662 		{
663 			if (buffer == NULL)
664 				return B_BAD_VALUE;
665 
666 			scsi_position position;
667 			status_t status = get_position(device, &position);
668 			if (status != B_OK)
669 				return status;
670 
671 			return user_memcpy(buffer, &position, sizeof(scsi_position));
672 		}
673 
674 		case B_SCSI_GET_VOLUME:
675 			// TODO: we pass a user buffer here!
676 			return get_set_volume(device, (scsi_volume *)buffer, false);
677 		case B_SCSI_SET_VOLUME:
678 			// TODO: we pass a user buffer here!
679 			return get_set_volume(device, (scsi_volume *)buffer, true);
680 
681 		case B_SCSI_PLAY_TRACK:
682 		{
683 			scsi_play_track track;
684 			if (user_memcpy(&track, buffer, sizeof(scsi_play_track)) != B_OK)
685 				return B_BAD_ADDRESS;
686 
687 			return play_track_index(device, &track);
688 		}
689 		case B_SCSI_PLAY_POSITION:
690 		{
691 			scsi_play_position position;
692 			if (user_memcpy(&position, buffer, sizeof(scsi_play_position))
693 					!= B_OK)
694 				return B_BAD_ADDRESS;
695 
696 			return play_msf(device, &position);
697 		}
698 
699 		case B_SCSI_STOP_AUDIO:
700 			return stop_audio(device);
701 		case B_SCSI_PAUSE_AUDIO:
702 			return pause_resume(device, false);
703 		case B_SCSI_RESUME_AUDIO:
704 			return pause_resume(device, true);
705 
706 		case B_SCSI_SCAN:
707 		{
708 			scsi_scan scanBuffer;
709 			if (user_memcpy(&scanBuffer, buffer, sizeof(scsi_scan)) != B_OK)
710 				return B_BAD_ADDRESS;
711 
712 			return scan(device, &scanBuffer);
713 		}
714 		case B_SCSI_READ_CD:
715 			// TODO: we pass a user buffer here!
716 			return read_cd(device, (scsi_read_cd *)buffer);
717 
718 		default:
719 			return sSCSIPeripheral->ioctl(handle->scsi_periph_handle, op,
720 				buffer, length);
721 	}
722 }
723 
724 
725 //	#pragma mark - scsi_periph callbacks
726 
727 
728 static void
729 cd_set_capacity(cd_device_info *device, uint64 capacity, uint32 blockSize)
730 {
731 	TRACE("das_set_capacity(device = %p, capacity = %Ld, blockSize = %ld)\n",
732 		device, capacity, blockSize);
733 
734 	// get log2, if possible
735 	uint32 blockShift = log2(blockSize);
736 
737 	if ((1UL << blockShift) != blockSize)
738 		blockShift = 0;
739 
740 	device->capacity = capacity;
741 	device->block_size = blockSize;
742 
743 	sBlockIO->set_media_params(device->block_io_device, blockSize,
744 		blockShift, capacity);
745 }
746 
747 
748 static void
749 cd_media_changed(cd_device_info *device, scsi_ccb *request)
750 {
751 	// do a capacity check
752 	// TBD: is this a good idea (e.g. if this is an empty CD)?
753 	sSCSIPeripheral->check_capacity(device->scsi_periph_device, request);
754 }
755 
756 
757 scsi_periph_callbacks callbacks = {
758 	(void (*)(periph_device_cookie, uint64, uint32))cd_set_capacity,
759 	(void (*)(periph_device_cookie, scsi_ccb *))cd_media_changed
760 };
761 
762 
763 //	#pragma mark - driver module API
764 
765 
766 static float
767 cd_supports_device(device_node *parent)
768 {
769 	const char *bus;
770 	uint8 deviceType;
771 
772 	// make sure parent is really the SCSI bus manager
773 	if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
774 		return -1;
775 
776 	if (strcmp(bus, "scsi"))
777 		return 0.0;
778 
779 	// check whether it's really a CD-ROM or WORM
780 	if (sDeviceManager->get_attr_uint8(parent, SCSI_DEVICE_TYPE_ITEM,
781 			&deviceType, true) != B_OK
782 		|| (deviceType != scsi_dev_CDROM && deviceType != scsi_dev_WORM))
783 		return 0.0;
784 
785 	return 0.6;
786 }
787 
788 
789 /*!	Called whenever a new device was added to system;
790 	if we really support it, we create a new node that gets
791 	server by the block_io module
792 */
793 static status_t
794 cd_register_device(device_node *node)
795 {
796 	const scsi_res_inquiry *deviceInquiry = NULL;
797 	size_t inquiryLength;
798 	uint32 maxBlocks;
799 
800 	// get inquiry data
801 	if (sDeviceManager->get_attr_raw(node, SCSI_DEVICE_INQUIRY_ITEM,
802 			(const void **)&deviceInquiry, &inquiryLength, true) != B_OK
803 		|| inquiryLength < sizeof(deviceInquiry))
804 		return B_ERROR;
805 
806 	// get block limit of underlying hardware to lower it (if necessary)
807 	if (sDeviceManager->get_attr_uint32(node, B_BLOCK_DEVICE_MAX_BLOCKS_ITEM,
808 			&maxBlocks, true) != B_OK)
809 		maxBlocks = INT_MAX;
810 
811 	// using 10 byte commands, at most 0xffff blocks can be transmitted at once
812 	// (sadly, we cannot update this value later on if only 6 byte commands
813 	//  are supported, but the block_io module can live with that)
814 	maxBlocks = min_c(maxBlocks, 0xffff);
815 
816 	// ready to register
817 	device_attr attrs[] = {
818 		// tell block_io whether the device is removable
819 		{"removable", B_UINT8_TYPE, {ui8: deviceInquiry->removable_medium}},
820 		// impose own max block restriction
821 		{B_BLOCK_DEVICE_MAX_BLOCKS_ITEM, B_UINT32_TYPE, {ui32: maxBlocks}},
822 		{ NULL }
823 	};
824 
825 	return sDeviceManager->register_node(node, SCSI_CD_MODULE_NAME, attrs,
826 		NULL, NULL);
827 }
828 
829 
830 static status_t
831 cd_init_driver(device_node *node, void **cookie)
832 {
833 	cd_device_info *device;
834 	status_t status;
835 	uint8 removable;
836 
837 	TRACE("cd_init_driver");
838 
839 	status = sDeviceManager->get_attr_uint8(node, "removable",
840 		&removable, false);
841 	if (status != B_OK)
842 		return status;
843 
844 	device = (cd_device_info *)malloc(sizeof(*device));
845 	if (device == NULL)
846 		return B_NO_MEMORY;
847 
848 	memset(device, 0, sizeof(*device));
849 
850 	device->node = node;
851 	device->removable = removable;
852 
853 	// set capacity to zero, so it get checked on first opened handle
854 	device->capacity = 0;
855 	device->block_size = 0;
856 
857 	sDeviceManager->get_attr_uint8(node, SCSI_DEVICE_TYPE_ITEM,
858 		&device->device_type, true);
859 
860 	device_node *parent = sDeviceManager->get_parent_node(node);
861 	sDeviceManager->get_driver(parent, (driver_module_info **)&device->scsi,
862 		(void **)&device->scsi_device);
863 	sDeviceManager->put_node(parent);
864 
865 	status = sSCSIPeripheral->register_device((periph_device_cookie)device,
866 		&callbacks, device->scsi_device, device->scsi, device->node,
867 		device->removable, &device->scsi_periph_device);
868 	if (status != B_OK) {
869 		free(device);
870 		return status;
871 	}
872 
873 	*cookie = device;
874 	return B_OK;
875 }
876 
877 
878 static void
879 cd_uninit_driver(void *_cookie)
880 {
881 	cd_device_info *device = (cd_device_info *)_cookie;
882 
883 	sSCSIPeripheral->unregister_device(device->scsi_periph_device);
884 	free(device);
885 }
886 
887 
888 static status_t
889 cd_register_child_devices(void *_cookie)
890 {
891 	cd_device_info *device = (cd_device_info *)_cookie;
892 	status_t status;
893 	char *name;
894 
895 	name = sSCSIPeripheral->compose_device_name(device->node, "disk/scsi");
896 	if (name == NULL)
897 		return B_ERROR;
898 
899 	status = sDeviceManager->publish_device(device->node, name,
900 		B_BLOCK_IO_DEVICE_MODULE_NAME);
901 
902 	free(name);
903 	return status;
904 }
905 
906 
907 module_dependency module_dependencies[] = {
908 	{SCSI_PERIPH_MODULE_NAME, (module_info **)&sSCSIPeripheral},
909 	{B_BLOCK_IO_FOR_DRIVER_MODULE_NAME, (module_info **)&sBlockIO},
910 	{B_DEVICE_MANAGER_MODULE_NAME, (module_info **)&sDeviceManager},
911 	{}
912 };
913 
914 block_device_interface sSCSICDModule = {
915 	{
916 		{
917 			SCSI_CD_MODULE_NAME,
918 			0,
919 			NULL
920 		},
921 
922 		cd_supports_device,
923 		cd_register_device,
924 		cd_init_driver,
925 		cd_uninit_driver,
926 		cd_register_child_devices,
927 		NULL,	// rescan
928 		NULL,	// removed
929 	},
930 
931 	(void (*)(block_device_cookie *, block_io_device))	&cd_set_device,
932 	(status_t (*)(block_device_cookie *, block_device_handle_cookie **))&cd_open,
933 	(status_t (*)(block_device_handle_cookie *))		&cd_close,
934 	(status_t (*)(block_device_handle_cookie *))		&cd_free,
935 
936 	(status_t (*)(block_device_handle_cookie *, const phys_vecs *,
937 		off_t, size_t, uint32, size_t *))				&cd_read,
938 	(status_t (*)(block_device_handle_cookie *, const phys_vecs *,
939 		off_t, size_t, uint32, size_t *))				&cd_write,
940 
941 	(status_t (*)(block_device_handle_cookie *, int, void *, size_t))&cd_ioctl,
942 };
943 
944 module_info *modules[] = {
945 	(module_info *)&sSCSICDModule,
946 	NULL
947 };
948