#include "StdAfx.h"
#include "Defines.h"
#include "HDD.h"

CHDD::CHDD(const CString &name, BOOL Slave)
{
	attach_hdd(name, Slave);
	int_init();
}

CHDD::CHDD()
{
	attach_hdd(_T(""), FALSE);
	int_init();
}

CHDD::~CHDD(void)
{
	//если в буфере есть данные нужно сделать flush buffer;
	write_file();
	SAFE_DELETE_ARRAY(m_SectorBuffer_Byte);
	detach_hdd();
}

void CHDD::int_init()
{
	m_nDataReg_1f0 = 0;
	m_nErrorReg_1f1 = 0;
	m_nFeaturesReg_1f1 = 0;
	m_nSectorCountReg_1f2 = 0;
	m_nSectorNumberReg_1f3 = 0;
	m_nCylNumberLowReg_1f4 = 0;
	m_nCylNumberHiReg_1f5 = 0;
	m_nDrvHeadReg_1f6 = 0;
	m_nStatusReg_1f7 = 0;
	m_nCommandReg_1f7 = 0;
	m_nAltStatusReg_3f6 = 0;
	m_nControlReg_3f6 = 0;
	m_nDriveAddrReg_3f7 = 0;
	m_SectorBuffer_Byte = new BYTE[SECTOR_SIZE];
	m_SectorBuffer_Word = reinterpret_cast<WORD *>(m_SectorBuffer_Byte);
	ZeroMemory(m_SectorBuffer_Byte, SECTOR_SIZE);
	m_bChangeBuffer = FALSE;
}

BOOL CHDD::attach_hdd(const CString &name, BOOL Slave)
{
	detach_hdd();
	m_strImageFileName = name;
	m_dwMaster_Slave = Slave ? 0x10 : 0;

	if (open_file())
	{
		return configure();
	}

	return FALSE;
}

void CHDD::detach_hdd()
{
	if (m_fHDDImageFile.m_hFile != CFile::hFileNull)
	{
		m_fHDDImageFile.Close();
		m_strImageFileName = _T("");
	}
}

BOOL CHDD::open_file()
{
	BOOL bRet;

	if (!m_strImageFileName.IsEmpty())
	{
		if (bRet = m_fHDDImageFile.Open(m_strImageFileName, CFile::modeReadWrite | CFile::shareDenyWrite))
		{
			m_bReadOnly = FALSE;
		}
		else if (bRet = m_fHDDImageFile.Open(m_strImageFileName, CFile::modeRead | CFile::shareDenyWrite))
		{
			m_bReadOnly = TRUE;
		}
		else
		{
			//вообще никак не удалось открыть файл
			m_bReadOnly = FALSE;
			return FALSE;
		}

		return TRUE;
	}

	return FALSE;
}

BOOL CHDD::configure()
{
	m_nPhase = HDD_IDE_PHASE_FAILURE;

	if (m_fHDDImageFile.m_hFile != CFile::hFileNull)
	{
		/* перемещаемся к началу файла */
		if (m_fHDDImageFile.Seek(0, CFile::begin) != 0)
		{
			return FALSE;
		}

		/* читаем служебный сектор */
		if (m_fHDDImageFile.Read(m_SectorBuffer_Byte, SECTOR_SIZE) != SECTOR_SIZE)
		{
			return FALSE;    /* read error */
		}

		// проверим данные на валидность
		BYTE cs = 0;

		for (int i = 0; i < 511; i++)
		{
			cs += m_SectorBuffer_Byte[i];
		}

		cs = -cs;

		if (m_SectorBuffer_Byte[511] == cs && m_SectorBuffer_Byte[510] == 0xA5)
		{
			m_nCylinders = m_SectorBuffer_Word[1];
			m_nHeads = m_SectorBuffer_Word[3];
			m_nSectors = m_SectorBuffer_Word[6];
			command_ok();
			return TRUE;
		}
	}

	return FALSE;
}

void CHDD::command_ok()
{
	m_nPhase = HDD_IDE_PHASE_READY;
	m_nSectorPos_Word = -1;
	m_nErrorReg_1f1 = 0;
	m_nStatusReg_1f7 = STATUS_DRDY | STATUS_DSC;
}

