summaryrefslogtreecommitdiff
path: root/src/soc/intel/common/block/systemagent
diff options
context:
space:
mode:
authorSubrata Banik <subrata.banik@intel.com>2017-05-19 14:50:09 +0530
committerMartin Roth <martinroth@google.com>2017-06-09 17:05:49 +0200
commit7609c654b18b232bd0b45621ebd3921d14db8a39 (patch)
treeecef80a30af6372f3222422ab323e814e440724b /src/soc/intel/common/block/systemagent
parent43dcbfd85581de4f173953282a4917c1ee9a5922 (diff)
soc/intel/common/block: Add Intel common systemagent support
Add Intel common systemagent support for romstage and ramstage. Include soc specific macros need to compile systemagent common code. Change-Id: I969ff187e3d4199864cb2e9c9a13f4d04158e27c Signed-off-by: V Sowmya <v.sowmya@intel.com> Signed-off-by: Subrata Banik <subrata.banik@intel.com> Reviewed-on: https://review.coreboot.org/19668 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Aaron Durbin <adurbin@chromium.org>
Diffstat (limited to 'src/soc/intel/common/block/systemagent')
-rw-r--r--src/soc/intel/common/block/systemagent/Kconfig12
-rw-r--r--src/soc/intel/common/block/systemagent/Makefile.inc5
-rw-r--r--src/soc/intel/common/block/systemagent/systemagent.c311
-rw-r--r--src/soc/intel/common/block/systemagent/systemagent_def.h82
-rw-r--r--src/soc/intel/common/block/systemagent/systemagent_early.c134
5 files changed, 512 insertions, 32 deletions
diff --git a/src/soc/intel/common/block/systemagent/Kconfig b/src/soc/intel/common/block/systemagent/Kconfig
index 773a56b831..01a4f8e201 100644
--- a/src/soc/intel/common/block/systemagent/Kconfig
+++ b/src/soc/intel/common/block/systemagent/Kconfig
@@ -24,3 +24,15 @@ config PCIEX_LENGTH_128MB
config PCIEX_LENGTH_64MB
bool "64MB"
+
+config SA_ENABLE_IMR
+ bool
+ default n
+ help
+ This option allows you to add the isolated memory ranges (IMRs).
+
+config SA_ENABLE_DPR
+ bool
+ default n
+ help
+ This option allows you to add the DMA Protected Range (DPR).
diff --git a/src/soc/intel/common/block/systemagent/Makefile.inc b/src/soc/intel/common/block/systemagent/Makefile.inc
index 75d5626cea..81e680b6f6 100644
--- a/src/soc/intel/common/block/systemagent/Makefile.inc
+++ b/src/soc/intel/common/block/systemagent/Makefile.inc
@@ -1 +1,4 @@
-bootblock-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SA) += systemagent.c
+bootblock-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SA) += systemagent_early.c
+romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SA) += systemagent_early.c
+ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SA) += systemagent_early.c
+ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SA) += systemagent.c
diff --git a/src/soc/intel/common/block/systemagent/systemagent.c b/src/soc/intel/common/block/systemagent/systemagent.c
index 58e2c7e054..f2e74cc29f 100644
--- a/src/soc/intel/common/block/systemagent/systemagent.c
+++ b/src/soc/intel/common/block/systemagent/systemagent.c
@@ -14,46 +14,295 @@
*/
#include <arch/io.h>
-#include <commonlib/helpers.h>
+#include <cbmem.h>
+#include <device/device.h>
+#include <device/pci.h>
+#include <device/pci_ids.h>
#include <intelblocks/systemagent.h>
+#include <soc/iomap.h>
#include <soc/pci_devs.h>
+#include <soc/systemagent.h>
+#include "systemagent_def.h"
-void bootblock_systemagent_early_init(void)
+/* SoC override function */
+__attribute__((weak)) void soc_systemagent_init(struct device *dev)
{
- uint32_t reg;
- uint8_t pciexbar_length;
+ /* no-op */
+}
+
+__attribute__((weak)) void soc_add_fixed_mmio_resources(struct device *dev,
+ int *resource_cnt)
+{
+ /* no-op */
+}
+/*
+ * Add all known fixed MMIO ranges that hang off the host bridge/memory
+ * controller device.
+ */
+void sa_add_fixed_mmio_resources(struct device *dev, int *resource_cnt,
+ const struct sa_mmio_descriptor *sa_fixed_resources, size_t count)
+{
+ int i;
+ int index = *resource_cnt;
+
+ for (i = 0; i < count; i++) {
+ uintptr_t base;
+ size_t size;
+
+ size = sa_fixed_resources[i].size;
+ base = sa_fixed_resources[i].base;
+
+ mmio_resource(dev, index++, base / KiB, size / KiB);
+ }
+
+ *resource_cnt = index;
+}
+
+/*
+ * DRAM memory mapped register
+ *
+ * TOUUD: This 64 bit register defines the Top of Upper Usable DRAM
+ * TOLUD: This 32 bit register defines the Top of Low Usable DRAM
+ * BGSM: This register contains the base address of stolen DRAM memory for GTT
+ * TSEG: This register contains the base address of TSEG DRAM memory
+ */
+static const struct sa_mem_map_descriptor sa_memory_map[MAX_MAP_ENTRIES] = {
+ { TOUUD, true, "TOUUD" },
+ { TOLUD, false, "TOLUD" },
+ { BGSM, false, "BGSM" },
+ { TSEG, false, "TSEG" },
+};
+
+/* Read DRAM memory map register value through PCI configuration space */
+static void sa_read_map_entry(device_t dev,
+ const struct sa_mem_map_descriptor *entry, uint64_t *result)
+{
+ uint64_t value = 0;
+
+ if (entry->is_64_bit) {
+ value = pci_read_config32(dev, entry->reg + 4);
+ value <<= 32;
+ }
+
+ value |= pci_read_config32(dev, entry->reg);
+ /* All registers are on a 1MiB granularity. */
+ value = ALIGN_DOWN(value, 1 * MiB);
+
+ *result = value;
+}
+
+static void sa_get_mem_map(struct device *dev, uint64_t *values)
+{
+ int i;
+ for (i = 0; i < MAX_MAP_ENTRIES; i++)
+ sa_read_map_entry(dev, &sa_memory_map[i], &values[i]);
+}
+
+/*
+ * Get DPR size incase CONFIG_SA_ENABLE_DPR is selected by SoC.
+ */
+static size_t get_dpr_size(void)
+{
+ uintptr_t dpr_reg;
+ size_t size = 0;
/*
- * The PCIEXBAR is assumed to live in the memory mapped IO space under
- * 4GiB.
+ * DMA Protected Range can be reserved below TSEG for PCODE patch
+ * or TXT/BootGuard related data. Rather than report a base address
+ * the DPR register reports the TOP of the region, which is the same
+ * as TSEG base. The region size is reported in MiB in bits 11:4.
*/
- reg = 0;
- pci_io_write_config32(SA_DEV_ROOT, PCIEXBAR + 4, reg);
-
- /* Get PCI Express Region Length */
- switch (CONFIG_SA_PCIEX_LENGTH) {
- case 256 * MiB:
- pciexbar_length = PCIEXBAR_LENGTH_256MB;
- break;
- case 128 * MiB:
- pciexbar_length = PCIEXBAR_LENGTH_128MB;
- break;
- case 64 * MiB:
- pciexbar_length = PCIEXBAR_LENGTH_64MB;
- break;
- default:
- pciexbar_length = PCIEXBAR_LENGTH_256MB;
- }
- reg = CONFIG_MMCONF_BASE_ADDRESS | (pciexbar_length << 1)
- | PCIEXBAR_PCIEXBAREN;
- pci_io_write_config32(SA_DEV_ROOT, PCIEXBAR, reg);
+ dpr_reg = pci_read_config32(SA_DEV_ROOT, DPR);
+ if (dpr_reg & DPR_EPM)
+ size = (dpr_reg & DPR_SIZE_MASK) << 16;
+
+ return size;
+}
+/*
+ * These are the host memory ranges that should be added:
+ * - 0 -> 0xa0000: cacheable
+ * - 0xc0000 -> top_of_ram : cacheable
+ * - top_of_ram -> TSEG - DPR: uncacheable
+ * - TESG - DPR -> BGSM: cacheable with standard MTRRs and reserved
+ * - BGSM -> TOLUD: not cacheable with standard MTRRs and reserved
+ * - 4GiB -> TOUUD: cacheable
+ *
+ * The default SMRAM space is reserved so that the range doesn't
+ * have to be saved during S3 Resume. Once marked reserved the OS
+ * cannot use the memory. This is a bit of an odd place to reserve
+ * the region, but the CPU devices don't have dev_ops->read_resources()
+ * called on them.
+ *
+ * The range 0xa0000 -> 0xc0000 does not have any resources
+ * associated with it to handle legacy VGA memory. If this range
+ * is not omitted the mtrr code will setup the area as cacheable
+ * causing VGA access to not work.
+ *
+ * The TSEG region is mapped as cacheable so that one can perform
+ * SMRAM relocation faster. Once the SMRR is enabled the SMRR takes
+ * precedence over the existing MTRRs covering this region.
+ *
+ * It should be noted that cacheable entry types need to be added in
+ * order. The reason is that the current MTRR code assumes this and
+ * falls over itself if it isn't.
+ *
+ * The resource index starts low and should not meet or exceed
+ * PCI_BASE_ADDRESS_0.
+ */
+static void sa_add_dram_resources(struct device *dev, int *resource_count)
+{
+ uintptr_t base_k, touud_k;
+ size_t dpr_size = 0, size_k;
+ uint64_t sa_map_values[MAX_MAP_ENTRIES];
+ uintptr_t top_of_ram;
+ int index = *resource_count;
+
+ if (IS_ENABLED(CONFIG_SA_ENABLE_DPR))
+ dpr_size = get_dpr_size();
+
+ top_of_ram = (uintptr_t)cbmem_top();
+
+ /* 0 - > 0xa0000 */
+ base_k = 0;
+ size_k = (0xa0000 / KiB) - base_k;
+ ram_resource(dev, index++, base_k, size_k);
+
+ /* 0xc0000 -> top_of_ram */
+ base_k = 0xc0000 / KiB;
+ size_k = (top_of_ram / KiB) - base_k;
+ ram_resource(dev, index++, base_k, size_k);
+
+ sa_get_mem_map(dev, &sa_map_values[0]);
+
+ /* top_of_ram -> TSEG - DPR */
+ base_k = top_of_ram;
+ size_k = sa_map_values[SA_TSEG_REG] - dpr_size - base_k;
+ mmio_resource(dev, index++, base_k / KiB, size_k / KiB);
+
+ /* TSEG - DPR -> BGSM */
+ base_k = sa_map_values[SA_TSEG_REG] - dpr_size;
+ size_k = sa_map_values[SA_BGSM_REG] - base_k;
+ reserved_ram_resource(dev, index++, base_k / KiB, size_k / KiB);
+
+ /* BGSM -> TOLUD */
+ base_k = sa_map_values[SA_BGSM_REG];
+ size_k = sa_map_values[SA_TOLUD_REG] - base_k;
+ mmio_resource(dev, index++, base_k / KiB, size_k / KiB);
+
+ /* 4GiB -> TOUUD */
+ base_k = 4 * (GiB / KiB); /* 4GiB */
+ touud_k = sa_map_values[SA_TOUUD_REG] / KiB;
+ size_k = touud_k - base_k;
+ if (touud_k > base_k)
+ ram_resource(dev, index++, base_k, size_k);
+
+ /*
+ * Reserve everything between A segment and 1MB:
+ *
+ * 0xa0000 - 0xbffff: legacy VGA
+ * 0xc0000 - 0xfffff: RAM
+ */
+ mmio_resource(dev, index++, 0xa0000 / KiB, (0xc0000 - 0xa0000) / KiB);
+ reserved_ram_resource(dev, index++, 0xc0000 / KiB,
+ (1*MiB - 0xc0000) / KiB);
+
+ *resource_count = index;
+}
+
+static bool is_imr_enabled(uint32_t imr_base_reg)
+{
+ return !!(imr_base_reg & (1 << 31));
+}
+
+static void imr_resource(device_t dev, int idx, uint32_t base, uint32_t mask)
+{
+ uint32_t base_k, size_k;
+ /* Bits 28:0 encode the base address bits 38:10, hence the KiB unit. */
+ base_k = (base & 0x0fffffff);
+ /* Bits 28:0 encode the AND mask used for comparison, in KiB. */
+ size_k = ((~mask & 0x0fffffff) + 1);
/*
- * TSEG defines the base of SMM range. BIOS determines the base
- * of TSEG memory which must be at or below Graphics base of GTT
- * Stolen memory, hence its better to clear TSEG register early
- * to avoid power on default non-zero value (if any).
+ * IMRs sit in lower DRAM. Mark them cacheable, otherwise we run
+ * out of MTRRs. Memory reserved by IMRs is not usable for host
+ * so mark it reserved.
*/
- pci_write_config32(SA_DEV_ROOT, TSEG, 0);
+ reserved_ram_resource(dev, idx, base_k, size_k);
}
+/*
+ * Add IMR ranges that hang off the host bridge/memory
+ * controller device in case CONFIG_SA_ENABLE_IMR is selected by SoC.
+ */
+static void sa_add_imr_resources(struct device *dev, int *resource_cnt)
+{
+ size_t i, imr_offset;
+ uint32_t base, mask;
+ int index = *resource_cnt;
+
+ for (i = 0; i < MCH_NUM_IMRS; i++) {
+ imr_offset = i * MCH_IMR_PITCH;
+ base = MCHBAR32(imr_offset + MCH_IMR0_BASE);
+ mask = MCHBAR32(imr_offset + MCH_IMR0_MASK);
+
+ if (is_imr_enabled(base))
+ imr_resource(dev, index++, base, mask);
+ }
+
+ *resource_cnt = index;
+}
+
+static void systemagent_read_resources(struct device *dev)
+{
+ int index = 0;
+
+ /* Read standard PCI resources. */
+ pci_dev_read_resources(dev);
+
+ /* Add all fixed MMIO resources. */
+ soc_add_fixed_mmio_resources(dev, &index);
+ /* Calculate and add DRAM resources. */
+ sa_add_dram_resources(dev, &index);
+ if (IS_ENABLED(CONFIG_SA_ENABLE_IMR))
+ /* Add the isolated memory ranges (IMRs). */
+ sa_add_imr_resources(dev, &index);
+}
+
+void enable_power_aware_intr(void)
+{
+ uint8_t pair;
+
+ /* Enable Power Aware Interrupt Routing */
+ pair = MCHBAR8(MCH_PAIR);
+ pair &= ~0x7; /* Clear 2:0 */
+ pair |= 0x4; /* Fixed Priority */
+ MCHBAR8(MCH_PAIR) = pair;
+}
+
+static struct device_operations systemagent_ops = {
+ .read_resources = &systemagent_read_resources,
+ .set_resources = &pci_dev_set_resources,
+ .enable_resources = &pci_dev_enable_resources,
+ .init = soc_systemagent_init,
+};
+
+static const unsigned short systemagent_ids[] = {
+ PCI_DEVICE_ID_INTEL_GLK_NB,
+ PCI_DEVICE_ID_INTEL_APL_NB,
+ PCI_DEVICE_ID_INTEL_SKL_ID_U,
+ PCI_DEVICE_ID_INTEL_SKL_ID_Y,
+ PCI_DEVICE_ID_INTEL_SKL_ID_ULX,
+ PCI_DEVICE_ID_INTEL_SKL_ID_H,
+ PCI_DEVICE_ID_INTEL_SKL_ID_H_EM,
+ PCI_DEVICE_ID_INTEL_KBL_ID_U,
+ PCI_DEVICE_ID_INTEL_KBL_ID_Y,
+ PCI_DEVICE_ID_INTEL_KBL_ID_H,
+ PCI_DEVICE_ID_INTEL_KBL_U_R,
+ 0
+};
+
+static const struct pci_driver systemagent_driver __pci_driver = {
+ .ops = &systemagent_ops,
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .devices = systemagent_ids
+};
diff --git a/src/soc/intel/common/block/systemagent/systemagent_def.h b/src/soc/intel/common/block/systemagent/systemagent_def.h
new file mode 100644
index 0000000000..29ce9ecd76
--- /dev/null
+++ b/src/soc/intel/common/block/systemagent/systemagent_def.h
@@ -0,0 +1,82 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2017 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef SOC_INTEL_COMMON_BLOCK_SA_DEF_H
+#define SOC_INTEL_COMMON_BLOCK_SA_DEF_H
+
+
+/* Device 0:0.0 PCI configuration space */
+
+/* DPR register incase CONFIG_SA_ENABLE_DPR is selected by SoC */
+#define DPR 0x5c
+#define DPR_EPM (1 << 2)
+#define DPR_PRS (1 << 1)
+#define DPR_SIZE_MASK 0xff0
+
+#define PCIEXBAR_LENGTH_64MB 2
+#define PCIEXBAR_LENGTH_128MB 1
+#define PCIEXBAR_LENGTH_256MB 0
+#define PCIEXBAR_PCIEXBAREN (1 << 0)
+
+#define PAM0 0x80
+#define PAM1 0x81
+#define PAM2 0x82
+#define PAM3 0x83
+#define PAM4 0x84
+#define PAM5 0x85
+#define PAM6 0x86
+
+/* Device 0:0.0 MMIO space */
+#define MCH_PAIR 0x5418
+
+/*
+ * IMR register incase CONFIG_SA_ENABLE_IMR is selected by SoC.
+ *
+ * IMR registers are found under MCHBAR.
+ */
+#define MCH_IMR0_BASE 0x6870
+#define MCH_IMR0_MASK 0x6874
+#define MCH_IMR_PITCH 0x20
+#define MCH_NUM_IMRS 20
+
+/*
+ * System Memory Map Registers
+ * - top_of_ram -> TSEG - DPR: uncacheable
+ * - TESG - DPR -> BGSM: cacheable with standard MTRRs and reserved
+ * - BGSM -> TOLUD: not cacheable with standard MTRRs and reserved
+ * - 4GiB -> TOUUD: cacheable
+ */
+enum {
+ SA_TOUUD_REG,
+ SA_TOLUD_REG,
+ SA_BGSM_REG,
+ SA_TSEG_REG,
+ /* Must be last. */
+ MAX_MAP_ENTRIES
+};
+
+/*
+ * Set Fixed MMIO range
+ * REG = Either PCI configuration space registers.
+ * IS_64_BIT = If registers/offset is 64 bit.
+ * DESCRIPTION = Name of the register/offset.
+ */
+struct sa_mem_map_descriptor {
+ unsigned int reg;
+ bool is_64_bit;
+ const char *description;
+};
+
+#endif /* SOC_INTEL_COMMON_BLOCK_SA_DEF_H */
diff --git a/src/soc/intel/common/block/systemagent/systemagent_early.c b/src/soc/intel/common/block/systemagent/systemagent_early.c
new file mode 100644
index 0000000000..7cf78e7352
--- /dev/null
+++ b/src/soc/intel/common/block/systemagent/systemagent_early.c
@@ -0,0 +1,134 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2017 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <arch/io.h>
+#include <delay.h>
+#include <device/device.h>
+#include <device/pci.h>
+#include <intelblocks/systemagent.h>
+#include <soc/iomap.h>
+#include <soc/pci_devs.h>
+#include <soc/systemagent.h>
+#include "systemagent_def.h"
+#include <timer.h>
+
+#if !ENV_RAMSTAGE
+void bootblock_systemagent_early_init(void)
+{
+ uint32_t reg;
+ uint8_t pciexbar_length;
+
+ /*
+ * The PCIEXBAR is assumed to live in the memory mapped IO space under
+ * 4GiB.
+ */
+ reg = 0;
+ pci_io_write_config32(SA_DEV_ROOT, PCIEXBAR + 4, reg);
+
+ /* Get PCI Express Region Length */
+ switch (CONFIG_SA_PCIEX_LENGTH) {
+ case 256 * MiB:
+ pciexbar_length = PCIEXBAR_LENGTH_256MB;
+ break;
+ case 128 * MiB:
+ pciexbar_length = PCIEXBAR_LENGTH_128MB;
+ break;
+ case 64 * MiB:
+ pciexbar_length = PCIEXBAR_LENGTH_64MB;
+ break;
+ default:
+ pciexbar_length = PCIEXBAR_LENGTH_256MB;
+ }
+ reg = CONFIG_MMCONF_BASE_ADDRESS | (pciexbar_length << 1)
+ | PCIEXBAR_PCIEXBAREN;
+ pci_io_write_config32(SA_DEV_ROOT, PCIEXBAR, reg);
+
+ /*
+ * TSEG defines the base of SMM range. BIOS determines the base
+ * of TSEG memory which must be at or below Graphics base of GTT
+ * Stolen memory, hence its better to clear TSEG register early
+ * to avoid power on default non-zero value (if any).
+ */
+ pci_write_config32(SA_DEV_ROOT, TSEG, 0);
+}
+#endif
+
+void sa_set_pci_bar(const struct sa_mmio_descriptor *fixed_set_resources,
+ size_t count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ uintptr_t base;
+ unsigned int index;
+
+ index = fixed_set_resources[i].index;
+ /* Check if PCI BAR already enabled */
+ base = pci_read_config32(SA_DEV_ROOT, index);
+
+ /* If enabled don't program it. */
+ if (base & 0x1)
+ return;
+
+ base = fixed_set_resources[i].base;
+
+ pci_write_config32(SA_DEV_ROOT, index, base | 1);
+ }
+}
+
+/*
+ * There are special BARs that actually are programmed in the MCHBAR. These
+ * Intel special features, but they do consume resources that need to be
+ * accounted for.
+ */
+void sa_set_mch_bar(const struct sa_mmio_descriptor *fixed_set_resources,
+ size_t count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ uintptr_t base;
+ unsigned int index;
+
+ base = fixed_set_resources[i].base;
+ index = fixed_set_resources[i].index;
+ write32((void *)(MCH_BASE_ADDRESS + index), base | 1);
+ }
+}
+
+void enable_pam_region(void)
+{
+ /* All read and writes in this region are serviced by DRAM */
+ pci_write_config8(SA_DEV_ROOT, PAM0, 0x30);
+ pci_write_config8(SA_DEV_ROOT, PAM1, 0x33);
+ pci_write_config8(SA_DEV_ROOT, PAM2, 0x33);
+ pci_write_config8(SA_DEV_ROOT, PAM3, 0x33);
+ pci_write_config8(SA_DEV_ROOT, PAM4, 0x33);
+ pci_write_config8(SA_DEV_ROOT, PAM5, 0x33);
+ pci_write_config8(SA_DEV_ROOT, PAM6, 0x33);
+}
+
+void enable_bios_reset_cpl(void)
+{
+ u8 bios_reset_cpl;
+
+ /*
+ * Set bits 0+1 of BIOS_RESET_CPL to indicate to the CPU
+ * that BIOS has initialized memory and power management
+ */
+ bios_reset_cpl = MCHBAR8(BIOS_RESET_CPL);
+ bios_reset_cpl |= 3;
+ MCHBAR8(BIOS_RESET_CPL) = bios_reset_cpl;
+}