/*
 * \brief  ATAPI specific implementation of an Ata::Device
 * \author Sebastian.Sumpf <sebastian.sumpf@genode-labs.com>
 * \date   2010-07-19
 */

/*
 * Copyright (C) 2010-2012 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

/* Genode includes */
#include <base/env.h>
#include <base/printf.h>
#include <util/string.h>

/* local includes */
#include "ata_device.h"
#include "ata_bus_master.h"
#include "endian.h"
#include "mindrvr.h"

static const int verbose = 0;

using namespace Ata;
using namespace Genode;

enum Commands {
	CMD_READ_10         = 0x28,
	CMD_READ_CAPACITY   = 0x25,
	CMD_REQUEST_SENSE   = 0x03,
	CMD_TEST_UNIT_READY = 0x00,
};


Atapi_device::Atapi_device(unsigned base_cmd, unsigned base_ctrl)
  : Device(base_cmd, base_ctrl)
{ }


int Atapi_device::read_sense(unsigned char *sense, int length)
{
		unsigned char cmd_sense[12];
		
		memset(cmd_sense, 0, 12);
		memset(sense, 0, length);

		cmd_sense[0] = CMD_REQUEST_SENSE;
		cmd_sense[4] = length; /* allocation length */
		return reg_packet(dev_num(), 12, cmd_sense, 0, length, sense);
}


bool Atapi_device::test_unit_ready(int level)
{
	unsigned char cmd[12];

	memset(cmd, 0, 12);
	cmd[0] = CMD_TEST_UNIT_READY;

	if (reg_packet(dev_num(), 12, cmd, 0, 0, 0)) {
		unsigned char sense[32];
		read_sense(sense, 32);

		PERR("Sense: key %x sub-key %x", sense[2], sense[12]);
		if (level < 3)
			return test_unit_ready(level + 1);
		else {
			return false;
		}
	}

	return true;
}


void Atapi_device::read_capacity()
{
	unsigned char cmd[12];
	unsigned buffer[2];
	memset(cmd, 0, 12);
	memset(buffer, 0, 8);

	cmd[0] = CMD_READ_CAPACITY;
	
	if (!test_unit_ready())
		throw Io_error();

	if (reg_packet(dev_num(), 12, cmd, 0, 8, (unsigned char *)buffer))
		throw Io_error();

	_block_start = 0;
	_block_end   = bswap32(buffer[0]);
	_block_size  = bswap32(buffer[1]);

	if (verbose)
		PDBG("First block: %u last block %u, block size %u",
		     _block_start, _block_end, _block_size);
}


void Atapi_device::read(unsigned long block_nr, unsigned long count,
                        off_t offset)
{
	unsigned char cmd[12];
	memset(cmd, 0, 12);

	cmd[0] = CMD_READ_10;

	block_nr += _block_start;

	if (block_nr < _block_start || block_nr + count > _block_end + 1)
		throw Io_error();

	/* block address */
	cmd[2] = (block_nr >> 24) & 0xff;
	cmd[3] = (block_nr >> 16) & 0xff;
	cmd[4] = (block_nr >> 8)  & 0xff;
	cmd[5] =  block_nr & 0xff;

	/* transfer length */
	cmd[7] = (count >> 8) & 0xff;
	cmd[8] = count & 0xff;

	if (dma_enabled()) {
		if (verbose)
			PDBG("DMA read: block %lu, count %lu, offset: %08lx, base: %08lx",
			      block_nr, count, offset, _base_addr);

		if (dma_pci_packet(dev_num(), 12, cmd, 0, count * _block_size,
		                   (unsigned char*)(_base_addr + offset)))
			throw Io_error();
	} else {
		if (reg_packet(dev_num(), 12, cmd, 0, count * _block_size,
		               (unsigned char*)(_base_addr + offset)))
			throw Io_error();
	}
}

void Atapi_device::write(unsigned long block_nr, unsigned long count,
                         off_t offset) { throw Io_error(); }