void CHDD::write_regs(HDD_REGISTERS reg, WORD data)
{
	switch (reg)
	{
		case REG_1F0:
			if ((m_nDrvHeadReg_1f6 ^ m_dwMaster_Slave) & 0x10)
			{
				return;
			}

			if (m_nStatusReg_1f7 & STATUS_DRQ)
			{
				write_data(data);
			}

			break;

		case REG_1F1:
			if ((m_nStatusReg_1f7 & (STATUS_BSY | STATUS_DRQ)) == 0)
			{
				m_nFeaturesReg_1f1 = LOBYTE(data);
			}

			break;

		case REG_1F2:
			if ((m_nStatusReg_1f7 & (STATUS_BSY | STATUS_DRQ)) == 0)
			{
				m_nSectorCountReg_1f2 = LOBYTE(data);
			}

			break;

		case REG_1F3:
			if ((m_nStatusReg_1f7 & (STATUS_BSY | STATUS_DRQ)) == 0)
			{
				m_nSectorNumberReg_1f3 = LOBYTE(data);
			}

			break;

		case REG_1F4:
			if ((m_nStatusReg_1f7 & (STATUS_BSY | STATUS_DRQ)) == 0)
			{
				m_nCylNumberLowReg_1f4 = LOBYTE(data);
			}

			break;

		case REG_1F5:
			if ((m_nStatusReg_1f7 & (STATUS_BSY | STATUS_DRQ)) == 0)
			{
				m_nCylNumberHiReg_1f5 = LOBYTE(data);
			}

			break;

		case REG_1F6:
			if ((m_nStatusReg_1f7 & (STATUS_BSY | STATUS_DRQ)) == 0)
			{
				m_nDrvHeadReg_1f6 = LOBYTE(data);
				BYTE hs = (m_nDrvHeadReg_1f6 & HDD_IDE_HEAD_HEAD) << 2;
				m_nDriveAddrReg_3f7 &= ~HDD_DRVADDR_HS;
				m_nDriveAddrReg_3f7 |= hs;

				if (m_nDrvHeadReg_1f6 & HDD_IDE_HEAD_DEV)
				{
					m_nDriveAddrReg_3f7 |= HDD_DRVADDR_DS1;
					m_nDriveAddrReg_3f7 &= ~HDD_DRVADDR_DS0;
				}
				else
				{
					m_nDriveAddrReg_3f7 |= HDD_DRVADDR_DS0;
					m_nDriveAddrReg_3f7 &= ~HDD_DRVADDR_DS1;
				}
			}

			break;

		case REG_1F7:
			if ((m_nDrvHeadReg_1f6 ^ m_dwMaster_Slave) & 0x10 && data != 0x90)
			{
				return;
			}

			if ((m_nStatusReg_1f7 & (STATUS_BSY | STATUS_DRQ)) == 0)
			{
				m_nCommandReg_1f7 = LOBYTE(data);
				execute_command((HDD_IDE_COMMAND)m_nCommandReg_1f7);
			}

			break;

		case REG_3F6:
			m_nControlReg_3f6 = LOBYTE(data);

			if (m_nControlReg_3f6 & CONTROL_SRST)
			{
				reset(FALSE);
			}

			break;

		case REG_3F7:
			//не используется
			break;
			//запись в остальные значения надо просто игнорировать
	}
}

void CHDD::write_data(WORD data)
{
	/* Data register can only be written in PIO output phase */
	if (m_nPhase != HDD_IDE_PHASE_PIO_OUT)
	{
		return;
	}

	m_nStatusReg_1f7 |= STATUS_BSY;
	m_nDataReg_1f0 = data;
	m_SectorBuffer_Word[m_nSectorPos_Word++] = data; //пишем данные в буфер
	m_bChangeBuffer = TRUE;

	if (m_nSectorPos_Word >= SECTOR_SIZE / 2) //если пришла пора сохрянять буфер
	{
		m_nDriveAddrReg_3f7 |= HDD_DRVADDR_WG;

		if (!write_file())
		{
			m_nStatusReg_1f7 |= STATUS_ERR | STATUS_DWF;
			m_nErrorReg_1f1 = ERR_ABRT | ERR_AMNF;
		}

		if (m_nSectorCountReg_1f2)
		{
			writesector();
		}
		else
		{
			/* all sectors done */
			command_ok();
		}

		m_nDriveAddrReg_3f7 &= ~HDD_DRVADDR_WG;
	}

	m_nStatusReg_1f7 &= ~STATUS_BSY;
}

