[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-ppc] [PULL 27/49] ppc/pnv: add a PnvCore object
From: |
David Gibson |
Subject: |
[Qemu-ppc] [PULL 27/49] ppc/pnv: add a PnvCore object |
Date: |
Wed, 26 Oct 2016 22:42:31 +1100 |
From: Cédric Le Goater <address@hidden>
This is largy inspired by sPAPRCPUCore with some simplification, no
hotplug for instance. A set of PnvCore objects is added to the PnvChip
and the device tree is populated looping on these cores.
Real HW cpu ids are now generated depending on the chip cpu model, the
chip id and a core mask. The id is propagated to the CPU object, using
properties, to set the SPR_PIR (Processor Identification Register)
Signed-off-by: Cédric Le Goater <address@hidden>
Signed-off-by: David Gibson <address@hidden>
---
hw/ppc/Makefile.objs | 2 +-
hw/ppc/pnv.c | 189 +++++++++++++++++++++++++++++++++++++++++++++-
hw/ppc/pnv_core.c | 182 ++++++++++++++++++++++++++++++++++++++++++++
include/hw/ppc/pnv.h | 2 +
include/hw/ppc/pnv_core.h | 48 ++++++++++++
5 files changed, 421 insertions(+), 2 deletions(-)
create mode 100644 hw/ppc/pnv_core.c
create mode 100644 include/hw/ppc/pnv_core.h
diff --git a/hw/ppc/Makefile.objs b/hw/ppc/Makefile.objs
index 8105db7..f8c7d1d 100644
--- a/hw/ppc/Makefile.objs
+++ b/hw/ppc/Makefile.objs
@@ -6,7 +6,7 @@ obj-$(CONFIG_PSERIES) += spapr_hcall.o spapr_iommu.o
spapr_rtas.o
obj-$(CONFIG_PSERIES) += spapr_pci.o spapr_rtc.o spapr_drc.o spapr_rng.o
obj-$(CONFIG_PSERIES) += spapr_cpu_core.o
# IBM PowerNV
-obj-$(CONFIG_POWERNV) += pnv.o
+obj-$(CONFIG_POWERNV) += pnv.o pnv_core.o
ifeq ($(CONFIG_PCI)$(CONFIG_PSERIES)$(CONFIG_LINUX), yyy)
obj-y += spapr_pci_vfio.o
endif
diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c
index 825d28c..3413107 100644
--- a/hw/ppc/pnv.c
+++ b/hw/ppc/pnv.c
@@ -27,6 +27,7 @@
#include "hw/ppc/fdt.h"
#include "hw/ppc/ppc.h"
#include "hw/ppc/pnv.h"
+#include "hw/ppc/pnv_core.h"
#include "hw/loader.h"
#include "exec/address-spaces.h"
#include "qemu/cutils.h"
@@ -75,12 +76,160 @@ static void powernv_populate_memory_node(void *fdt, int
chip_id, hwaddr start,
_FDT((fdt_setprop_cell(fdt, off, "ibm,chip-id", chip_id)));
}
+static int get_cpus_node(void *fdt)
+{
+ int cpus_offset = fdt_path_offset(fdt, "/cpus");
+
+ if (cpus_offset < 0) {
+ cpus_offset = fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"),
+ "cpus");
+ if (cpus_offset) {
+ _FDT((fdt_setprop_cell(fdt, cpus_offset, "#address-cells", 0x1)));
+ _FDT((fdt_setprop_cell(fdt, cpus_offset, "#size-cells", 0x0)));
+ }
+ }
+ _FDT(cpus_offset);
+ return cpus_offset;
+}
+
+/*
+ * The PowerNV cores (and threads) need to use real HW ids and not an
+ * incremental index like it has been done on other platforms. This HW
+ * id is stored in the CPU PIR, it is used to create cpu nodes in the
+ * device tree, used in XSCOM to address cores and in interrupt
+ * servers.
+ */
+static void powernv_create_core_node(PnvChip *chip, PnvCore *pc, void *fdt)
+{
+ CPUState *cs = CPU(DEVICE(pc->threads));
+ DeviceClass *dc = DEVICE_GET_CLASS(cs);
+ PowerPCCPU *cpu = POWERPC_CPU(cs);
+ int smt_threads = ppc_get_compat_smt_threads(cpu);
+ CPUPPCState *env = &cpu->env;
+ PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cs);
+ uint32_t servers_prop[smt_threads];
+ int i;
+ uint32_t segs[] = {cpu_to_be32(28), cpu_to_be32(40),
+ 0xffffffff, 0xffffffff};
+ uint32_t tbfreq = PNV_TIMEBASE_FREQ;
+ uint32_t cpufreq = 1000000000;
+ uint32_t page_sizes_prop[64];
+ size_t page_sizes_prop_size;
+ const uint8_t pa_features[] = { 24, 0,
+ 0xf6, 0x3f, 0xc7, 0xc0, 0x80, 0xf0,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00 };
+ int offset;
+ char *nodename;
+ int cpus_offset = get_cpus_node(fdt);
+
+ nodename = g_strdup_printf("address@hidden", dc->fw_name, pc->pir);
+ offset = fdt_add_subnode(fdt, cpus_offset, nodename);
+ _FDT(offset);
+ g_free(nodename);
+
+ _FDT((fdt_setprop_cell(fdt, offset, "ibm,chip-id", chip->chip_id)));
+
+ _FDT((fdt_setprop_cell(fdt, offset, "reg", pc->pir)));
+ _FDT((fdt_setprop_cell(fdt, offset, "ibm,pir", pc->pir)));
+ _FDT((fdt_setprop_string(fdt, offset, "device_type", "cpu")));
+
+ _FDT((fdt_setprop_cell(fdt, offset, "cpu-version", env->spr[SPR_PVR])));
+ _FDT((fdt_setprop_cell(fdt, offset, "d-cache-block-size",
+ env->dcache_line_size)));
+ _FDT((fdt_setprop_cell(fdt, offset, "d-cache-line-size",
+ env->dcache_line_size)));
+ _FDT((fdt_setprop_cell(fdt, offset, "i-cache-block-size",
+ env->icache_line_size)));
+ _FDT((fdt_setprop_cell(fdt, offset, "i-cache-line-size",
+ env->icache_line_size)));
+
+ if (pcc->l1_dcache_size) {
+ _FDT((fdt_setprop_cell(fdt, offset, "d-cache-size",
+ pcc->l1_dcache_size)));
+ } else {
+ error_report("Warning: Unknown L1 dcache size for cpu");
+ }
+ if (pcc->l1_icache_size) {
+ _FDT((fdt_setprop_cell(fdt, offset, "i-cache-size",
+ pcc->l1_icache_size)));
+ } else {
+ error_report("Warning: Unknown L1 icache size for cpu");
+ }
+
+ _FDT((fdt_setprop_cell(fdt, offset, "timebase-frequency", tbfreq)));
+ _FDT((fdt_setprop_cell(fdt, offset, "clock-frequency", cpufreq)));
+ _FDT((fdt_setprop_cell(fdt, offset, "ibm,slb-size", env->slb_nr)));
+ _FDT((fdt_setprop_string(fdt, offset, "status", "okay")));
+ _FDT((fdt_setprop(fdt, offset, "64-bit", NULL, 0)));
+
+ if (env->spr_cb[SPR_PURR].oea_read) {
+ _FDT((fdt_setprop(fdt, offset, "ibm,purr", NULL, 0)));
+ }
+
+ if (env->mmu_model & POWERPC_MMU_1TSEG) {
+ _FDT((fdt_setprop(fdt, offset, "ibm,processor-segment-sizes",
+ segs, sizeof(segs))));
+ }
+
+ /* Advertise VMX/VSX (vector extensions) if available
+ * 0 / no property == no vector extensions
+ * 1 == VMX / Altivec available
+ * 2 == VSX available */
+ if (env->insns_flags & PPC_ALTIVEC) {
+ uint32_t vmx = (env->insns_flags2 & PPC2_VSX) ? 2 : 1;
+
+ _FDT((fdt_setprop_cell(fdt, offset, "ibm,vmx", vmx)));
+ }
+
+ /* Advertise DFP (Decimal Floating Point) if available
+ * 0 / no property == no DFP
+ * 1 == DFP available */
+ if (env->insns_flags2 & PPC2_DFP) {
+ _FDT((fdt_setprop_cell(fdt, offset, "ibm,dfp", 1)));
+ }
+
+ page_sizes_prop_size = ppc_create_page_sizes_prop(env, page_sizes_prop,
+ sizeof(page_sizes_prop));
+ if (page_sizes_prop_size) {
+ _FDT((fdt_setprop(fdt, offset, "ibm,segment-page-sizes",
+ page_sizes_prop, page_sizes_prop_size)));
+ }
+
+ _FDT((fdt_setprop(fdt, offset, "ibm,pa-features",
+ pa_features, sizeof(pa_features))));
+
+ if (cpu->cpu_version) {
+ _FDT((fdt_setprop_cell(fdt, offset, "cpu-version", cpu->cpu_version)));
+ }
+
+ /* Build interrupt servers properties */
+ for (i = 0; i < smt_threads; i++) {
+ servers_prop[i] = cpu_to_be32(pc->pir + i);
+ }
+ _FDT((fdt_setprop(fdt, offset, "ibm,ppc-interrupt-server#s",
+ servers_prop, sizeof(servers_prop))));
+}
+
static void powernv_populate_chip(PnvChip *chip, void *fdt)
{
+ PnvChipClass *pcc = PNV_CHIP_GET_CLASS(chip);
+ char *typename = pnv_core_typename(pcc->cpu_model);
+ size_t typesize = object_type_get_instance_size(typename);
+ int i;
+
+ for (i = 0; i < chip->nr_cores; i++) {
+ PnvCore *pnv_core = PNV_CORE(chip->cores + i * typesize);
+
+ powernv_create_core_node(chip, pnv_core, fdt);
+ }
+
if (chip->ram_size) {
powernv_populate_memory_node(fdt, chip->chip_id, chip->ram_start,
chip->ram_size);
}
+ g_free(typename);
}
static void *powernv_create_fdt(MachineState *machine)
@@ -410,13 +559,51 @@ static void pnv_chip_realize(DeviceState *dev, Error
**errp)
{
PnvChip *chip = PNV_CHIP(dev);
Error *error = NULL;
+ PnvChipClass *pcc = PNV_CHIP_GET_CLASS(chip);
+ char *typename = pnv_core_typename(pcc->cpu_model);
+ size_t typesize = object_type_get_instance_size(typename);
+ int i, core_hwid;
+
+ if (!object_class_by_name(typename)) {
+ error_setg(errp, "Unable to find PowerNV CPU Core '%s'", typename);
+ return;
+ }
- /* Early checks on the core settings */
+ /* Cores */
pnv_chip_core_sanitize(chip, &error);
if (error) {
error_propagate(errp, error);
return;
}
+
+ chip->cores = g_malloc0(typesize * chip->nr_cores);
+
+ for (i = 0, core_hwid = 0; (core_hwid < sizeof(chip->cores_mask) * 8)
+ && (i < chip->nr_cores); core_hwid++) {
+ char core_name[32];
+ void *pnv_core = chip->cores + i * typesize;
+
+ if (!(chip->cores_mask & (1ull << core_hwid))) {
+ continue;
+ }
+
+ object_initialize(pnv_core, typesize, typename);
+ snprintf(core_name, sizeof(core_name), "core[%d]", core_hwid);
+ object_property_add_child(OBJECT(chip), core_name, OBJECT(pnv_core),
+ &error_fatal);
+ object_property_set_int(OBJECT(pnv_core), smp_threads, "nr-threads",
+ &error_fatal);
+ object_property_set_int(OBJECT(pnv_core), core_hwid,
+ CPU_CORE_PROP_CORE_ID, &error_fatal);
+ object_property_set_int(OBJECT(pnv_core),
+ pcc->core_pir(chip, core_hwid),
+ "pir", &error_fatal);
+ object_property_set_bool(OBJECT(pnv_core), true, "realized",
+ &error_fatal);
+ object_unref(OBJECT(pnv_core));
+ i++;
+ }
+ g_free(typename);
}
static Property pnv_chip_properties[] = {
diff --git a/hw/ppc/pnv_core.c b/hw/ppc/pnv_core.c
new file mode 100644
index 0000000..04713ca
--- /dev/null
+++ b/hw/ppc/pnv_core.c
@@ -0,0 +1,182 @@
+/*
+ * QEMU PowerPC PowerNV CPU Core model
+ *
+ * Copyright (c) 2016, IBM Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "qemu/osdep.h"
+#include "sysemu/sysemu.h"
+#include "qapi/error.h"
+#include "target-ppc/cpu.h"
+#include "hw/ppc/ppc.h"
+#include "hw/ppc/pnv.h"
+#include "hw/ppc/pnv_core.h"
+
+static void powernv_cpu_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+ CPUPPCState *env = &cpu->env;
+
+ cpu_reset(cs);
+
+ /*
+ * the skiboot firmware elects a primary thread to initialize the
+ * system and it can be any.
+ */
+ env->gpr[3] = PNV_FDT_ADDR;
+ env->nip = 0x10;
+ env->msr |= MSR_HVB; /* Hypervisor mode */
+}
+
+static void powernv_cpu_init(PowerPCCPU *cpu, Error **errp)
+{
+ CPUPPCState *env = &cpu->env;
+ int core_pir;
+ int thread_index = 0; /* TODO: TCG supports only one thread */
+ ppc_spr_t *pir = &env->spr_cb[SPR_PIR];
+
+ core_pir = object_property_get_int(OBJECT(cpu), "core-pir", &error_abort);
+
+ /*
+ * The PIR of a thread is the core PIR + the thread index. We will
+ * need to find a way to get the thread index when TCG supports
+ * more than 1. We could use the object name ?
+ */
+ pir->default_value = core_pir + thread_index;
+
+ /* Set time-base frequency to 512 MHz */
+ cpu_ppc_tb_init(env, PNV_TIMEBASE_FREQ);
+
+ qemu_register_reset(powernv_cpu_reset, cpu);
+}
+
+static void pnv_core_realize_child(Object *child, Error **errp)
+{
+ Error *local_err = NULL;
+ CPUState *cs = CPU(child);
+ PowerPCCPU *cpu = POWERPC_CPU(cs);
+
+ object_property_set_bool(child, true, "realized", &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ powernv_cpu_init(cpu, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+}
+
+static void pnv_core_realize(DeviceState *dev, Error **errp)
+{
+ PnvCore *pc = PNV_CORE(OBJECT(dev));
+ CPUCore *cc = CPU_CORE(OBJECT(dev));
+ PnvCoreClass *pcc = PNV_CORE_GET_CLASS(OBJECT(dev));
+ const char *typename = object_class_get_name(pcc->cpu_oc);
+ size_t size = object_type_get_instance_size(typename);
+ Error *local_err = NULL;
+ void *obj;
+ int i, j;
+ char name[32];
+
+ pc->threads = g_malloc0(size * cc->nr_threads);
+ for (i = 0; i < cc->nr_threads; i++) {
+ obj = pc->threads + i * size;
+
+ object_initialize(obj, size, typename);
+
+ snprintf(name, sizeof(name), "thread[%d]", i);
+ object_property_add_child(OBJECT(pc), name, obj, &local_err);
+ object_property_add_alias(obj, "core-pir", OBJECT(pc),
+ "pir", &local_err);
+ if (local_err) {
+ goto err;
+ }
+ object_unref(obj);
+ }
+
+ for (j = 0; j < cc->nr_threads; j++) {
+ obj = pc->threads + j * size;
+
+ pnv_core_realize_child(obj, &local_err);
+ if (local_err) {
+ goto err;
+ }
+ }
+ return;
+
+err:
+ while (--i >= 0) {
+ obj = pc->threads + i * size;
+ object_unparent(obj);
+ }
+ g_free(pc->threads);
+ error_propagate(errp, local_err);
+}
+
+static Property pnv_core_properties[] = {
+ DEFINE_PROP_UINT32("pir", PnvCore, pir, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pnv_core_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ PnvCoreClass *pcc = PNV_CORE_CLASS(oc);
+
+ dc->realize = pnv_core_realize;
+ dc->props = pnv_core_properties;
+ pcc->cpu_oc = cpu_class_by_name(TYPE_POWERPC_CPU, data);
+}
+
+static const TypeInfo pnv_core_info = {
+ .name = TYPE_PNV_CORE,
+ .parent = TYPE_CPU_CORE,
+ .instance_size = sizeof(PnvCore),
+ .class_size = sizeof(PnvCoreClass),
+ .abstract = true,
+};
+
+static const char *pnv_core_models[] = {
+ "POWER8E", "POWER8", "POWER8NVL", "POWER9"
+};
+
+static void pnv_core_register_types(void)
+{
+ int i ;
+
+ type_register_static(&pnv_core_info);
+ for (i = 0; i < ARRAY_SIZE(pnv_core_models); ++i) {
+ TypeInfo ti = {
+ .parent = TYPE_PNV_CORE,
+ .instance_size = sizeof(PnvCore),
+ .class_init = pnv_core_class_init,
+ .class_data = (void *) pnv_core_models[i],
+ };
+ ti.name = pnv_core_typename(pnv_core_models[i]);
+ type_register(&ti);
+ g_free((void *)ti.name);
+ }
+}
+
+type_init(pnv_core_register_types)
+
+char *pnv_core_typename(const char *model)
+{
+ return g_strdup_printf(TYPE_PNV_CORE "-%s", model);
+}
diff --git a/include/hw/ppc/pnv.h b/include/hw/ppc/pnv.h
index b7987f8..cec869c 100644
--- a/include/hw/ppc/pnv.h
+++ b/include/hw/ppc/pnv.h
@@ -47,6 +47,7 @@ typedef struct PnvChip {
uint32_t nr_cores;
uint64_t cores_mask;
+ void *cores;
} PnvChip;
typedef struct PnvChipClass {
@@ -103,5 +104,6 @@ typedef struct PnvMachineState {
} PnvMachineState;
#define PNV_FDT_ADDR 0x01000000
+#define PNV_TIMEBASE_FREQ 512000000ULL
#endif /* _PPC_PNV_H */
diff --git a/include/hw/ppc/pnv_core.h b/include/hw/ppc/pnv_core.h
new file mode 100644
index 0000000..a151e28
--- /dev/null
+++ b/include/hw/ppc/pnv_core.h
@@ -0,0 +1,48 @@
+/*
+ * QEMU PowerPC PowerNV CPU Core model
+ *
+ * Copyright (c) 2016, IBM Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef _PPC_PNV_CORE_H
+#define _PPC_PNV_CORE_H
+
+#include "hw/cpu/core.h"
+
+#define TYPE_PNV_CORE "powernv-cpu-core"
+#define PNV_CORE(obj) \
+ OBJECT_CHECK(PnvCore, (obj), TYPE_PNV_CORE)
+#define PNV_CORE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(PnvCoreClass, (klass), TYPE_PNV_CORE)
+#define PNV_CORE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(PnvCoreClass, (obj), TYPE_PNV_CORE)
+
+typedef struct PnvCore {
+ /*< private >*/
+ CPUCore parent_obj;
+
+ /*< public >*/
+ void *threads;
+ uint32_t pir;
+} PnvCore;
+
+typedef struct PnvCoreClass {
+ DeviceClass parent_class;
+ ObjectClass *cpu_oc;
+} PnvCoreClass;
+
+extern char *pnv_core_typename(const char *model);
+
+#endif /* _PPC_PNV_CORE_H */
--
2.7.4
- [Qemu-ppc] [PULL 37/49] pseries: Consolidate RTAS loading, (continued)
- [Qemu-ppc] [PULL 37/49] pseries: Consolidate RTAS loading, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 16/49] ppc/xics: add a xics_set_nr_servers common routine, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 13/49] nvram: Rename openbios_firmware_abi.h into sun_nvram.h, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 17/49] ppc/xics: add a XICSState backlink in ICPState, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 15/49] target-ppc: implement xxbr[qdwh] instruction, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 29/49] ppc/pnv: add XSCOM handlers to PnvCore, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 34/49] pseries: Remove rtas_addr and fdt_addr fields from machinestate, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 32/49] target-ppc: add vmul10[u, eu, cu, ecu]q instructions, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 42/49] pseries: Move /hypervisor node construction to fdt_build_fdt(), David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 39/49] pseries: Consolidate construction of /chosen device tree node, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 27/49] ppc/pnv: add a PnvCore object,
David Gibson <=
- [Qemu-ppc] [PULL 36/49] pseries: Move adding of fdt reserve map entries, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 38/49] pseries: Move construction of /interrupt-controller fdt node, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 26/49] ppc/pnv: add a PIR handler to PnvChip, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 40/49] pseries: Consolidate construction of /rtas device tree node, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 43/49] pseries: Consolidate construction of /vdevice device tree node, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 25/49] ppc/pnv: add a core mask to PnvChip, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 44/49] pseries: Remove spapr_create_fdt_skel(), David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 47/49] spapr: add option vector handling in CAS-generated resets, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 28/49] ppc/pnv: add XSCOM infrastructure, David Gibson, 2016/10/26
- [Qemu-ppc] [PULL 46/49] spapr_hcall: use spapr_ovec_* interfaces for CAS options, David Gibson, 2016/10/26