#include <stdio.h>
#include <pci/pci.h>
#include <sys/io.h>

#define SMRAM_OFFSET    0x9d
#define D_OPEN_BIT      (0x01 << 6)
#define D_CLS_BIT       (0x01 << 5)
#define D_LCK_BIT       (0x01 << 4)
#define G_SMRAME_BIT    (0x01 << 3)
#define C_BASE_SEG2_BIT (0x01 << 2)
#define C_BASE_SEG1_BIT (0x01 << 1)
#define C_BASE_SEG0_BIT (0x01)

#define LPC_DEV                 31
#define LPC_FUNC                0
#define PMBASE_REG              0x40
#define SMI_CTRL_ADDR           0x30    /* aka SMI_EN */
#define SMI_STATUS_ADDR         0x34    /* aka SMI_STS */
#define INTEL_USB2_EN_BIT       (0x01 << 18)
#define LEGACY_USB2_EN_BIT      (0x01 << 17)
#define PERIODIC_EN_BIT         (0x01 << 14)
#define TCO_EN_BIT              (0x01 << 13)
#define MCSMI_EN_BIT            (0x01 << 11)
#define SWSMI_TMR_EN_BIT        (0x01 << 6)
#define APMC_EN_BIT             (0x01 << 5)
#define SLP_EN_BIT              (0x01 << 4)
#define LEGACY_USB_EN_BIT       (0x01 << 3)
#define BIOS_EN_BIT             (0x01 << 2)
#define GBL_SMI_EN_BIT          (0x01)          /* This is reset by a PCI reset event! */

void show_smram(struct pci_dev* SMRAM)
{
    u8 smram_value;

    smram_value = pci_read_byte(SMRAM, SMRAM_OFFSET);

    if (smram_value & D_OPEN_BIT) {
        printf("D_OPEN_BIT:      1\n");
    } else {
        printf("D_OPEN_BIT:      0\n");
    }
    if (smram_value & D_CLS_BIT) {
        printf("D_CLS_BIT:       1\n");
    } else {
        printf("D_CLS_BIT:       0\n");
    }
    if (smram_value & D_LCK_BIT) {
        printf("D_LCK_BIT:       1\n");
    } else {
        printf("D_LCK_BIT:       0\n");
    }
    if (smram_value & G_SMRAME_BIT) {
        printf("G_SMRAME_BIT:    1\n");
    } else {
        printf("G_SMRAME_BIT:    0\n");
    }
    if (smram_value & C_BASE_SEG2_BIT) {
        printf("C_BASE_SEG2_BIT: 1\n");
    } else {
        printf("C_BASE_SEG2_BIT: 0\n");
    }
    if (smram_value & C_BASE_SEG1_BIT) {
        printf("C_BASE_SEG1_BIT: 1\n");
    } else {
        printf("C_BASE_SEG1_BIT: 0\n");
    }
    if (smram_value & C_BASE_SEG0_BIT) {
        printf("C_BASE_SEG0_BIT: 1\n");
    } else {
        printf("C_BASE_SEG0_BIT: 0\n");
    }
    printf("\n");
}

uint32_t show_smi_en(int pmbase)
{
    unsigned int val = inl(pmbase + SMI_CTRL_ADDR), sts = inl(pmbase + SMI_STATUS_ADDR);
    printf("SMI_STS:             %x; ", sts);
    if ((sts & (1 << 2))) printf("BIOS ");
    if ((sts & (1 << 4))) printf("SLP_SMI ");
    if ((sts & (1 << 5))) printf("APM ");
    if ((sts & (1 << 6))) printf("SWSMI_TMR ");
    if ((sts & (1 << 8))) printf("PM1 ");
    if ((sts & (1 << 9))) printf("GPEO0 ");
    if ((sts & (1 << 12))) printf("DEVMON ");
    if ((sts & (1 << 13))) printf("TCO ");
    if ((sts & (1 << 14))) printf("PERIODIC ");
    if ((sts & (1 << 15))) printf("ILB ");
    if ((sts & (1 << 16))) printf("SMBUS_SMI ");
    if ((sts & (1 << 17))) printf("LEGACY_USB2 ");
    if ((sts & (1 << 18))) printf("INTEL_USB2 ");
    if ((sts & (1 << 20))) printf("PCI_EXP_SMI ");
    if ((sts & (1 << 26))) printf("SPI ");
    if ((sts & (1 << 28))) printf("PUNIT ");
    if ((sts & (1 << 29))) printf("GUNIT");
    printf("\n");
    printf("INTEL_USB2_EN:       %d\n", !!(val & INTEL_USB2_EN_BIT));
    printf("LEGACY_USB2_EN:      %d\n", !!(val & LEGACY_USB2_EN_BIT));
    printf("PERIODIC_EN:         %d\n", !!(val & PERIODIC_EN_BIT));
    printf("TCO_EN:              %d\n", !!(val & TCO_EN_BIT));
    printf("MCSMI_EN:            %d\n", !!(val & MCSMI_EN_BIT));
    printf("SWSMI_TMR_EN:        %d\n", !!(val & SWSMI_TMR_EN_BIT));
    printf("APMC_EN:             %d\n", !!(val & APMC_EN_BIT));
    printf("SLP_EN:              %d\n", !!(val & SLP_EN_BIT));
    printf("LEGACY_USB_EN:       %d\n", !!(val & LEGACY_USB_EN_BIT));
    printf("BIOS_EN:             %d\n", !!(val & BIOS_EN_BIT));
    printf("GBL_SMI_EN:          %d\n", !!(val & GBL_SMI_EN_BIT));
    printf("\n");
    return val;
}

int main(void) {
    char vendor_name[128], device_name[128];
    struct pci_access *pacc;
    struct pci_dev *dev, *SMRAM;

    pacc = pci_alloc();
    pci_init(pacc);
    pci_scan_bus(pacc);

    for (dev = pacc->devices; dev; dev = dev->next) {
        unsigned int pmbase, smi_en;
        pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_BASES);
        if (dev->dev != LPC_DEV || dev->func != LPC_FUNC)
            continue;
        pci_lookup_name(pacc, vendor_name, sizeof(vendor_name), PCI_LOOKUP_VENDOR, dev->vendor_id);
        pci_lookup_name(pacc, device_name, sizeof(device_name), PCI_LOOKUP_DEVICE, dev->vendor_id, dev->device_id);
        printf("SMI-enabled chipset found:\n %s %s (%04x:%04x)\n", vendor_name, device_name, dev->vendor_id,
            dev->device_id);
        pmbase = ((pci_read_byte(dev, PMBASE_REG+1) << 1) | (pci_read_byte(dev, PMBASE_REG) >> 7)) << 7;
        printf(" PMBASE: %16x\n", pmbase);
        if (iopl(3) < 0)
            perror("iopl");
        if (ioperm(pmbase, SMI_STATUS_ADDR+4, 1) < 0)
            perror("ioperm");
        smi_en = show_smi_en(pmbase);
        printf("Disabling GBL_SMI_EN.\n");
//        smi_en &= ~GBL_SMI_EN_BIT;
        smi_en = 0;
        outl(smi_en, pmbase + SMI_CTRL_ADDR);
        show_smi_en(pmbase);
    }

    SMRAM = pci_get_dev(pacc, 0, 0, 0, 0);

    printf("Current status of SMRAM:\n");
    show_smram(SMRAM);

    printf("Settings D_OPEN to 1\n");
    pci_write_byte(SMRAM, SMRAM_OFFSET, 0x4a);
    show_smram(SMRAM);

    return 0;
}