/* Execute the WRITE SECTOR command */
void CHDD::writesector()
{
	if (!seek())
	{
		m_nStatusReg_1f7 |= STATUS_ERR;
		m_nErrorReg_1f1 = ERR_ABRT | ERR_IDNF;
		return;
	}

	/* Initiate the PIO output phase */
	m_nPhase = HDD_IDE_PHASE_PIO_OUT;
	m_nStatusReg_1f7 |= STATUS_DRQ;
	m_nErrorReg_1f1 = 0;
	m_nSectorPos_Word = 0;
}

BOOL CHDD::write_file()
{
	if (!m_bChangeBuffer) //если буфер не изменялся, то и записывать нечего.
	{
		return TRUE;
	}

	long sector_position = DATA_OFFSET + (SECTOR_SIZE * m_nSectorNumber);

	/* перемещаемся к нужной позиции */
	if (m_fHDDImageFile.Seek(sector_position, CFile::begin) != sector_position)
	{
		return FALSE;
	}

	/* Сохраняем сектор */
	try
	{
		m_fHDDImageFile.Write(m_SectorBuffer_Byte, SECTOR_SIZE);
	}
	catch (...) //единственный способ узнать, произошла ли ошибка при записи
	{
		/* write error */
		return FALSE;
	}

	m_bChangeBuffer = FALSE;
	return TRUE;
}

WORD CHDD::read_regs(HDD_REGISTERS reg)
{
	WORD dret = 0xffff;

	if ((m_nDrvHeadReg_1f6 ^ m_dwMaster_Slave) & 0x10)
	{
		return dret;
	}

	switch (reg)
	{
		case REG_1F0:
			if (m_nStatusReg_1f7 & STATUS_DRQ)
			{
				return read_data();
			}

			break;

		case REG_1F1:
			//if (((m_nStatusReg_1f7 & (STATUS_BSY | STATUS_DRQ)) == 0) && (m_nStatusReg_1f7 & STATUS_ERR)) //будем выдавать всегда.
			return (WORD)m_nErrorReg_1f1;
			break;

		case REG_1F2:
			if ((m_nStatusReg_1f7 & STATUS_BSY) == 0)
			{
				return (WORD)m_nSectorCountReg_1f2;
			}

			break;

		case REG_1F3:
			if ((m_nStatusReg_1f7 & STATUS_BSY) == 0)
			{
				return (WORD)m_nSectorNumberReg_1f3;
			}

			break;

		case REG_1F4:
			if ((m_nStatusReg_1f7 & STATUS_BSY) == 0)
			{
				return (WORD)m_nCylNumberLowReg_1f4;
			}

			break;

		case REG_1F5:
			if ((m_nStatusReg_1f7 & STATUS_BSY) == 0)
			{
				return (WORD)m_nCylNumberHiReg_1f5;
			}

			break;

		case REG_1F6:
			if ((m_nStatusReg_1f7 & STATUS_BSY) == 0)
			{
				return (WORD)m_nDrvHeadReg_1f6;
			}

			break;

		case REG_1F7:
			return (WORD)m_nStatusReg_1f7;

		case REG_3F6:
			return (WORD)m_nStatusReg_1f7;

		case REG_3F7:
			if ((m_nStatusReg_1f7 & STATUS_BSY) == 0)
			{
				return (WORD)m_nDriveAddrReg_3f7;
			}

			break;
	}

	return dret;
}

