// SPDX-License-Identifier: MPL-2.0
#include "irq/gsi.h"
#include "irq/irq.h"
static sh_gsi_ISO *iso_array=SH_NULLPTR;
sh_uint16 bumb=0;
SH_STATUS sh_gsi_iso_array_init() {
  iso_array=sh_malloc(sizeof(sh_gsi_ISO)*256);
  if (!iso_array) return SH_STATUS_ERROR_NULLPTR_RETURNED;
  bumb=0;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_gsi_iso_register(sh_uint8 bus,sh_uint8 source,sh_uint32 gsi,sh_uint16 flags) {
  if (!iso_array) return SH_STATUS_UNAVAILABLE;
  if (bumb>=256) return SH_STATUS_TOO_MUCH_DATA;
  for (sh_uint16 i=0;i<bumb;i++) {
    if (iso_array[i].valid && iso_array[i].source_irq==source) {
      iso_array[i].bus=bus;
      iso_array[i].gsi=gsi;
      iso_array[i].source_irq=source;
      switch (flags&0x3) {
        case 0:
          iso_array[i].polarity=SH_GSI_POLARITY_BUS_DEFAULT;
          break;
        case 1:
          iso_array[i].polarity=SH_GSI_POLARITY_ACTIVE_HIGH;
          break;
        case 3:
          iso_array[i].polarity=SH_GSI_POLARITY_ACTIVE_LOW;
          break;
        default:
          iso_array[i].polarity=SH_GSI_POLARITY_BUS_DEFAULT;
          break;
      }
      switch (flags&0xC) {
        case 0:
          iso_array[i].trigger_mode=SH_GSI_TRIGGER_MODE_BUS_DEFAULT;
          break;
        case 0x4:
          iso_array[i].trigger_mode=SH_GSI_TRIGGER_MODE_EDGE;
          break;
        case 0xC:
          iso_array[i].trigger_mode=SH_GSI_TRIGGER_MODE_LEVEL;
          break;
        default:
          iso_array[i].trigger_mode=SH_GSI_TRIGGER_MODE_BUS_DEFAULT;
          break;
      }
      iso_array[i].valid=SH_TRUE;
      return SH_STATUS_SUCCESS;
    }
  }
  sh_gsi_ISO *iso=&iso_array[bumb++];
  iso->bus=bus;
  iso->source_irq=source;
  iso->gsi=gsi;
  iso->valid=SH_TRUE;
  switch (flags & 0x3) {
    case 0:
      iso->polarity=SH_GSI_POLARITY_BUS_DEFAULT;
      break;
    case 1:
      iso->polarity=SH_GSI_POLARITY_ACTIVE_HIGH;
      break;
    case 3:
      iso->polarity=SH_GSI_POLARITY_ACTIVE_LOW;
      break;
    default:
      iso->polarity=SH_GSI_POLARITY_BUS_DEFAULT;
      break;
  }
  switch (flags & 0xC) {
    case 0:
      iso->trigger_mode=SH_GSI_TRIGGER_MODE_BUS_DEFAULT;
      break;
    case 0x4:
      iso->trigger_mode=SH_GSI_TRIGGER_MODE_EDGE;
      break;
    case 0xC:
      iso->trigger_mode=SH_GSI_TRIGGER_MODE_LEVEL;
      break;
    default:
      iso->trigger_mode=SH_GSI_TRIGGER_MODE_BUS_DEFAULT;
      break;
  }
  return SH_STATUS_SUCCESS;
}
sh_gsi_ISO *sh_gsi_get_iso_by_irq(sh_uint8 irq) {
  if (!iso_array) return SH_NULLPTR;
  for (sh_uint16 i=0;i<bumb;i++) {
    if (iso_array[i].valid && iso_array[i].source_irq==irq) return &iso_array[i];
  }
  return SH_NULLPTR;
}
sh_gsi_ISO *sh_gsi_get_iso_by_gsi(sh_uint32 gsi) {
  if (!iso_array) return SH_NULLPTR;
  for (sh_uint16 i=0;i<bumb;i++) {
    if (iso_array[i].valid && iso_array[i].gsi==gsi) return &iso_array[i];
  }
  return SH_NULLPTR;
}
SH_STATUS sh_gsi_get(sh_uint32 gsi,sh_ioapic_IOREDTBL_ENTRY *entry) {
  if (entry==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_ioapic_DEVICE *dev=sh_ioapic_get_dev_by_gsi(gsi);
  if (!dev) return SH_STATUS_ERROR_NULLPTR_RETURNED;
  SH_STATUS status=sh_ioapic_read_ioredtbl_entry(dev,gsi,entry);
  return status;
}
SH_STATUS sh_gsi_set(sh_uint32 gsi,sh_ioapic_IOREDTBL_ENTRY *entry) {
  if (entry==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_ioapic_DEVICE *dev=sh_ioapic_get_dev_by_gsi(gsi);
  if (!dev) return SH_STATUS_ERROR_NULLPTR_RETURNED;
  SH_STATUS status=sh_ioapic_mask_gsi(dev,gsi);
  if (sh_status_error(status)) return status;
  status=sh_ioapic_write_ioredtbl_entry(dev,gsi,entry);
  if (sh_status_error(status)) return status;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_gsi_irq_set(sh_uint8 irq,sh_ioapic_IOREDTBL_ENTRY *entry) {
  if (!entry) return SH_STATUS_INVALID_PARAMETER;
  sh_uint32 gsi=irq;
  sh_gsi_ISO *iso=sh_gsi_get_iso_by_irq((sh_uint8)irq);
  if (iso && iso->valid) {
    gsi=iso->gsi;
  }
  return sh_gsi_set(gsi,entry);
}
SH_STATUS sh_gsi_irq_mask(sh_uint8 irq) {
  sh_uint32 gsi=irq;
  sh_gsi_ISO *iso=sh_gsi_get_iso_by_irq(irq);
  if (iso && iso->valid) {
    gsi=iso->gsi;
  }
  return sh_gsi_mask(gsi);
}
SH_STATUS sh_gsi_irq_unmask(sh_uint8 irq) {
  sh_uint32 gsi=irq;
  sh_gsi_ISO *iso=sh_gsi_get_iso_by_irq(irq);
  if (iso && iso->valid) {
    gsi=iso->gsi;
  }
  return sh_gsi_unmask(gsi);
}
SH_STATUS sh_gsi_irq_switch() {
  sh_bool gsi_already_used[256]={0};
  for (sh_iter64 i=0;i<16;i++) {
    sh_ioapic_IOREDTBL_ENTRY entry={0};
    sh_gsi_ISO *iso=sh_gsi_get_iso_by_irq((sh_uint8)i);
    sh_uint32 gsi=(sh_uint32)i;
    sh_uint8 polarity=SH_IOAPIC_IOREDTBL_POLARITY_HIGH;
    sh_uint8 trigger_mode=SH_IOAPIC_IOREDTBL_TRIGGER_EDGE;
    if (iso && iso->valid) {
      gsi=iso->gsi;
      if (iso->polarity==SH_GSI_POLARITY_ACTIVE_LOW) {
        polarity=SH_IOAPIC_IOREDTBL_POLARITY_LOW;
      } else if (iso->polarity==SH_GSI_POLARITY_BUS_DEFAULT) {
        if (iso->bus==0) {
          polarity=SH_IOAPIC_IOREDTBL_POLARITY_HIGH;
        } else {
          return SH_STATUS_ISO_UNKNOW_BUS;
        }
      }
      if (iso->trigger_mode==SH_GSI_TRIGGER_MODE_LEVEL) {
        trigger_mode=SH_IOAPIC_IOREDTBL_TRIGGER_LEVEL;
      } else if (iso->trigger_mode==SH_GSI_TRIGGER_MODE_BUS_DEFAULT) {
        if (iso->bus==0) {
          trigger_mode=SH_IOAPIC_IOREDTBL_TRIGGER_EDGE;
        } else {
          return SH_STATUS_ISO_UNKNOW_BUS;
        }
      }
    }
    if (gsi_already_used[gsi]) continue;
    entry=sh_ioapic_make_ioredtbl_entry((sh_uint8)i+32,SH_IOAPIC_IOREDTBL_DELIVERY_FIXED,SH_IOAPIC_IOREDTBL_DEST_MODE_PHYSICAL,polarity,trigger_mode,SH_TRUE,0);
    SH_STATUS status=sh_gsi_irq_set((sh_uint8)i,&entry);
    if (sh_status_error(status)) return status;
    gsi_already_used[gsi]=SH_TRUE;
    sh_gsi_irq_mask((sh_uint8)i);
  }
  SH_STATUS status=sh_irq_switch_irq_management();
  if (sh_status_error(status)) return status;
  return SH_STATUS_SUCCESS;
}
