
/* 2011/06/15		http://forum.osdev.org/viewtopic.php?t=16990 を参考にしました。			By moge32 */
/* 2011/07/25		コードを整理しました。													By moge32 */

#include "bootpack.h"
#include "shutdown.h"

unsigned int *acpiCheckRSDPtr(unsigned int *ptr)
{
	char *sig = "RSD PTR ";
	struct RSDPtr *rsdp = (struct RSDPtr *)ptr;
	unsigned char *bptr;
	unsigned char check = 0;
	int i;

	if (memcmp(sig, rsdp, 8) == 0) {
		bptr = (char *)ptr;
		for (i = 0; i < sizeof(struct RSDPtr); i++, bptr++)
			check += *bptr;
		if (!check)
			return (int *)rsdp->RsdtAddress;
	}

	return NULL;
}

unsigned int *acpiGetRSDPtr(void)
{
	unsigned int *addr;
	unsigned int *rsdp;
	int ebda;

	for (addr = (int *)0xe0000; (int)addr < 0x100000; addr += 0x10 / sizeof(addr)) {
		rsdp = acpiCheckRSDPtr(addr);
		if (rsdp != NULL)
			return rsdp;
	}

	ebda = *((short *)0x040e);
	ebda = ebda * 0x10 & 0xfffff;

	for (addr = (int *)ebda; (int)addr < ebda + 0x400; addr += 0x10 / sizeof(addr)) {
		rsdp = acpiCheckRSDPtr(addr);
		if (rsdp != NULL)
			return rsdp;
	}

	return NULL;
}

int acpiCheckHeader(unsigned int *ptr, char *sig)
{
	char *checkPtr = (char *)ptr;
	int len;
	char check;

	if (memcmp(ptr, sig, 4) == 0) {
		check = 0;
		checkPtr = (char *)ptr;
		len = ptr[1];
		while (0 < len--) {
			check += *checkPtr;
			checkPtr++;
		}
		if (check == 0)
			return 0;
	}
	return -1;
}

int initAcpi(void)
{
	unsigned int *ptr = acpiGetRSDPtr();
	int entrys, dsdtLength;
	char *S5Addr;

	// アドレスが正しいか確認 (ACPIが使用可能な場合のみ)
	if (ptr != NULL && acpiCheckHeader(ptr, "RSDT") == 0) {
		entrys = (ptr[1] - 36) / 4;
		ptr += 36 / 4;			// ヘッダ情報を読み飛ばす

		while (0 < entrys--) {
			// 目的のテーブルに到達したか？
			if (acpiCheckHeader((int *)*ptr, "FACP") == 0) {
				entrys = -2;
				struct FACP *facp = (struct FACP *) *ptr;

				if (acpiCheckHeader((int *)facp->DSDT, "DSDT") == 0) {
					// DSDT内で\_S5パッケージを探す
					S5Addr = (char *)facp->DSDT + 36;			// ヘッダ読み飛ばし
					dsdtLength = *(facp->DSDT + 1) - 36;

					while (0 < dsdtLength--) {
						if (memcmp(S5Addr, "_S5_", 4) == 0)
							break;
						S5Addr++;
					}
					// \_S5 があるか
					if (dsdtLength > 0) {
						// 有効なAML構造体をチェック
						if ((S5Addr[-1] == 0x08 || (S5Addr[-2] == 0x08 && S5Addr[-1] == '\\')) && S5Addr[4] == 0x12) {
							S5Addr += 5;
							S5Addr += ((*S5Addr & 0xc0) >> 6) + 2;	// PkgLengthのサイズを計算

							if (*S5Addr == 0x0a)
								S5Addr++;			// バイトプレフィックスを読み飛ばす
							SLP_TYPa = *S5Addr << 10;
							S5Addr++;

							if (*S5Addr == 0x0a)
								S5Addr++;			// バイトプレフィックスを読み飛ばす
							SLP_TYPb = *S5Addr << 10;

							SMI_CMD = facp->SMI_CMD;

							ACPI_ENABLE = facp->ACPI_ENABLE;
							ACPI_DISABLE = facp->ACPI_DISABLE;

							PM1a_CNT = facp->PM1a_CNT_BLK;
							PM1b_CNT = facp->PM1b_CNT_BLK;
							
							PM1_CNT_LEN = facp->PM1_CNT_LEN;

							SLP_EN = 1 << 13;
							SCI_EN = 1;

							return 0;
						} else {
							// \_S5の解析エラー
							return 1;
						}
					} else {
						// \_S5が存在しない
						return 2;
					}
				} else {
					// DSDTが無効だった
					return 3;
				}
			}
			ptr++;
		}
		// 有効なFACPが存在しない
		return 4;
	} else {
		// ACPI搭載PCでない
		return 5;
	}

	return -1;
}

void sendCommand(void)
{
	// シャットダウンコマンド送信
	io_out16((int)PM1a_CNT, SLP_TYPa | SLP_EN );
	if (PM1b_CNT != 0)
		io_out16((int)PM1b_CNT, SLP_TYPb | SLP_EN );
	return;
}

int acpiEnable(void)
{
	// ACPI有効化コマンドの送信
	io_out8((int)SMI_CMD, ACPI_ENABLE);
	if (!(io_in16((int)PM1a_CNT) & SCI_EN))		// ACPI有効化失敗
		return 0;
	if (PM1b_CNT) {
		if (!(io_in16((int)PM1b_CNT) & SCI_EN))	// ACPI有効化失敗
			return 0;
	}
	return 1;
}

void acpiPowerOff(void)
{
	// ACPIでシャットダウンが可能な場合、SCI_ENは1に設定される
	if (!SCI_EN) return;

	// ACPIが使えるか
	if (!(io_in16((int)PM1a_CNT) & SCI_EN)) {
		// ACPIが有効化できるか
		if (SMI_CMD && ACPI_ENABLE) {
			if (!acpiEnable())
				return;
			sendCommand();
			return;
		} else {
			return;
		}
	} else {
		sendCommand();
		return;
	}

	return;
}
