// SPDX-License-Identifier: MPL-2.0
#include "devs/acpi_tables/madt.h"
#include "devs/apic/lapic.h"
#include "devs/apic/ioapic.h"
#include "memory/memory.h"
#include "kernel/log.h"
#include "irq/gsi.h"
SH_STATUS sh_acpi_madt_parse(sh_uint64 madt_ptr) {
  if (madt_ptr==0) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 page=madt_ptr & ~0xfffull;
  SH_STATUS status=sh_memory_identity_map(page,1,SH_PAGE_PRESENT | SH_PAGE_NX);
  if (sh_status_error(status) && status!=SH_STATUS_ERROR_VA_FULLY_MAPPED) return status;
  sh_acpi_madt_MADT *madt=(sh_acpi_madt_MADT*)madt_ptr;
  if (madt->header.signature[0]!='A' || madt->header.signature[1]!='P' || madt->header.signature[2]!='I' || madt->header.signature[3]!='C') return SH_STATUS_INVALID_SIGNATURE;
  if (madt->header.length<sizeof(sh_acpi_madt_MADT)) return SH_STATUS_INVALID_STATE;
  sh_uint64 offset=sizeof(sh_acpi_madt_MADT);
  sh_uint64 end=madt->header.length;
  sh_uint16 max_apic_id=0;
  sh_uint16 max_acpi_processor_id=0;
  sh_uint8 max_ioapic_id=0;
  status=sh_gsi_iso_array_init();
  if (sh_status_error(status)) return status;
  while (offset<end) {
    sh_acpi_madt_ENTRY_HEADER *header=(sh_acpi_madt_ENTRY_HEADER*)((sh_uint8*)madt+offset);
    if (header->length==0) break;
    switch (header->type) {
      case 0: {
        sh_acpi_madt_LAPIC *lapic=(sh_acpi_madt_LAPIC*)header;
        if (lapic->flags & 1) {
          sh_log_fdebug(SH_LOG_SOURCE_ACPI,"Found LAPIC with CPU: apic_id=%1u acpi_processor_id=%1u flags=0x%4U\n",lapic->apic_id,lapic->acpi_processor_id,lapic->flags);
          if (lapic->apic_id>max_apic_id) max_apic_id=lapic->apic_id;
          if (lapic->acpi_processor_id>max_acpi_processor_id) max_acpi_processor_id=lapic->acpi_processor_id;
        } else {
          sh_log_fdebug(SH_LOG_SOURCE_ACPI,"Found LAPIC without CPU: apic_id=%1u acpi_processor_id=%1u flags=0x%4U\n",lapic->apic_id,lapic->acpi_processor_id,lapic->flags);
        }
        break;
      }
      case 1: {
        sh_acpi_madt_IOAPIC *ioapic=(sh_acpi_madt_IOAPIC*)header;
        if (ioapic->ioapic_id>max_ioapic_id) max_ioapic_id=ioapic->ioapic_id;
        sh_log_fdebug(SH_LOG_SOURCE_ACPI,"Found IOAPIC: ioapic_id=%1u ioapic_address=0x%4U gsi_base=%4U\n",ioapic->ioapic_id,ioapic->ioapic_address,ioapic->global_system_interrupt_base);
        break;
      }
      case 2: {
        sh_acpi_madt_ISO *iso=(sh_acpi_madt_ISO*)header;
        sh_log_fdebug(SH_LOG_SOURCE_ACPI,"Found interrupt source override: bus=%1u source=%1u gsi=%4u flags=0x%2U\n",iso->bus,iso->source,iso->gsi,iso->flags);
        break;
      }
      default:
        sh_log_fdebug(SH_LOG_SOURCE_ACPI,"Found unknow type: %1u\n",header->type);
        break;
    }
    offset+=header->length;
  }
  status=sh_lapic_init_devs(max_apic_id,max_acpi_processor_id);
  if (sh_status_error(status)) return status;
  status=sh_ioapic_init_devs(max_ioapic_id);
  if (sh_status_error(status)) return status;
  offset=sizeof(sh_acpi_madt_MADT);
  end=madt->header.length;
  while (offset<end) {
    sh_acpi_madt_ENTRY_HEADER *header=(sh_acpi_madt_ENTRY_HEADER*)((sh_uint8*)madt+offset);
    if (header->length==0) break;
    switch (header->type) {
      case 0: {
        sh_acpi_madt_LAPIC *lapic=(sh_acpi_madt_LAPIC*)header;
        if (lapic->flags & 1) {
          sh_lapic_DEVICE *lapic_dev=sh_malloc(sizeof(sh_lapic_DEVICE));
          if (!lapic_dev) return SH_STATUS_ERROR_NULLPTR_RETURNED;
          status=sh_lapic_init(madt->local_apic_address,0xFF,lapic->apic_id,lapic->acpi_processor_id,lapic->flags,lapic_dev);
          if (sh_status_error(status)) return status;
          status=sh_lapic_bind(lapic_dev);
          if (sh_status_error(status)) return status;
        } else {
          // ignore
        }
        break;
      }
      case 1: {
        sh_acpi_madt_IOAPIC *ioapic=(sh_acpi_madt_IOAPIC*)header;
        sh_ioapic_DEVICE *ioapic_dev=sh_malloc(sizeof(sh_ioapic_DEVICE));
        if (!ioapic_dev) return SH_STATUS_ERROR_NULLPTR_RETURNED;
        status=sh_ioapic_init(ioapic->ioapic_address,ioapic->ioapic_id,ioapic->global_system_interrupt_base,ioapic_dev);
        if (sh_status_error(status)) return status;
        status=sh_ioapic_bind(ioapic_dev);
        if (sh_status_error(status)) return status;
        break;
      }
      case 2: {
        sh_acpi_madt_ISO *iso=(sh_acpi_madt_ISO*)header;
        status=sh_gsi_iso_register(iso->bus,iso->source,iso->gsi,iso->flags);
        if (sh_status_error(status)) return status;
        break;
      }
      default:
        // ignore
        break;
    }
    offset+=header->length;
  }
  return SH_STATUS_SUCCESS;
}
