/*
 * Copyright 2004-2012, Haiku, Inc. All rights reserved.
 * Copyright 2002-2003, Thomas Kurschel. All rights reserved.
 *
 * Distributed under the terms of the MIT License.
 */
 
 
/*!	Peripheral driver to handle CD-ROM drives. To be more
	precisely, it supports CD-ROM and WORM drives (well -
	I've never _seen_ a WORM driver).
 
	Much work is done by scsi_periph and block_io.
*/
 
 
#include "scsi_cd.h"
 
#include <stdlib.h>
#include <string.h>
 
#include <algorithm>
 
#include <fs/devfs.h>
#include <io_requests.h>
#include <vm/vm_page.h>
 
#include "IOCache.h"
#include "IOSchedulerSimple.h"
 
 
//#define TRACE_CD_DISK
#ifdef TRACE_CD_DISK
#	define TRACE(x...) dprintf("scsi_cd: " x)
#else
#	define TRACE(x...) ;
#endif
 
 
static const uint8 kCDIcon[] = {
	0x6e, 0x63, 0x69, 0x66, 0x05, 0x05, 0x00, 0x02, 0x03, 0x06, 0x05, 0xb8,
	0x12, 0xa5, 0xbe, 0x03, 0xe1, 0x3d, 0xe7, 0x84, 0xb8, 0x02, 0x10, 0x49,
	0xf7, 0x9f, 0x49, 0xed, 0xd8, 0x00, 0xf1, 0xf1, 0xf1, 0x36, 0xd9, 0xdd,
	0xf4, 0x8a, 0x99, 0x96, 0xb9, 0xb4, 0xb8, 0xbe, 0xdb, 0xff, 0xf4, 0xf4,
	0xf4, 0x04, 0xeb, 0xd0, 0x02, 0x00, 0x06, 0x02, 0x3c, 0x92, 0xc0, 0x38,
	0x8f, 0x5f, 0xb8, 0x54, 0x50, 0x3c, 0x57, 0x63, 0x48, 0xd8, 0xdf, 0x48,
	0x89, 0x5b, 0x00, 0x41, 0x37, 0xa9, 0xff, 0xb9, 0xb9, 0xb9, 0x04, 0x01,
	0x7e, 0x04, 0x02, 0x04, 0x3f, 0x2c, 0x4e, 0x2c, 0x30, 0x2c, 0x22, 0x40,
	0x22, 0x34, 0x22, 0x4c, 0x3f, 0x54, 0x30, 0x54, 0x4e, 0x54, 0x5c, 0x40,
	0x5c, 0x4c, 0x5c, 0x34, 0x02, 0x04, 0x3f, 0x3a, 0x43, 0x3a, 0x3b, 0x3a,
	0x39, 0x3e, 0x39, 0x3c, 0x39, 0x40, 0x3f, 0x42, 0x3b, 0x42, 0x43, 0x42,
	0x45, 0x3e, 0x45, 0x40, 0x45, 0x3c, 0x02, 0x04, 0x4b, 0x3e, 0x4b, 0x3a,
	0x4b, 0x42, 0x3f, 0x46, 0x47, 0x46, 0x37, 0x46, 0x33, 0x3e, 0x33, 0x42,
	0x33, 0x3a, 0x3f, 0xbb, 0xf7, 0x37, 0xbb, 0xf7, 0x47, 0xbb, 0xf7, 0x02,
	0x04, 0x40, 0x2a, 0x54, 0x2a, 0x50, 0x2c, 0x5c, 0x40, 0x5c, 0x34, 0x5c,
	0x4c, 0x40, 0x56, 0x50, 0x54, 0x54, 0x56, 0x60, 0x40, 0x60, 0x4c, 0x60,
	0x34, 0x06, 0x0a, 0x04, 0x01, 0x03, 0x00, 0x0a, 0x00, 0x02, 0x00, 0x01,
	0x18, 0x15, 0xff, 0x01, 0x17, 0x84, 0x00, 0x04, 0x0a, 0x00, 0x02, 0x00,
	0x01, 0x18, 0x00, 0x15, 0x01, 0x17, 0x86, 0x00, 0x04, 0x0a, 0x01, 0x02,
	0x00, 0x02, 0x00, 0x0a, 0x02, 0x02, 0x02, 0x01, 0x00, 0x0a, 0x03, 0x01,
	0x02, 0x10, 0x01, 0x17, 0x82, 0x00, 0x04
};
 
 
static scsi_periph_interface *sSCSIPeripheral;
static device_manager_info *sDeviceManager;
 
 
#define SCSI_CD_STD_TIMEOUT 10
 
 
static status_t
update_capacity(cd_driver_info *info)
{
	TRACE("update_capacity()\n");
 
	scsi_ccb *ccb = info->scsi->alloc_ccb(info->scsi_device);
	if (ccb == NULL)
		return B_NO_MEMORY;
 
	status_t status = sSCSIPeripheral->check_capacity(
		info->scsi_periph_device, ccb);
 
	info->scsi->free_ccb(ccb);
 
	return status;
}
 
 
/*!	Iteratively correct the reported capacity by trying to read from the device
	close to its end.
*/
static uint64
test_capacity(cd_driver_info *info)
{
	static const size_t kMaxEntries = 4;
	const uint32 blockSize = info->block_size;
	const size_t kBufferSize = blockSize * 4;
 
	TRACE("test_capacity: read with buffer size %" B_PRIuSIZE ", block size %"
		B_PRIu32", capacity %llu\n", kBufferSize, blockSize,
		info->original_capacity);
 
	info->capacity = info->original_capacity;
 
	size_t numBlocks = B_PAGE_SIZE / blockSize;
	uint64 offset = info->original_capacity;
	if (offset <= numBlocks)
		return B_OK;
 
	offset -= numBlocks;
 
	scsi_ccb *request = info->scsi->alloc_ccb(info->scsi_device);
	if (request == NULL)
		return B_NO_MEMORY;
 
	// Allocate buffer
 
	physical_entry entries[4];
	size_t numEntries = 0;
 
	vm_page_reservation reservation;
	vm_page_reserve_pages(&reservation,
		(kBufferSize - 1 + B_PAGE_SIZE) / B_PAGE_SIZE, VM_PRIORITY_SYSTEM);
 
	for (size_t left = kBufferSize; numEntries < kMaxEntries && left > 0;
			numEntries++) {
		size_t bytes = std::min(left, (size_t)B_PAGE_SIZE);
 
		vm_page* page = vm_page_allocate_page(&reservation,
			PAGE_STATE_WIRED | VM_PAGE_ALLOC_BUSY);
 
		entries[numEntries].address = page->physical_page_number * B_PAGE_SIZE;
		entries[numEntries].size = bytes;;
 
		left -= bytes;
	}
 
	vm_page_unreserve_pages(&reservation);
 
	// Read close to the end of the device to find out its real end
 
	// Only try 1 second before the end (= 75 blocks)
	while (offset > info->original_capacity - 75) {
		size_t bytesTransferred;
		status_t status = sSCSIPeripheral->read_write(info->scsi_periph_device,
			request, offset, numBlocks, entries, numEntries, false,
			&bytesTransferred);
 
		TRACE("test_capacity: read from offset %llu: %s\n", offset,
			strerror(status));
 
		if (status == B_OK || (request->sense[0] & 0x7f) != 0x70)
			break;
 
		switch (request->sense[2]) {
			case SCSIS_KEY_MEDIUM_ERROR:
			case SCSIS_KEY_ILLEGAL_REQUEST:
			case SCSIS_KEY_VOLUME_OVERFLOW:
			{
				// find out the problematic sector
				uint32 errorBlock = (request->sense[3] << 24U)
					| (request->sense[4] << 16U) | (request->sense[5] << 8U)
					| request->sense[6];
				if (errorBlock >= offset)
					info->capacity = errorBlock;
				break;
			}
 
			default:
				break;
		}
 
		if (numBlocks > offset)
			break;
 
		offset -= numBlocks;
	}
 
	info->scsi->free_ccb(request);
 
	for (size_t i = 0; i < numEntries; i++) {
		vm_page_set_state(vm_lookup_page(entries[i].address / B_PAGE_SIZE),
			PAGE_STATE_FREE);
	}
 
	if (info->capacity != info->original_capacity) {
		dprintf("scsi_cd: adjusted capacity from %" B_PRIu64 " to %" B_PRIu64
			" blocks.\n", info->original_capacity, info->capacity);
	}
 
	return B_OK;
}
 
 
static status_t
get_geometry(cd_handle *handle, device_geometry *geometry)
{
	cd_driver_info *info = handle->info;
 
	status_t status = update_capacity(info);
 
	// it seems that Be expects B_GET_GEOMETRY to always succeed unless
	// the medium has been changed; e.g. if we report B_DEV_NO_MEDIA, the
	// info is ignored by the CDPlayer and CDBurner
	if (status == B_DEV_MEDIA_CHANGED)
		return B_DEV_MEDIA_CHANGED;
 
	devfs_compute_geometry_size(geometry, info->capacity, info->block_size);
 
	geometry->device_type = info->device_type;
	geometry->removable = info->removable;
 
	// TBD: for all but CD-ROMs, read mode sense - medium type
	// (bit 7 of block device specific parameter for Optical Memory Block Device)
	// (same for Direct-Access Block Devices)
	// (same for write-once block devices)
	// (same for optical memory block devices)
	geometry->read_only = true;
	geometry->write_once = info->device_type == scsi_dev_WORM;
 
	TRACE("scsi_disk: get_geometry(): %ld, %ld, %ld, %ld, %d, %d, %d, %d\n",
		geometry->bytes_per_sector, geometry->sectors_per_track,
		geometry->cylinder_count, geometry->head_count, geometry->device_type,
		geometry->removable, geometry->read_only, geometry->write_once);
 
	return B_OK;
}
 
 
static status_t
get_toc(cd_driver_info *info, scsi_toc *toc)
{
	scsi_ccb *ccb;
	status_t res;
	scsi_cmd_read_toc *cmd;
	size_t dataLength;
	scsi_toc_general *shortResponse = (scsi_toc_general *)toc->toc_data;
 
	TRACE("get_toc()\n");
 
	ccb = info->scsi->alloc_ccb(info->scsi_device);
	if (ccb == NULL)
		return B_NO_MEMORY;
 
	// first read number of tracks only
	ccb->flags = SCSI_DIR_IN;
 
	cmd = (scsi_cmd_read_toc *)ccb->cdb;
 
	memset(cmd, 0, sizeof(*cmd));
	cmd->opcode = SCSI_OP_READ_TOC;
	cmd->time = 1;
	cmd->format = SCSI_TOC_FORMAT_TOC;
	cmd->track = 1;
	cmd->allocation_length = B_HOST_TO_BENDIAN_INT16(sizeof(scsi_toc_general));
 
	ccb->cdb_length = sizeof(*cmd);
 
	ccb->sort = -1;
	ccb->timeout = SCSI_CD_STD_TIMEOUT;
 
	ccb->data = toc->toc_data;
	ccb->sg_list = NULL;
	ccb->data_length = sizeof(toc->toc_data);
 
	res = sSCSIPeripheral->safe_exec(info->scsi_periph_device, ccb);
	if (res != B_OK)
		goto err;
 
	// then read all track infos
	// (little hint: number of tracks is last - first + 1;
	//  but scsi_toc_toc has already one track, so we get
	//  last - first extra tracks; finally, we want the lead-out as
	//  well, so we add an extra track)
	dataLength = (shortResponse->last - shortResponse->first + 1)
		* sizeof(scsi_toc_track) + sizeof(scsi_toc_toc);
	dataLength = min_c(dataLength, sizeof(toc->toc_data));
 
	TRACE("  tracks: %d - %d, data length %d\n", shortResponse->first,
		shortResponse->last, (int)dataLength);
 
	cmd->allocation_length = B_HOST_TO_BENDIAN_INT16(dataLength);
 
	res = sSCSIPeripheral->safe_exec(info->scsi_periph_device, ccb);
 
err:
	info->scsi->free_ccb(ccb);
 
	return res;
}
 
 
static status_t
load_eject(cd_driver_info *info, bool load)
{
	TRACE("load_eject()\n");
 
	scsi_ccb *ccb = info->scsi->alloc_ccb(info->scsi_device);
	if (ccb == NULL)
		return B_NO_MEMORY;
 
	err_res result = sSCSIPeripheral->send_start_stop(
		info->scsi_periph_device, ccb, load, true);
 
	info->scsi->free_ccb(ccb);
 
	return result.error_code;
}
 
 
static status_t
get_position(cd_driver_info *info, scsi_position *position)
{
	scsi_cmd_read_subchannel cmd;
 
	TRACE("get_position()\n");
 
	memset(&cmd, 0, sizeof(cmd));
	cmd.opcode = SCSI_OP_READ_SUB_CHANNEL;
	cmd.time = 1;
	cmd.subq = 1;
	cmd.parameter_list = scsi_sub_channel_parameter_list_cd_pos;
	cmd.track = 0;
	cmd.allocation_length = B_HOST_TO_BENDIAN_INT16(sizeof(scsi_position));
 
	return sSCSIPeripheral->simple_exec(info->scsi_periph_device,
		&cmd, sizeof(cmd), position, sizeof(*position), SCSI_DIR_IN);
}
 
 
static status_t
get_set_volume(cd_driver_info *info, scsi_volume *volume, bool set)
{
	scsi_cmd_mode_sense_6 cmd;
	scsi_mode_param_header_6 header;
	size_t len;
	void *buffer;
	scsi_modepage_audio	*page;
	status_t res;
 
	TRACE("get_set_volume()\n");
 
	// determine size of block descriptor
	memset(&cmd, 0, sizeof(cmd));
	cmd.opcode = SCSI_OP_MODE_SENSE_6;
	cmd.page_code = SCSI_MODEPAGE_AUDIO;
	cmd.page_control = SCSI_MODE_SENSE_PC_CURRENT;
	cmd.allocation_length = sizeof(header);
 
	memset(&header, -2, sizeof(header));
 
	res = sSCSIPeripheral->simple_exec(info->scsi_periph_device, &cmd,
		sizeof(cmd), &header, sizeof(header), SCSI_DIR_IN);
	if (res != B_OK)
		return res;
 
	TRACE("  block_desc_len=%d", header.block_desc_length);
#if 0
	// ToDo: why this??
	return B_ERROR;
#endif
 
	// retrieve param header, block descriptor and actual codepage
	len = sizeof(header) + header.block_desc_length
		+ sizeof(scsi_modepage_audio);
 
	buffer = malloc(len);
	if (buffer == NULL)
		return B_NO_MEMORY;
 
	memset(buffer, -1, len);
 
	cmd.allocation_length = len;
 
	res = sSCSIPeripheral->simple_exec(info->scsi_periph_device, &cmd,
		sizeof(cmd), buffer, len, SCSI_DIR_IN);
	if (res != B_OK) {
		free(buffer);
		return res;
	}
 
	TRACE("  mode_data_len=%d, block_desc_len=%d",
		((scsi_mode_param_header_6 *)buffer)->mode_data_length,
		((scsi_mode_param_header_6 *)buffer)->block_desc_length);
 
	// find control page and retrieve values
	page = (scsi_modepage_audio *)((char *)buffer + sizeof(header)
		+ header.block_desc_length);
 
	TRACE("  page=%p, codepage=%d", page, page->header.page_code);
 
	if (!set) {
		volume->port0_channel = page->ports[0].channel;
		volume->port0_volume  = page->ports[0].volume;
		volume->port1_channel = page->ports[1].channel;
		volume->port1_volume  = page->ports[1].volume;
		volume->port2_channel = page->ports[2].channel;
		volume->port2_volume  = page->ports[2].volume;
		volume->port3_channel = page->ports[3].channel;
		volume->port3_volume  = page->ports[3].volume;
 
#if 0
		SHOW_FLOW(3, "1: %d - %d", volume->port0_channel, volume->port0_volume);
		SHOW_FLOW(3, "2: %d - %d", volume->port1_channel, volume->port1_volume);
		SHOW_FLOW(3, "3: %d - %d", volume->port2_channel, volume->port2_volume);
		SHOW_FLOW(3, "4: %d - %d", volume->port3_channel, volume->port3_volume);
#endif
		res = B_OK;
	} else {
		scsi_cmd_mode_select_6 cmd;
 
		if (volume->flags & 0x01)
			page->ports[0].channel = volume->port0_channel;
		if (volume->flags & 0x02)
			page->ports[0].volume = volume->port0_volume;
		if (volume->flags & 0x04)
			page->ports[1].channel = volume->port1_channel;
		if (volume->flags & 0x08)
			page->ports[1].volume = volume->port1_volume;
		if (volume->flags & 0x10)
			page->ports[2].channel = volume->port2_channel;
		if (volume->flags & 0x20)
			page->ports[2].volume = volume->port2_volume;
		if (volume->flags & 0x40)
			page->ports[3].channel = volume->port3_channel;
		if (volume->flags & 0x80)
			page->ports[3].volume = volume->port3_volume;
 
		memset(&cmd, 0, sizeof(cmd));
		cmd.opcode = SCSI_OP_MODE_SELECT_6;
		cmd.pf = 1;
		cmd.param_list_length = sizeof(header) + header.block_desc_length
			+ sizeof(*page);
 
		res = sSCSIPeripheral->simple_exec(info->scsi_periph_device,
			&cmd, sizeof(cmd), buffer, len, SCSI_DIR_OUT);
	}
 
	free(buffer);
	return res;
}
 
 
/*!	Play audio cd; time is in MSF */
static status_t
play_msf(cd_driver_info *info, const scsi_play_position *position)
{
	scsi_cmd_play_msf cmd;
 
	TRACE("play_msf(): %d:%d:%d-%d:%d:%d\n", position->start_m,
		position->start_s, position->start_f, position->end_m, position->end_s,
		position->end_f);
 
	memset(&cmd, 0, sizeof(cmd));
 
	cmd.opcode = SCSI_OP_PLAY_MSF;
	cmd.start_minute = position->start_m;
	cmd.start_second = position->start_s;
	cmd.start_frame = position->start_f;
	cmd.end_minute = position->end_m;
	cmd.end_second = position->end_s;
	cmd.end_frame = position->end_f;
 
	return sSCSIPeripheral->simple_exec(info->scsi_periph_device,
		&cmd, sizeof(cmd), NULL, 0, SCSI_DIR_NONE);
}
 
 
/*! Play audio cd; time is in track/index */
static status_t
play_track_index(cd_driver_info *info, const scsi_play_track *buf)
{
	scsi_toc generic_toc;
	scsi_toc_toc *toc;
	status_t res;
	int start_track, end_track;
	scsi_play_position position;
 
	TRACE("play_track_index(): %d-%d\n", buf->start_track, buf->end_track);
 
	// the corresponding command PLAY AUDIO TRACK/INDEX	is deprecated,
	// so we have to simulate it by converting track to time via TOC
	res = get_toc(info, &generic_toc);
	if (res != B_OK)
		return res;
 
	toc = (scsi_toc_toc *)&generic_toc.toc_data[0];
 
	start_track = buf->start_track;
	end_track = buf->end_track;
 
	if (start_track > toc->last_track)
		return B_BAD_INDEX;
 
	if (end_track > toc->last_track)
		end_track = toc->last_track + 1;
 
	if (end_track < toc->last_track + 1)
		++end_track;
 
	start_track -= toc->first_track;
	end_track -= toc->first_track;
 
	if (start_track < 0 || end_track < 0)
		return B_BAD_INDEX;
 
	position.start_m = toc->tracks[start_track].start.time.minute;
	position.start_s = toc->tracks[start_track].start.time.second;
	position.start_f = toc->tracks[start_track].start.time.frame;
 
	position.end_m = toc->tracks[end_track].start.time.minute;
	position.end_s = toc->tracks[end_track].start.time.second;
	position.end_f = toc->tracks[end_track].start.time.frame;
 
	return play_msf(info, &position);
}
 
 
static status_t
stop_audio(cd_driver_info *info)
{
	scsi_cmd_stop_play cmd;
 
	TRACE("stop_audio()\n");
 
	memset( &cmd, 0, sizeof( cmd ));
	cmd.opcode = SCSI_OP_STOP_PLAY;
 
	return sSCSIPeripheral->simple_exec(info->scsi_periph_device,
		&cmd, sizeof(cmd), NULL, 0, SCSI_DIR_NONE);
}
 
 
static status_t
pause_resume(cd_driver_info *info, bool resume)
{
	scsi_cmd_pause_resume cmd;
 
	TRACE("pause_resume()\n");
 
	memset(&cmd, 0, sizeof(cmd));
	cmd.opcode = SCSI_OP_PAUSE_RESUME;
	cmd.resume = resume;
 
	return sSCSIPeripheral->simple_exec(info->scsi_periph_device,
		&cmd, sizeof(cmd), NULL, 0, SCSI_DIR_NONE);
}
 
 
static status_t
scan(cd_driver_info *info, const scsi_scan *buf)
{
	scsi_cmd_scan cmd;
	scsi_position curPos;
	scsi_cd_current_position *cdPos;
 
	TRACE("scan(direction =% d)\n", buf->direction);
 
	status_t res = get_position(info, &curPos);
	if (res != B_OK)
		return res;
 
	cdPos = (scsi_cd_current_position *)((char *)&curPos
		+ sizeof(scsi_subchannel_data_header));
 
	if (buf->direction == 0) {
		scsi_play_position playPos;
 
		// to stop scan, we issue play command with "open end"
		playPos.start_m = cdPos->absolute_address.time.minute;
		playPos.start_s = cdPos->absolute_address.time.second;
		playPos.start_f = cdPos->absolute_address.time.frame;
		playPos.end_m = 99;
		playPos.end_s = 59;
		playPos.end_f = 24;
 
		return play_msf(info, &playPos);
	}
 
	memset(&cmd, 0, sizeof(cmd));
 
	cmd.opcode = SCSI_OP_SCAN;
	cmd.direct = buf->direction < 0;
	cmd.start.time = cdPos->absolute_address.time;
	cmd.type = scsi_scan_msf;
 
	/*
	tmp = (uint8 *)&cmd;
	dprintf("%d %d %d %d %d %d %d %d %d %d %d %d\n",
		tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5],
		tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11]);
	*/
 
	return sSCSIPeripheral->simple_exec(info->scsi_periph_device,
		&cmd, sizeof(cmd), NULL, 0, SCSI_DIR_NONE);
}
 
 
static status_t
read_cd(cd_driver_info *info, const scsi_read_cd *readCD)
{
	scsi_cmd_read_cd *cmd;
	uint32 lba, length;
	scsi_ccb *ccb;
	status_t res;
 
	// we use safe_exec instead of simple_exec as we want to set
	// the sorting order manually (only makes much sense if you grab
	// multiple tracks at once, but we are prepared)
	ccb = info->scsi->alloc_ccb(info->scsi_device);
 
	if (ccb == NULL)
		return B_NO_MEMORY;
 
	cmd = (scsi_cmd_read_cd *)ccb->cdb;
	memset(cmd, 0, sizeof(*cmd));
	cmd->opcode = SCSI_OP_READ_CD;
	cmd->sector_type = 1;
 
	// skip first two seconds, they are lead-in
	lba = (readCD->start_m * 60 + readCD->start_s) * 75 + readCD->start_f
		- 2 * 75;
	length = (readCD->length_m * 60 + readCD->length_s) * 75 + readCD->length_f;
 
	cmd->lba = B_HOST_TO_BENDIAN_INT32(lba);
	cmd->high_length = (length >> 16) & 0xff;
	cmd->mid_length = (length >> 8) & 0xff;
	cmd->low_length = length & 0xff;
 
	cmd->error_field = scsi_read_cd_error_none;
	cmd->edc_ecc = 0;
	cmd->user_data = 1;
	cmd->header_code = scsi_read_cd_header_none;
	cmd->sync = 0;
	cmd->sub_channel_selection = scsi_read_cd_sub_channel_none;
 
	ccb->cdb_length = sizeof(*cmd);
 
	ccb->flags = SCSI_DIR_IN | SCSI_DIS_DISCONNECT;
	ccb->sort = lba;
	// are 10 seconds enough for timeout?
	ccb->timeout = 10;
 
	// TODO: we pass a user buffer here!
	ccb->data = (uint8 *)readCD->buffer;
	ccb->sg_list = NULL;
	ccb->data_length = readCD->buffer_length;
 
	res = sSCSIPeripheral->safe_exec(info->scsi_periph_device, ccb);
 
	info->scsi->free_ccb(ccb);
 
	return res;
}
 
 
static int
log2(uint32 x)
{
	int y;
 
	for (y = 31; y >= 0; --y) {
		if (x == (1UL << y))
			break;
	}
 
	return y;
}
 
 
static status_t
do_io(void* cookie, IOOperation* operation)
{
	cd_driver_info* info = (cd_driver_info*)cookie;
 
	// TODO: this can go away as soon as we pushed the IOOperation to the upper
	// layers - we can then set scsi_periph::io() as callback for the scheduler
	size_t bytesTransferred;
	status_t status = sSCSIPeripheral->io(info->scsi_periph_device, operation,
		&bytesTransferred);
 
	info->io_scheduler->OperationCompleted(operation, status, bytesTransferred);
	return status;
}
 
 
//	#pragma mark - device module API
 
 
static status_t
cd_init_device(void* _info, void** _cookie)
{
	cd_driver_info* info = (cd_driver_info*)_info;
 
	update_capacity(info);
		// Get initial capacity, but ignore the result; we do not care
		// whether or not a media is present
 
	*_cookie = info;
	return B_OK;
}
 
 
static void
cd_uninit_device(void* _cookie)
{
	cd_driver_info* info = (cd_driver_info*)_cookie;
 
	delete info->io_scheduler;
	delete info->dma_resource;
	info->io_scheduler = NULL;
	info->dma_resource = NULL;
}
 
 
static status_t
cd_open(void* _info, const char* path, int openMode, void** _cookie)
{
	cd_driver_info* info = (cd_driver_info*)_info;
 
	cd_handle* handle = (cd_handle*)malloc(sizeof(cd_handle));
	if (handle == NULL)
		return B_NO_MEMORY;
 
	handle->info = info;
 
	status_t status = sSCSIPeripheral->handle_open(info->scsi_periph_device,
		(periph_handle_cookie)handle, &handle->scsi_periph_handle);
	if (status < B_OK) {
		free(handle);
		return status;
	}
 
	*_cookie = handle;
	return B_OK;
}
 
 
static status_t
cd_close(void* cookie)
{
	cd_handle* handle = (cd_handle*)cookie;
	TRACE("close()\n");
 
	sSCSIPeripheral->handle_close(handle->scsi_periph_handle);
	return B_OK;
}
 
 
static status_t
cd_free(void* cookie)
{
	cd_handle* handle = (cd_handle*)cookie;
	TRACE("free()\n");
 
	sSCSIPeripheral->handle_free(handle->scsi_periph_handle);
	free(handle);
	return B_OK;
}
 
 
static status_t
cd_read(void* cookie, off_t pos, void* buffer, size_t* _length)
{
	cd_handle* handle = (cd_handle*)cookie;
	size_t length = *_length;
 
	if (handle->info->capacity == 0)
		return B_DEV_NO_MEDIA;
 
	IORequest request;
	status_t status = request.Init(pos, (addr_t)buffer, length, false, 0);
	if (status != B_OK)
		return status;
 
	if (handle->info->io_scheduler == NULL)
		return B_DEV_NO_MEDIA;
 
	status = handle->info->io_scheduler->ScheduleRequest(&request);
	if (status != B_OK)
		return status;
 
	status = request.Wait(0, 0);
	if (status == B_OK)
		*_length = length;
	else
		dprintf("cd_read(): request.Wait() returned: %s\n", strerror(status));
 
	return status;
}
 
 
static status_t
cd_write(void* cookie, off_t pos, const void* buffer, size_t* _length)
{
	cd_handle* handle = (cd_handle*)cookie;
	size_t length = *_length;
 
	if (handle->info->capacity == 0)
		return B_DEV_NO_MEDIA;
 
	IORequest request;
	status_t status = request.Init(pos, (addr_t)buffer, length, true, 0);
	if (status != B_OK)
		return status;
 
	if (handle->info->io_scheduler == NULL)
		return B_DEV_NO_MEDIA;
 
	status = handle->info->io_scheduler->ScheduleRequest(&request);
	if (status != B_OK)
		return status;
 
	status = request.Wait(0, 0);
	if (status == B_OK)
		*_length = length;
	else
		dprintf("cd_write(): request.Wait() returned: %s\n", strerror(status));
 
	return status;
}
 
 
static status_t
cd_io(void* cookie, io_request* request)
{
	cd_handle* handle = (cd_handle*)cookie;
 
	if (handle->info->capacity == 0) {
		notify_io_request(request, B_DEV_NO_MEDIA);
		return B_DEV_NO_MEDIA;
	}
 
	return handle->info->io_scheduler->ScheduleRequest(request);
}
 
 
static status_t
cd_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
{
	cd_handle* handle = (cd_handle*)cookie;
	cd_driver_info *info = handle->info;
 
	TRACE("ioctl(op = %lu)\n", op);
 
	switch (op) {
		case B_GET_DEVICE_SIZE:
		{
			status_t status = update_capacity(info);
			if (status != B_OK)
				return status;
 
			size_t size = info->capacity * info->block_size;
			return user_memcpy(buffer, &size, sizeof(size_t));
		}
 
		case B_GET_GEOMETRY:
		{
			if (buffer == NULL /*|| length != sizeof(device_geometry)*/)
				return B_BAD_VALUE;
 
		 	device_geometry geometry;
			status_t status = get_geometry(handle, &geometry);
			if (status != B_OK)
				return status;
 
			return user_memcpy(buffer, &geometry, sizeof(device_geometry));
		}
 
		case B_GET_ICON_NAME:
			return user_strlcpy((char*)buffer, "devices/drive-optical",
				B_FILE_NAME_LENGTH);
 
		case B_GET_VECTOR_ICON:
		{
			device_icon iconData;
			if (length != sizeof(device_icon))
				return B_BAD_VALUE;
			if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK)
				return B_BAD_ADDRESS;
 
			if (iconData.icon_size >= (int32)sizeof(kCDIcon)) {
				if (user_memcpy(iconData.icon_data, kCDIcon,
						sizeof(kCDIcon)) != B_OK)
					return B_BAD_ADDRESS;
			}
 
			iconData.icon_size = sizeof(kCDIcon);
			return user_memcpy(buffer, &iconData, sizeof(device_icon));
		}
 
		case B_SCSI_GET_TOC:
			// TODO: we pass a user buffer here!
			return get_toc(info, (scsi_toc *)buffer);
 
		case B_EJECT_DEVICE:
		case B_SCSI_EJECT:
			return load_eject(info, false);
 
		case B_LOAD_MEDIA:
			return load_eject(info, true);
 
		case B_SCSI_GET_POSITION:
		{
			if (buffer == NULL)
				return B_BAD_VALUE;
 
			scsi_position position;
			status_t status = get_position(info, &position);
			if (status != B_OK)
				return status;
 
			return user_memcpy(buffer, &position, sizeof(scsi_position));
		}
 
		case B_SCSI_GET_VOLUME:
			// TODO: we pass a user buffer here!
			return get_set_volume(info, (scsi_volume *)buffer, false);
		case B_SCSI_SET_VOLUME:
			// TODO: we pass a user buffer here!
			return get_set_volume(info, (scsi_volume *)buffer, true);
 
		case B_SCSI_PLAY_TRACK:
		{
			scsi_play_track track;
			if (user_memcpy(&track, buffer, sizeof(scsi_play_track)) != B_OK)
				return B_BAD_ADDRESS;
 
			return play_track_index(info, &track);
		}
		case B_SCSI_PLAY_POSITION:
		{
			scsi_play_position position;
			if (user_memcpy(&position, buffer, sizeof(scsi_play_position))
					!= B_OK)
				return B_BAD_ADDRESS;
 
			return play_msf(info, &position);
		}
 
		case B_SCSI_STOP_AUDIO:
			return stop_audio(info);
		case B_SCSI_PAUSE_AUDIO:
			return pause_resume(info, false);
		case B_SCSI_RESUME_AUDIO:
			return pause_resume(info, true);
 
		case B_SCSI_SCAN:
		{
			scsi_scan scanBuffer;
			if (user_memcpy(&scanBuffer, buffer, sizeof(scsi_scan)) != B_OK)
				return B_BAD_ADDRESS;
 
			return scan(info, &scanBuffer);
		}
		case B_SCSI_READ_CD:
			// TODO: we pass a user buffer here!
			return read_cd(info, (scsi_read_cd *)buffer);
 
		default:
			return sSCSIPeripheral->ioctl(handle->scsi_periph_handle, op,
				buffer, length);
	}
}
 
 
//	#pragma mark - scsi_periph callbacks
 
 
static void
cd_set_capacity(cd_driver_info* info, uint64 capacity, uint32 blockSize)
{
	TRACE("cd_set_capacity(info = %p, capacity = %Ld, blockSize = %ld)\n",
		info, capacity, blockSize);
 
	// get log2, if possible
	uint32 blockShift = log2(blockSize);
 
	if ((1UL << blockShift) != blockSize)
		blockShift = 0;
 
	if (info->block_size != blockSize) {
		if (capacity == 0) {
			// there is obviously no medium in the drive, don't try to update
			// the DMA resource
			return;
		}
 
		if (info->block_size != 0) {
			dprintf("old %" B_PRId32 ", new %" B_PRId32 "\n", info->block_size,
				blockSize);
			panic("updating DMAResource not yet implemented...");
		}
 
		// TODO: we need to replace the DMAResource in our IOScheduler
		status_t status = info->dma_resource->Init(info->node, blockSize, 1024,
			32);
		if (status != B_OK)
			panic("initializing DMAResource failed: %s", strerror(status));
 
		// Allocate the I/O scheduler. If there seems to be sufficient memory
		// we use an IOCache, since that adds caching at the lowest I/O layer
		// and thus dramatically reduces I/O operations and seeks. The
		// disadvantage is that it increases free memory (physical pages)
		// fragmentation, which makes large contiguous allocations more likely
		// to fail.
		size_t freeMemory = vm_page_num_free_pages();
		if (freeMemory > 180 * 1024 * 1024 / B_PAGE_SIZE) {
			info->io_scheduler = new(std::nothrow) IOCache(info->dma_resource,
				1024 * 1024);
		} else {
			dprintf("scsi_cd: Using IOSchedulerSimple instead of IOCache to "
				"avoid memory allocation issues.\n");
			info->io_scheduler = new(std::nothrow) IOSchedulerSimple(
				info->dma_resource);
		}
 
		if (info->io_scheduler == NULL)
			panic("allocating IOScheduler failed.");
 
		// TODO: use whole device name here
		status = info->io_scheduler->Init("scsi");
		if (status != B_OK)
			panic("initializing IOScheduler failed: %s", strerror(status));
 
		info->io_scheduler->SetCallback(do_io, info);
		info->block_size = blockSize;
	}
 
	if (info->original_capacity != capacity && info->io_scheduler != NULL) {
		info->original_capacity = capacity;
 
		// For CDs, it's obviously relatively normal that they report a larger
		// capacity than it can actually address. Therefore we'll manually
		// correct the value here.
		test_capacity(info);
 
		info->io_scheduler->SetDeviceCapacity(info->capacity * blockSize);
	}
}
 
 
static void
cd_media_changed(cd_driver_info* info, scsi_ccb* request)
{
	// do a capacity check
	// TODO: is this a good idea (e.g. if this is an empty CD)?
	info->original_capacity = 0;
	info->capacity = 0;
	sSCSIPeripheral->check_capacity(info->scsi_periph_device, request);
 
	if (info->io_scheduler != NULL)
		info->io_scheduler->MediaChanged();
}
 
 
scsi_periph_callbacks callbacks = {
	(void (*)(periph_device_cookie, uint64, uint32))cd_set_capacity,
	(void (*)(periph_device_cookie, scsi_ccb *))cd_media_changed
};
 
 
//	#pragma mark - driver module API
 
 
static float
cd_supports_device(device_node* parent)
{
	const char* bus;
	uint8 deviceType;
 
	// make sure parent is really the SCSI bus manager
	if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
		return -1;
 
	if (strcmp(bus, "scsi"))
		return 0.0;
 
	// check whether it's really a CD-ROM or WORM
	if (sDeviceManager->get_attr_uint8(parent, SCSI_DEVICE_TYPE_ITEM,
			&deviceType, true) != B_OK
		|| (deviceType != scsi_dev_CDROM && deviceType != scsi_dev_WORM))
		return 0.0;
 
	return 0.6;
}
 
 
/*!	Called whenever a new device was added to system;
	if we really support it, we create a new node that gets
	server by the block_io module
*/
static status_t
cd_register_device(device_node* node)
{
	const scsi_res_inquiry* deviceInquiry = NULL;
	size_t inquiryLength;
	uint32 maxBlocks;
 
	// get inquiry data
	if (sDeviceManager->get_attr_raw(node, SCSI_DEVICE_INQUIRY_ITEM,
			(const void**)&deviceInquiry, &inquiryLength, true) != B_OK
		|| inquiryLength < sizeof(deviceInquiry))
		return B_ERROR;
 
	// get block limit of underlying hardware to lower it (if necessary)
	if (sDeviceManager->get_attr_uint32(node, B_DMA_MAX_TRANSFER_BLOCKS,
			&maxBlocks, true) != B_OK)
		maxBlocks = INT_MAX;
 
	// using 10 byte commands, at most 0xffff blocks can be transmitted at once
	// (sadly, we cannot update this value later on if only 6 byte commands
	//  are supported, but the block_io module can live with that)
	maxBlocks = min_c(maxBlocks, 0xffff);
 
	// ready to register
	device_attr attrs[] = {
		{"removable", B_UINT8_TYPE, {ui8: deviceInquiry->removable_medium}},
		{B_DMA_MAX_TRANSFER_BLOCKS, B_UINT32_TYPE, {ui32: maxBlocks}},
		{ NULL }
	};
 
	return sDeviceManager->register_node(node, SCSI_CD_DRIVER_MODULE_NAME,
		attrs, NULL, NULL);
}
 
 
static status_t
cd_init_driver(device_node* node, void** _cookie)
{
	TRACE("cd_init_driver\n");
 
	uint8 removable;
	status_t status = sDeviceManager->get_attr_uint8(node, "removable",
		&removable, false);
	if (status != B_OK)
		return status;
 
	cd_driver_info* info = (cd_driver_info*)malloc(sizeof(cd_driver_info));
	if (info == NULL)
		return B_NO_MEMORY;
 
	memset(info, 0, sizeof(cd_driver_info));
 
	info->dma_resource = new(std::nothrow) DMAResource;
	if (info->dma_resource == NULL) {
		free(info);
		return B_NO_MEMORY;
	}
 
	info->node = node;
	info->removable = removable;
 
	// set capacity to zero, so it get checked on first opened handle
	info->original_capacity = 0;
	info->capacity = 0;
	info->block_size = 0;
 
	sDeviceManager->get_attr_uint8(node, SCSI_DEVICE_TYPE_ITEM,
		&info->device_type, true);
 
	device_node *parent = sDeviceManager->get_parent_node(node);
	sDeviceManager->get_driver(parent, (driver_module_info**)&info->scsi,
		(void**)&info->scsi_device);
	sDeviceManager->put_node(parent);
 
	status = sSCSIPeripheral->register_device((periph_device_cookie)info,
		&callbacks, info->scsi_device, info->scsi, info->node,
		info->removable, 10, &info->scsi_periph_device);
	if (status != B_OK) {
		free(info);
		return status;
	}
 
	*_cookie = info;
	return B_OK;
}
 
 
static void
cd_uninit_driver(void* _cookie)
{
	cd_driver_info* info = (cd_driver_info*)_cookie;
 
	sSCSIPeripheral->unregister_device(info->scsi_periph_device);
	free(info);
}
 
 
static status_t
cd_register_child_devices(void* _cookie)
{
	cd_driver_info* info = (cd_driver_info*)_cookie;
 
	char* name = sSCSIPeripheral->compose_device_name(info->node, "disk/scsi");
	if (name == NULL)
		return B_ERROR;
 
	status_t status = sDeviceManager->publish_device(info->node, name,
		SCSI_CD_DEVICE_MODULE_NAME);
 
	free(name);
	return status;
}
 
 
module_dependency module_dependencies[] = {
	{SCSI_PERIPH_MODULE_NAME, (module_info**)&sSCSIPeripheral},
	{B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager},
	{}
};
 
struct device_module_info sSCSICDDevice = {
	{
		SCSI_CD_DEVICE_MODULE_NAME,
		0,
		NULL
	},
 
	cd_init_device,
	cd_uninit_device,
	NULL,	// remove,
 
	cd_open,
	cd_close,
	cd_free,
	cd_read,
	cd_write,
	cd_io,
	cd_ioctl,
 
	NULL,	// select
	NULL,	// deselect
};
 
struct driver_module_info sSCSICDDriver = {
	{
		SCSI_CD_DRIVER_MODULE_NAME,
		0,
		NULL
	},
 
	cd_supports_device,
	cd_register_device,
	cd_init_driver,
	cd_uninit_driver,
	cd_register_child_devices,
	NULL,	// rescan
	NULL,	// removed
};
 
module_info* modules[] = {
	(module_info*)&sSCSICDDriver,
	(module_info*)&sSCSICDDevice,
	NULL
};

V568 It's odd that 'sizeof()' operator evaluates the size of a pointer to a class, but not the size of the 'deviceInquiry' class object.