// SPDX-License-Identifier: MPL-2.0
#include "devs/acpi.h"
#include "memory/memory.h"
#include "kernel/log.h"
static sh_uint8 sh_acpi_internal_checksum(sh_uint8 *ptr,sh_uint64 size) {
  sh_uint8 sum=0;
  for (sh_uint64 i=0;i<size;i++) {
    sum+=ptr[i];
  }
  return sum;
}
SH_STATUS sh_acpi_parse_rsdp(sh_uint64 rsdp,sh_uint8 acpi_ver,sh_apci_ACPI_POINTERS_ROOT *out) {
  if (rsdp==0 || (acpi_ver!=1 && acpi_ver!=2) || out==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 page=rsdp& ~0xFFFULL;
  sh_log_fdebug(SH_LOG_SOURCE_ACPI,"RSDP: 0x%x; RSDP aligned: 0x%x\n",rsdp,page);
  SH_STATUS status=sh_memory_identity_map(page,1,SH_PAGE_PRESENT | SH_PAGE_NX);
  if (sh_status_error(status)) return status;
  sh_acpi_RSDP_V1 *v1=(sh_acpi_RSDP_V1*)rsdp;
  const char sig[8]={'R','S','D',' ','P','T','R',' '};
  for (sh_uint8 i=0;i<8;i++) {
    if (v1->signature[i]!=sig[i]) return SH_STATUS_INVALID_SIGNATURE;
  }
  if (sh_acpi_internal_checksum((sh_uint8*)v1,sizeof(sh_acpi_RSDP_V1))!=0) return SH_STATUS_INVALID_CHECKSUM;
  if (acpi_ver==1 || v1->revision==0) {
    out->rsdt=v1->rsdt_address;
    out->xsdt=0;
    return SH_STATUS_SUCCESS;
  }
  sh_acpi_RSDP_V2 *v2=(sh_acpi_RSDP_V2 *)rsdp;
  if (v2->length<sizeof(sh_acpi_RSDP_V1)) return SH_STATUS_BAD_SIZE;
  if (sh_acpi_internal_checksum((sh_uint8*)v2,v2->length)!=0) return SH_STATUS_INVALID_CHECKSUM;
  if (acpi_ver==1 && v1->revision>=2) return SH_STATUS_INVALID_STATE;
  out->rsdt=v2->first_part.rsdt_address;
  out->xsdt=v2->xsdt_address;
  if (out->xsdt==0) return SH_STATUS_NOT_FOUND;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_acpi_parse_xsdt(sh_uint64 xsdt,sh_acpi_ACPI_XSDT_BODY *xsdt_body) {
  if (xsdt==0 || xsdt_body==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 page=xsdt& ~0xFFFULL;
  sh_log_fdebug(SH_LOG_SOURCE_ACPI,"XSDT: 0x%x; XSDT aligned: 0x%x\n",xsdt,page);
  SH_STATUS status=sh_memory_identity_map(page,1,SH_PAGE_PRESENT | SH_PAGE_NX);
  if (sh_status_error(status)) return status;
  sh_acpi_ACPI_XSDT_HEADER *header=(sh_acpi_ACPI_XSDT_HEADER*)xsdt;
  if (header->signature[0]!='X' || header->signature[1]!='S' ||header->signature[2]!='D' ||header->signature[3]!='T') return SH_STATUS_INVALID_SIGNATURE;
  if (header->length<sizeof(sh_acpi_ACPI_XSDT_HEADER)) return SH_STATUS_INVALID_STATE;
  if (sh_acpi_internal_checksum((sh_uint8*)header,header->length)!=0) return SH_STATUS_INVALID_CHECKSUM;
  sh_uint64 count=(header->length-sizeof(sh_acpi_ACPI_XSDT_HEADER))/8;
  if (count==0) return SH_STATUS_NOT_FOUND;
  sh_uint64 *entries=(sh_uint64*)(header+1);
  sh_uint64 entries_bytes=count*sizeof(sh_uint64);
  sh_uint64 entries_start=(sh_uint64)entries & ~0xFFFULL;
  sh_uint64 entries_end=((sh_uint64)entries+entries_bytes+0xFFF)&~0xFFFULL;
  sh_uint64 pages=(entries_end-entries_start)>>12;
  sh_log_fdebug(SH_LOG_SOURCE_ACPI,"XSDT entries: 0x%x; XSDT entries aligned: 0x%x\n",entries,entries_start);
  status=sh_memory_identity_map(entries_start,pages,SH_PAGE_PRESENT | SH_PAGE_NX);
  if (sh_status_error(status) && status!=SH_STATUS_ERROR_VA_FULLY_MAPPED) return status;
  sh_uint64 valid=0;
  for (sh_uint64 i=0;i<count;i++) {
    if (entries[i]!=0) valid++;
  }
  if (valid==0) return SH_STATUS_NOT_FOUND;
  xsdt_body->entries=entries;
  xsdt_body->entry_count=count;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_acpi_parse_tables(sh_acpi_ACPI_XSDT_BODY *xsdt_body,sh_acpi_ACPI_TABLES *acpi_tables) {
  if (xsdt_body==SH_NULLPTR || acpi_tables==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  acpi_tables->madt_physical_address=0;
  for (sh_uint64 i=0;i<xsdt_body->entry_count;i++) {
    sh_uint64 phys=xsdt_body->entries[i];
    if (phys==0) {
      sh_log_fdebug(SH_LOG_SOURCE_ACPI,"Table #%8u: empty, skipping.\n",i);
      continue;
    }
    sh_uint64 page=phys& ~0xfffull;
    SH_STATUS status=sh_memory_identity_map(page,1,SH_PAGE_PRESENT | SH_PAGE_NX);
    if (sh_status_error(status)) return status;
    sh_acpi_TABLE_HEADER *header=(sh_acpi_TABLE_HEADER*)phys;
    if (header->length<sizeof(sh_acpi_TABLE_HEADER)) {
      sh_log_fdebug(SH_LOG_SOURCE_ACPI,"Table #%8u: bad length, skipping.\n",i);
      continue;
    }
    if (header->signature[0]=='A' && header->signature[1]=='P' && header->signature[2]=='I' && header->signature[3]=='C') {
      if (acpi_tables->madt_physical_address!=0) {
        sh_log_fdebug(SH_LOG_SOURCE_ACPI,"Duplicated table with APIC signature, skipping.\n");
        continue;
      } else {
        acpi_tables->madt_physical_address=phys;
      }
    }
    char table_signature[5]={0};
    sh_mem_copy(table_signature,header->signature,sizeof(header->signature));
    sh_log_fdebug(SH_LOG_SOURCE_ACPI,"Table #%8u: Signature \"%s\". Stored at 0x%x\n",i,table_signature,(sh_uint64)header);
  }
  return SH_STATUS_SUCCESS;
}
