// SPDX-License-Identifier: MPL-2.0
#include "devs/apic/ioapic.h"
#include "memory/memory.h"
#include "kernel/log.h"
typedef struct {
  sh_ioapic_DEVICE **by_ioapic_id;
  sh_uint64 max_ioapic_id;
  sh_uint64 ioapic_count;
} ioapic_devs_template;
ioapic_devs_template ioapic_devs={0};
static inline sh_uint32 sh_ioapic_read(sh_ioapic_DEVICE *ioapic,sh_uint32 reg) {
  *(volatile sh_uint32*)((sh_uint8*)ioapic->base+SH_IOAPIC_OFFSET_IOREGSEL)=reg;
  return *(volatile sh_uint32*)((sh_uint8*)ioapic->base+SH_IOAPIC_OFFSET_IOWIN);
}
static inline void sh_ioapic_write(sh_ioapic_DEVICE *ioapic,sh_uint32 reg,sh_uint32 value) {
  *(volatile sh_uint32*)((sh_uint8*)ioapic->base+SH_IOAPIC_OFFSET_IOREGSEL)=reg;
  *(volatile sh_uint32*)((sh_uint8*)ioapic->base+SH_IOAPIC_OFFSET_IOWIN)=value;
}
SH_STATUS sh_ioapic_init(sh_uint32 base,sh_uint8 ioapic_id,sh_uint32 gsi_base,sh_ioapic_DEVICE *ioapic) {
  if (ioapic==SH_NULLPTR || base==0) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 page=base& ~0xfffull;
  SH_STATUS status=sh_memory_identity_map(page,1,SH_PAGE_RW | SH_PAGE_PRESENT | SH_PAGE_NX | SH_PAGE_PCD);
  if (sh_status_error(status) && status!=SH_STATUS_ERROR_VA_FULLY_MAPPED) return status;
  ioapic->base=(volatile sh_uint32*)(sh_uint64)base;
  ioapic->ioapic_id=ioapic_id;
  ioapic->gsi_base=gsi_base;
  ioapic->gsi_count=0;
  ioapic->lock=SH_LOCK_LOCAL();
  sh_uint32 ver=sh_ioapic_read(ioapic,SH_IOAPIC_REGISTER_IOAPICVER);
  sh_uint32 max_redir=(ver>>16)&0xFF;
  ioapic->gsi_count=max_redir+1;
  sh_log_fdebug(SH_LOG_SOURCE_ACPI,"IOAPIC with id %1u: [%4u - %4u]\n",ioapic->ioapic_id,ioapic->gsi_base,ioapic->gsi_base+ioapic->gsi_count-1);
  sh_uint32 id_reg=sh_ioapic_read(ioapic,SH_IOAPIC_REGISTER_IOAPICID);
  sh_uint8 hw_id=(id_reg>>24)&0x0F;
  if (hw_id!=ioapic_id) {
    sh_log_fwarning(SH_LOG_SOURCE_ACPI,"IOAPIC of IOAPIC id %1u doesn't provide the same id in his hardware register: %4u.\n",ioapic_id,hw_id);
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_ioapic_init_devs(sh_uint8 max_ioapic_id) {
  sh_ioapic_DEVICE **ioapic_ptr_by_ioapic_id=sh_malloc((max_ioapic_id+1)*sizeof(sh_ioapic_DEVICE*));
  if (!ioapic_ptr_by_ioapic_id) return SH_STATUS_ERROR_NULLPTR_RETURNED;
  sh_mem_set_8((sh_uint8*)ioapic_ptr_by_ioapic_id,0x0,(max_ioapic_id+1)*sizeof(sh_ioapic_DEVICE*));
  ioapic_devs.by_ioapic_id=ioapic_ptr_by_ioapic_id;
  ioapic_devs.max_ioapic_id=max_ioapic_id;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_ioapic_bind(sh_ioapic_DEVICE *ioapic_dev) {
  if (ioapic_dev==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if (ioapic_devs.by_ioapic_id==SH_NULLPTR) return SH_STATUS_DEVS_NOT_INITIALIZED;
  ioapic_devs.by_ioapic_id[ioapic_dev->ioapic_id]=ioapic_dev;
  ioapic_devs.ioapic_count++;
  return SH_STATUS_SUCCESS;
}
sh_ioapic_DEVICE *sh_ioapic_get_dev_ioapic_id(sh_uint64 ioapic_id) {
  if (ioapic_id>ioapic_devs.max_ioapic_id) return SH_NULLPTR;
  return ioapic_devs.by_ioapic_id[ioapic_id];
}
SH_STATUS sh_ioapic_read_ioredtbl_entry(sh_ioapic_DEVICE *ioapic_dev,sh_uint32 gsi,sh_ioapic_IOREDTBL_ENTRY *entry) {
  if (!ioapic_dev || !entry) return SH_STATUS_INVALID_PARAMETER;
  if (gsi<ioapic_dev->gsi_base) return SH_STATUS_INVALID_PARAMETER;
  sh_uint32 index=gsi-ioapic_dev->gsi_base;
  if (index>=ioapic_dev->gsi_count) return SH_STATUS_INVALID_PARAMETER;
  sh_uint32 low_reg=SH_IOAPIC_REGISTER_IOREDTBL_BASE+(index*2);
  sh_uint32 high_reg=low_reg+1;
  sh_spin_lock(&ioapic_dev->lock);
  entry->low=sh_ioapic_read(ioapic_dev,low_reg);
  entry->high=sh_ioapic_read(ioapic_dev,high_reg);
  sh_spin_unlock(&ioapic_dev->lock);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_ioapic_mask_gsi(sh_ioapic_DEVICE *dev,sh_uint32 gsi) {
  if (!dev) return SH_STATUS_INVALID_PARAMETER;
  sh_ioapic_IOREDTBL_ENTRY e;
  SH_STATUS status=sh_ioapic_read_ioredtbl_entry(dev,gsi,&e);
  if (status!=SH_STATUS_SUCCESS) return status;
  e.low|=(1<<16);
  return sh_ioapic_write_ioredtbl_entry(dev,gsi,&e);
}
SH_STATUS sh_ioapic_unmask_gsi(sh_ioapic_DEVICE *dev,sh_uint32 gsi) {
  if (!dev) return SH_STATUS_INVALID_PARAMETER;
  sh_ioapic_IOREDTBL_ENTRY e;
  SH_STATUS status=sh_ioapic_read_ioredtbl_entry(dev,gsi,&e);
  if (status!=SH_STATUS_SUCCESS) return status;
  e.low&= ~(1U<<16);
  return sh_ioapic_write_ioredtbl_entry(dev,gsi,&e);
}
SH_STATUS sh_ioapic_write_ioredtbl_entry(sh_ioapic_DEVICE *ioapic_dev,sh_uint32 gsi,sh_ioapic_IOREDTBL_ENTRY *entry) {
  if (!ioapic_dev || !entry) return SH_STATUS_INVALID_PARAMETER;
  if (gsi<ioapic_dev->gsi_base) return SH_STATUS_INVALID_PARAMETER;
  sh_uint32 index=gsi-ioapic_dev->gsi_base;
  if (index>=ioapic_dev->gsi_count) return SH_STATUS_INVALID_PARAMETER;
  sh_uint32 low_reg=SH_IOAPIC_REGISTER_IOREDTBL_BASE+(index*2);
  sh_uint32 high_reg=low_reg+1;
  sh_spin_lock(&ioapic_dev->lock);
  sh_ioapic_write(ioapic_dev,high_reg,entry->high);
  sh_ioapic_write(ioapic_dev,low_reg,entry->low);
  sh_spin_unlock(&ioapic_dev->lock);
  return SH_STATUS_SUCCESS;
}
sh_ioapic_DEVICE *sh_ioapic_get_dev_by_gsi(sh_uint32 gsi) {
  if (ioapic_devs.by_ioapic_id==SH_NULLPTR) return SH_NULLPTR;
  for (sh_uint64 i=0;i<=ioapic_devs.max_ioapic_id;i++) {
    sh_ioapic_DEVICE *dev=ioapic_devs.by_ioapic_id[i];
    if (!dev) continue;
    if (gsi>=dev->gsi_base && gsi<(dev->gsi_base+dev->gsi_count)) {
      return dev;
    }
  }
  return SH_NULLPTR;
}
SH_STATUS sh_ioapic_mask_all() {
  if (ioapic_devs.by_ioapic_id==SH_NULLPTR) return SH_STATUS_DEVS_NOT_INITIALIZED;
  for (sh_uint64 i=0;i<=ioapic_devs.max_ioapic_id;i++) {
    sh_ioapic_DEVICE *dev=ioapic_devs.by_ioapic_id[i];
    if (!dev) continue;
    for (sh_uint32 j=0;j<dev->gsi_count;j++) {
      sh_uint32 gsi=dev->gsi_base+j;
      SH_STATUS status=sh_ioapic_mask_gsi(dev,gsi);
      if (sh_status_error(status)) return status;
    }
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_ioapic_devs_query(char *sub_path,sh_devs_RESULT *result) {
  if (sub_path==SH_NULLPTR || result==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 path_len=sh_string_len(sub_path);
  if (path_len==0 || path_len>256) return SH_STATUS_INVALID_PARAMETER;
    if (ioapic_devs.by_ioapic_id==SH_NULLPTR) return SH_STATUS_DEVS_NOT_INITIALIZED;
  if (sh_string_compare(sub_path,"/count",6)) {
    if (path_len==6) {
      result->type=SH_DEVS_VALUE;
      result->value=ioapic_devs.ioapic_count;
      return SH_STATUS_SUCCESS;
    } else {
      return SH_STATUS_NOT_FOUND;
    }
  } else if (sh_string_compare(sub_path,"/max-ioapic-id",14)) {
    if (path_len==14) {
      result->type=SH_DEVS_VALUE;
      result->value=ioapic_devs.max_ioapic_id;
      return SH_STATUS_SUCCESS;
    } else {
      return SH_STATUS_NOT_FOUND;
    }
  } else if (sh_string_compare(sub_path,"/by-ioapic-id/",14)) {
    if (path_len>14 && path_len<=34) {
      sh_uint64 remaining_text=sh_string_len(sub_path)-sh_string_len("/by-ioapic-id/");
      char ioapic_id_str[22];
      sh_string_substring(sub_path,sh_string_len("/by-ioapic-id/"),remaining_text,ioapic_id_str);
      sh_uint64 ioapic_id=sh_string_to_uint64(ioapic_id_str);
      if (ioapic_id>ioapic_devs.max_ioapic_id) return SH_STATUS_NOT_FOUND;
      result->type=SH_DEVS_IOAPIC;
      result->value=(sh_uint64)ioapic_devs.by_ioapic_id[ioapic_id];
      return SH_STATUS_SUCCESS;
    } else {
      return SH_STATUS_NOT_FOUND;
    }
  } else if (sh_string_compare(sub_path,"/by-gsi/",8)) {
    if (path_len>8 && path_len<=28) {
      sh_uint64 remaining_text=sh_string_len(sub_path)-sh_string_len("/by-gsi/");
      char gsi_str[22];
      sh_string_substring(sub_path,sh_string_len("/by-gsi/"),remaining_text,gsi_str);
      sh_uint64 gsi=sh_string_to_uint64(gsi_str);
      result->type=SH_DEVS_IOAPIC;
      result->value=(sh_uint64)sh_ioapic_get_dev_by_gsi((sh_uint32)gsi);
      return SH_STATUS_SUCCESS;
    } else {
      return SH_STATUS_NOT_FOUND;
    }
  } else {
    return SH_STATUS_NOT_FOUND;
  }
  return SH_STATUS_SUCCESS;
}