WORD CHDD::read_data()
{
	if (m_nPhase != HDD_IDE_PHASE_PIO_IN)
	{
		return 0xffff;
	}

	m_nStatusReg_1f7 |= STATUS_BSY;
	m_nDataReg_1f0 = m_SectorBuffer_Word[m_nSectorPos_Word++];

	if (m_nSectorPos_Word >= SECTOR_SIZE / 2)
	{
		if (m_nSectorCountReg_1f2)
		{
			/* more sectors to read */
			readsector();
		}
		else
		{
			/* all sectors done */
			command_ok();
		}
	}

	m_nStatusReg_1f7 &= ~STATUS_BSY;
	return m_nDataReg_1f0;
}

/* Execute the READ SECTOR command */
void CHDD::readsector()
{
	if (!seek())
	{
		m_nStatusReg_1f7 |= STATUS_ERR;
		m_nErrorReg_1f1 = ERR_ABRT | ERR_IDNF;
		return;
	}

	/* Read data from disk */
	if (!read_file())
	{
		m_nStatusReg_1f7 |= STATUS_ERR;
		m_nErrorReg_1f1 = ERR_ABRT | ERR_AMNF | ERR_UNC;
	}
	else
	{
		/* Initiate the PIO input phase */
		m_nPhase = HDD_IDE_PHASE_PIO_IN;
		m_nStatusReg_1f7 |= STATUS_DRQ;
		m_nErrorReg_1f1 = 0;
		m_nSectorPos_Word = 0;
	}
}

BOOL CHDD::read_file()
{
	long sector_position = DATA_OFFSET + (SECTOR_SIZE * m_nSectorNumber);

	/* Seek to the correct file position */
	if (m_fHDDImageFile.Seek(sector_position, CFile::begin) != sector_position)
	{
		return FALSE;
	}

	/* Read the packed data into a temporary buffer */
	if (m_fHDDImageFile.Read(m_SectorBuffer_Byte, SECTOR_SIZE) != SECTOR_SIZE)
	{
		return FALSE;    /* read error */
	}

	m_bChangeBuffer = FALSE;
	return TRUE;
}

/* Seek to the addressed sector */
BOOL CHDD::seek()
{
	int sectornumber;
	int next_head;

	/* Calculate sector number, depending upon LBA/CHS mode. */
	if (m_nDrvHeadReg_1f6 & HDD_IDE_HEAD_LBA)
	{
		sectornumber = (m_nDrvHeadReg_1f6 & HDD_IDE_HEAD_HEAD << 24) +
		               (m_nCylNumberHiReg_1f5 << 16) +
		               (m_nCylNumberLowReg_1f4 << 8) +
		               (m_nSectorNumberReg_1f3);
	}
	else
	{
		int cylinder = ((int)m_nCylNumberHiReg_1f5 << 8) | (m_nCylNumberLowReg_1f4);
		int head = (m_nDrvHeadReg_1f6 & HDD_IDE_HEAD_HEAD);
		int sector = (m_nSectorNumberReg_1f3 - 1);

		if (cylinder >= m_nCylinders || head >= m_nHeads ||
		        sector < 0 || sector >= m_nSectors)
		{
			sectornumber = -1;
		}
		else
		{
			sectornumber = (((cylinder * m_nHeads) + head) * m_nSectors) + sector;
		}
	}

	/* Seek to the correct position */
	if (sectornumber < 0 ||
	        sectornumber >= (m_nCylinders * m_nHeads * m_nSectors))
	{
		m_nStatusReg_1f7 |= STATUS_ERR;
		m_nErrorReg_1f1 = ERR_ABRT | ERR_IDNF;
		return FALSE;
	}

	m_nSectorNumber = sectornumber;

	/* advance registers to next sector, for multiple sector accesses */
	if (--m_nSectorCountReg_1f2)
	{
		if (m_nDrvHeadReg_1f6 & HDD_IDE_HEAD_LBA)
		{
			/* increment using LBA scheme */
			m_nSectorNumberReg_1f3 = (m_nSectorNumberReg_1f3 + 1) & 0xff;

			if (!m_nSectorNumberReg_1f3)
			{
				m_nCylNumberLowReg_1f4 = (m_nCylNumberLowReg_1f4 + 1) & 0xff;

				if (!m_nCylNumberLowReg_1f4)
				{
					m_nCylNumberHiReg_1f5 = (m_nCylNumberHiReg_1f5 + 1) & 0xff;

					if (!m_nCylNumberHiReg_1f5)
					{
						next_head = ((m_nDrvHeadReg_1f6 & HDD_IDE_HEAD_HEAD) + 1) & HDD_IDE_HEAD_HEAD;
						m_nDrvHeadReg_1f6 = (m_nDrvHeadReg_1f6 & ~HDD_IDE_HEAD_HEAD) | next_head;
					}
				}
			}
		}
		else
		{
			/* increment using CHS scheme */
			m_nSectorNumberReg_1f3 = (m_nSectorNumberReg_1f3 % m_nSectors) + 1;

			/* NB sector number is 1-based */
			if (m_nSectorNumberReg_1f3 == 1)
			{
				next_head = ((m_nDrvHeadReg_1f6 & HDD_IDE_HEAD_HEAD) + 1) % m_nHeads;
				m_nDrvHeadReg_1f6 = (m_nDrvHeadReg_1f6 & ~HDD_IDE_HEAD_HEAD) | next_head;

				if (!next_head)
				{
					m_nCylNumberLowReg_1f4 = (m_nCylNumberLowReg_1f4 + 1) & 0xff;

					if (!m_nCylNumberLowReg_1f4)
					{
						m_nCylNumberHiReg_1f5++;
					}
				}
			}
		}
	}

	m_nStatusReg_1f7 |= STATUS_DSC;
	return TRUE;
}

