// SPDX-License-Identifier: GPL-2.0-only /* * EFI Boot Guard, iTCO support (Version 3 and later) * * Copyright (c) 2006-2011 Wim Van Sebroeck . * Copyright (c) 2019 Siemens AG * Copyright (c) 2019 Ahmad Fatoum, Pengutronix * * Authors: * Jan Kiszka * Christian Storm */ #include #include #include #include #include #define ACPIBASE 0x40 #define ACPICTRL_PMCBASE 0x44 #define PMBASE_ADDRMASK 0x0000ff80 #define PMCBASE_ADDRMASK 0xfffffe00 #define ACPIBASE_GCS_OFF 0x3410 #define ACPIBASE_SMI_OFF 0x30 #define ACPIBASE_SMI_END 0x33 #define ACPIBASE_PMC_OFF 0x08 #define ACPIBASE_PMC_END 0x0c #define ACPIBASE_TCO_OFF 0x60 #define ACPIBASE_TCO_END 0x7f #define SMI_TCO_MASK (1 << 13) #define TCO_TMR_HLT_MASK (1 << 11) /* SMI Control and Enable Register */ #define SMI_EN(itco) ((itco)->smibase) /* TCO base address */ #define TCOBASE(itco) ((itco)->tcobase) #define TCO_RLD(p) (TCOBASE(p) + 0x00) /* TCO Timer Reload/Curr. Value */ #define TCOv1_TMR(p) (TCOBASE(p) + 0x01) /* TCOv1 Timer Initial Value*/ #define TCO_DAT_IN(p) (TCOBASE(p) + 0x02) /* TCO Data In Register */ #define TCO_DAT_OUT(p) (TCOBASE(p) + 0x03) /* TCO Data Out Register */ #define TCO1_STS(p) (TCOBASE(p) + 0x04) /* TCO1 Status Register */ #define TCO2_STS(p) (TCOBASE(p) + 0x06) /* TCO2 Status Register */ #define TCO1_CNT(p) (TCOBASE(p) + 0x08) /* TCO1 Control Register */ #define TCO2_CNT(p) (TCOBASE(p) + 0x0a) /* TCO2 Control Register */ #define TCOv2_TMR(p) (TCOBASE(p) + 0x12) /* TCOv2 Timer Initial Value*/ #define PMC_NO_REBOOT_MASK (1 << 4) #define RCBABASE 0xf0 #define PCI_ID_ITCO_INTEL_ICH9 0x2918 struct itco_priv; struct itco_info { u32 pci_id; const char *name; u32 pmbase; u32 smireg; int (*update_no_reboot_bit)(struct itco_priv *itco, bool set); unsigned version; }; struct itco_priv { struct pci_dev *pdev; struct watchdog wdd; void __iomem *io; u32 smibase; u32 tcobase; void __iomem *gcs_pmc; struct itco_info *info; unsigned timeout; }; static u32 itco_get_pmbase(struct itco_priv *itco) { u32 pmbase = itco->info->pmbase; if (!pmbase) pci_read_config_dword(itco->pdev, ACPIBASE, &pmbase); return pmbase & PMBASE_ADDRMASK; } static inline struct itco_priv *to_itco_priv(struct watchdog *wdd) { return container_of(wdd, struct itco_priv, wdd); } static void itco_wdt_ping(struct itco_priv *itco) { /* Reload the timer by writing to the TCO Timer Counter register */ outw(0x0001, TCO_RLD(itco)); } static inline unsigned int seconds_to_ticks(struct itco_priv *itco, int secs) { return itco->info->version == 3 ? secs : (secs * 10) / 6; } static inline unsigned int ticks_to_seconds(struct itco_priv *itco, int ticks) { return itco->info->version == 3 ? ticks : (ticks * 6) / 10; } static int itco_wdt_start(struct itco_priv *itco, unsigned int timeout) { unsigned tmrval; u32 value; int ret; tmrval = seconds_to_ticks(itco, timeout); /* Enable TCO SMIs */ value = inl(SMI_EN(itco)) | SMI_TCO_MASK; outl(value, SMI_EN(itco)); /* Set timer value */ value = inw(TCOv2_TMR(itco)); value &= 0xfc00; value |= tmrval & 0x3ff; outw(value, TCOv2_TMR(itco)); value = inw(TCOv2_TMR(itco)); if ((value & 0x3ff) != tmrval) return -EINVAL; /* Force reloading of timer value */ outw(1, TCO_RLD(itco)); /* Clear NO_REBOOT flag */ ret = itco->info->update_no_reboot_bit(itco, false); if (ret) return ret; /* Clear HLT flag to start timer */ value = inw(TCO1_CNT(itco)) & ~TCO_TMR_HLT_MASK; outw(value, TCO1_CNT(itco)); value = inw(TCO1_CNT(itco)); if (value & 0x0800) return -EIO; return 0; } static int itco_wdt_stop(struct itco_priv *itco) { u32 val; /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */ val = inw(TCO1_CNT(itco)) | 0x0800; outw(val, TCO1_CNT(itco)); val = inb(TCO1_CNT(itco)); /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ itco->info->update_no_reboot_bit(itco, true); if ((val & 0x0800) == 0) return -EIO; return 0; } static int itco_wdt_set_timeout(struct watchdog *wdd, unsigned int timeout) { struct itco_priv *itco = to_itco_priv(wdd); int ret; if (!timeout) return itco_wdt_stop(itco); /* from the specs: */ /* "Values of 0h-3h are ignored and should not be attempted" */ if (timeout < 0x04) return -EINVAL; if (itco->timeout != timeout) { ret = itco_wdt_start(itco, timeout); if (ret) { dev_err(wdd->hwdev, "Fail to (re)start watchdog\n"); return ret; } } itco_wdt_ping(itco); return 0; } static inline u32 no_reboot_bit(unsigned version) { u32 enable_bit; switch (version) { case 5: case 3: enable_bit = 0x00000010; break; case 2: enable_bit = 0x00000020; break; case 4: case 1: default: enable_bit = 0x00000002; break; } return enable_bit; } static int update_no_reboot_bit(struct itco_priv *itco, bool set) { u32 val32 = 0, newval32 = 0; val32 = readl(itco->gcs_pmc); if (set) val32 |= no_reboot_bit(itco->info->version); else val32 &= ~no_reboot_bit(itco->info->version); writel(val32, itco->gcs_pmc); newval32 = readl(itco->gcs_pmc); /* make sure the update is successful */ if (val32 != newval32) return -EPERM; return 0; } static void lpc_ich_enable_acpi_space(struct itco_priv *itco) { u8 reg_save; pci_read_config_byte(itco->pdev, ACPICTRL_PMCBASE, ®_save); pci_write_config_byte(itco->pdev, ACPICTRL_PMCBASE, reg_save | 0x80); } enum itco_chipsets { ITCO_INTEL_ICH9, }; /* version 1 not supported! */ static struct itco_info itco_chipset_info[] = { [ITCO_INTEL_ICH9] = { .pci_id = PCI_ID_ITCO_INTEL_ICH9, .name = "ICH9", /* QEmu machine q35 */ .smireg = 0x30, .update_no_reboot_bit = update_no_reboot_bit, .version = 2, }, }; static int itco_wdt_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct itco_priv *itco; struct watchdog *wdd; u32 rcba_base_cfg; u32 pmbase; int ret; int i; pci_enable_device(pdev); pci_set_master(pdev); itco = xzalloc(sizeof(*itco)); itco->pdev = pdev; for (i = 0; i < ARRAY_SIZE(itco_chipset_info); i++) { if (id->device == itco_chipset_info[i].pci_id) { itco->info = &itco_chipset_info[i]; break; } } if (!itco->info) return -ENODEV; pci_read_config_dword(itco->pdev, RCBABASE, &rcba_base_cfg); if (!(rcba_base_cfg & 1)) { dev_notice(&pdev->dev, "RCBA is disabled by hardware/BIOS, device disabled\n"); return -ENODEV; } pmbase = itco_get_pmbase(itco); if (!pmbase) { dev_notice(&itco->pdev->dev, "I/O space for ACPI uninitialized\n"); return -ENODEV; } itco->smibase = pmbase + ACPIBASE_SMI_OFF; itco->tcobase = pmbase + ACPIBASE_TCO_OFF; lpc_ich_enable_acpi_space(itco); itco->gcs_pmc = IOMEM(rcba_base_cfg & 0xffffc000UL) + ACPIBASE_GCS_OFF; dev_notice(&pdev->dev, "gcs_pmc = 0x%p, smibase = 0x%x, tcobase = 0x%x\n", itco->gcs_pmc, itco->smibase, itco->tcobase); wdd = &itco->wdd; wdd->hwdev = &pdev->dev; wdd->set_timeout = itco_wdt_set_timeout; wdd->timeout_max = ticks_to_seconds(itco, 0x3ff); outw(0x0008, TCO1_STS(itco)); /* Clear the Time Out Status bit */ outw(0x0002, TCO2_STS(itco)); /* Clear SECOND_TO_STS bit */ outw(0x0004, TCO2_STS(itco)); /* Clear BOOT_STS bit */ ret = watchdog_register(wdd); if (ret) { dev_err(&pdev->dev, "Failed to register watchdog device\n"); return ret; } dev_info(&pdev->dev, "probed Intel TCO %s watchdog\n", itco->info->name); return 0; } static DEFINE_PCI_DEVICE_TABLE(itco_wdt_pci_tbl) = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_ID_ITCO_INTEL_ICH9) }, { /* sentinel */ }, }; static struct pci_driver itco_wdt_driver = { .name = "itco_wdt", .id_table = itco_wdt_pci_tbl, .probe = itco_wdt_probe, }; device_pci_driver(itco_wdt_driver);