void CHDD::execute_command(HDD_IDE_COMMAND cmd)
{
	m_nStatusReg_1f7 = 0;

	switch (cmd)
	{
		case HDD_IDE_COMMAND_NOP:
		case HDD_IDE_COMMAND_IDLE:
		case HDD_IDE_COMMAND_IDLE_IMMEDIATE:
		case HDD_IDE_COMMAND_RECALIBRATE:
		case HDD_IDE_COMMAND_SEEK:
		case HDD_IDE_SLEEP:
		case HDD_IDE_STANDBY:
		case HDD_IDE_STANBY_IMMEDIATE:
		case HDD_IDE_COMMAND_READ_VERIFY_SECTOR_RETRY:
		case HDD_IDE_COMMAND_READ_VERIFY_SECTOR_NORETRY:
			command_ok();
			return;

		case HDD_IDE_COMMAND_FORMAT_TRACK:
			command_ok();
			m_nStatusReg_1f7 |= STATUS_DRQ;
			return;

		case HDD_IDE_COMMAND_EXECUTE_DEVICE_DIAGNOSTICS:
			reset_signature(FALSE);
			return;

		case HDD_IDE_COMMAND_READ_SECTOR_RETRY:
		case HDD_IDE_COMMAND_READ_SECTOR_NORETRY:
			readsector();
			return;

//пока непонятно как считать ecc и как принимать
//  case HDD_IDE_COMMAND_READ_LONG_SECTOR_RETRY:
//  case HDD_IDE_COMMAND_READ_LONG_SECTOR_NORETRY:
//      return;
		case HDD_IDE_COMMAND_WRITE_SECTOR_RETRY:
		case HDD_IDE_COMMAND_WRITE_SECTOR_NORETRY:
		case HDD_IDE_COMMAND_WRITE_VERIFY_SECTOR:
			if (m_bReadOnly)
			{
				m_nStatusReg_1f7 = STATUS_DWF;
				break;
			}
			else
			{
				writesector();
				return;
			}

//пока непонятно как считать ecc и как передавать
//  case HDD_IDE_COMMAND_WRITE_LONG_SECTOR_RETRY:
//  case HDD_IDE_COMMAND_WRITE_LONG_SECTOR_NORETRY:
//      return;
//ещё не реализовано.
//  case HDD_IDE_COMMAND_WRITE_SAME:
//  case HDD_IDE_COMMAND_READ_BUFFER:
//  case HDD_IDE_COMMAND_WRITE_BUFFER:
//  case HDD_IDE_COMMAND_INITIALIZE_DEVICE_PARAMETERS:
		case HDD_IDE_COMMAND_IDENTIFY_DEVICE:
			identifydevice();
			return;

		default:
			TRACE("HDD: Unknown command %02x\n", cmd);
	}

	m_nStatusReg_1f7 |= STATUS_DSC | STATUS_DRDY | STATUS_ERR;
	m_nErrorReg_1f1 = ERR_ABRT;
	m_nPhase = HDD_IDE_PHASE_READY;
}

BOOL CHDD::identifydevice()
{
	if (m_fHDDImageFile.m_hFile != CFile::hFileNull)
	{
		/* перемещаемся к началу файла */
		if (m_fHDDImageFile.Seek(0, CFile::begin) == 0)
		{
			/* читаем служебный сектор */
			if (m_fHDDImageFile.Read(m_SectorBuffer_Byte, SECTOR_SIZE) == SECTOR_SIZE)
			{
				m_nSectorCountReg_1f2 = 0;
				/* Initiate the PIO input phase */
				m_nPhase = HDD_IDE_PHASE_PIO_IN;
				m_nStatusReg_1f7 |= STATUS_DRQ;
				m_nSectorPos_Word = 0;
				return TRUE;
			}
		}
	}

	memset(m_SectorBuffer_Byte, 0, SECTOR_SIZE);
	m_nSectorCountReg_1f2 = 0;
	/* Initiate the PIO input phase */
	m_nPhase = HDD_IDE_PHASE_FAILURE;
	m_nStatusReg_1f7 |= STATUS_ERR;
	m_nErrorReg_1f1 = ERR_ABRT | ERR_UNC;
	m_nSectorPos_Word = 0;
	return FALSE;
}

void CHDD::reset(BOOL reset_hard)
{
	m_nControlReg_3f6 = 0;
	command_ok();
	reset_signature(reset_hard);
}

void CHDD::reset_signature(BOOL reset_hard)
{
	if (m_fHDDImageFile.m_hFile != CFile::hFileNull)
	{
		m_nSectorCountReg_1f2 = m_nSectorNumberReg_1f3 = m_nErrorReg_1f1 = 0x01;
		m_nCylNumberLowReg_1f4 = m_nCylNumberHiReg_1f5 = 0;
		m_nDrvHeadReg_1f6 = 0;
		m_nStatusReg_1f7 = (reset_hard) ? 0 : STATUS_DRDY;
	}
	else
	{
		m_nPhase = HDD_IDE_PHASE_FAILURE;
		m_nDataReg_1f0 = 0xffff;
		m_nErrorReg_1f1 = m_nFeaturesReg_1f1 = m_nSectorCountReg_1f2 = m_nSectorNumberReg_1f3 =
		                                                                   m_nCylNumberLowReg_1f4 = m_nCylNumberHiReg_1f5 = m_nDrvHeadReg_1f6 = m_nStatusReg_1f7 =
		                                                                               m_nCommandReg_1f7 = m_nAltStatusReg_3f6 = m_nControlReg_3f6 = m_nDriveAddrReg_3f7 = 0xff;
	}
}

BOOL CATA_IDE::attach_master(const CString &name)
{
	BOOL bRet;
	bRet = m_mDrive[0].attach_hdd(name, FALSE);
	m_mDrive[0].reset(TRUE);
	return bRet;
}

BOOL CATA_IDE::attach_slave(const CString &name)
{
	BOOL bRet;
	bRet = m_mDrive[1].attach_hdd(name, TRUE);
	m_mDrive[1].reset(TRUE);
	return bRet;
}

void CATA_IDE::reset()
{
	m_mDrive[0].reset(TRUE);
	m_mDrive[1].reset(TRUE);
}

void CATA_IDE::write_regs(HDD_REGISTERS reg, WORD data)
{
	m_mDrive[0].write_regs(reg, data);
	m_mDrive[1].write_regs(reg, data);
}

WORD CATA_IDE::read_regs(HDD_REGISTERS reg)
{
	WORD r0 = m_mDrive[0].read_regs(reg);
	WORD r1 = m_mDrive[1].read_regs(reg);
	WORD r = r0 & r1;
	return r;
}

void CATA_IDE::write_data(WORD data)
{
	m_mDrive[0].write_data(data);
	m_mDrive[1].write_data(data);
}

WORD CATA_IDE::read_data()
{
	return m_mDrive[0].read_data() & m_mDrive[1].read_data();
}

