From 773b25c55c7428b64d21b23a6b08fc629a665ca5 Mon Sep 17 00:00:00 2001
From: zhanghailiang <zhang.zhanghailiang@huawei.com>
Date: Mon, 29 Jul 2019 09:54:43 +0800
Subject: [PATCH] hw/arm: expose host CPU frequency info to guest

On ARM64, CPU frequency is fetched by ACPI CPPC, so we add virtual
CPPC registers and ACPI _CPC objects.

The default frequency is set to the nominal frequency of Hi1616, which
will not support CPPC in future. On Hi1620 we are fetching the value
from Host CPPC sys file.

All performance data are set to the same value for we don't support
guest initiating performance scaling.

We don't emulate performance counters and simply return 1 for all
counter readings, and guest Linux should fall back to use the desired
performance value as the current performance.

Signed-off-by: Heyi Guo <guoheyi@huawei.com>
Signed-off-by: zhanghailiang <zhang.zhanghailiang@huawei.com>
---
 default-configs/aarch64-softmmu.mak |   1 +
 hw/acpi/Makefile.objs               |   1 +
 hw/acpi/aml-build.c                 |  22 +++
 hw/acpi/cpufreq.c                   | 278 ++++++++++++++++++++++++++++
 hw/arm/virt-acpi-build.c            |  78 +++++++-
 hw/arm/virt.c                       |  13 ++
 hw/char/Kconfig                     |   4 +
 include/hw/acpi/acpi-defs.h         |  38 ++++
 include/hw/acpi/aml-build.h         |   3 +
 include/hw/arm/virt.h               |   1 +
 10 files changed, 437 insertions(+), 2 deletions(-)
 create mode 100644 hw/acpi/cpufreq.c

diff --git a/default-configs/aarch64-softmmu.mak b/default-configs/aarch64-softmmu.mak
index 4ea9add0..37399c14 100644
--- a/default-configs/aarch64-softmmu.mak
+++ b/default-configs/aarch64-softmmu.mak
@@ -10,3 +10,4 @@ CONFIG_XLNX_ZYNQMP=y
 CONFIG_XLNX_ZYNQMP_ARM=y
 CONFIG_XLNX_VERSAL=y
 CONFIG_ARM_SMMUV3=y
+CONFIG_CPUFREQ=y
diff --git a/hw/acpi/Makefile.objs b/hw/acpi/Makefile.objs
index 2d46e378..60979db9 100644
--- a/hw/acpi/Makefile.objs
+++ b/hw/acpi/Makefile.objs
@@ -12,6 +12,7 @@ common-obj-y += acpi_interface.o
 common-obj-y += bios-linker-loader.o
 common-obj-y += aml-build.o
 common-obj-$(CONFIG_TPM) += tpm.o
+common-obj-$(CONFIG_CPUFREQ) += cpufreq.o
 
 common-obj-$(CONFIG_IPMI) += ipmi.o
 common-obj-$(call lnot,$(CONFIG_IPMI)) += ipmi-stub.o
diff --git a/hw/acpi/aml-build.c b/hw/acpi/aml-build.c
index 555c24f2..73f97751 100644
--- a/hw/acpi/aml-build.c
+++ b/hw/acpi/aml-build.c
@@ -1369,6 +1369,28 @@ Aml *aml_sleep(uint64_t msec)
     return var;
 }
 
+/* ACPI 5.0b: 6.4.3.7 Generic Register Descriptor */
+Aml *aml_generic_register(AmlRegionSpace rs, uint8_t reg_width,
+                          uint8_t reg_offset, AmlAccessType type, uint64_t addr)
+{
+    int i;
+    Aml *var = aml_alloc();
+    build_append_byte(var->buf, 0x82); /* Generic Register Descriptor */
+    build_append_byte(var->buf, 0x0C); /* Length, bits[7:0] value = 0x0C */
+    build_append_byte(var->buf, 0);    /* Length, bits[15:8] value = 0 */
+    build_append_byte(var->buf, rs);   /* Address Space ID */
+    build_append_byte(var->buf, reg_width);   /* Register Bit Width */
+    build_append_byte(var->buf, reg_offset);  /* Register Bit Offset */
+    build_append_byte(var->buf, type);        /* Access Size */
+
+    /* Register address */
+    for (i = 0; i < 8; i++) {
+        build_append_byte(var->buf, extract64(addr, i * 8, 8));
+    }
+
+    return var;
+}
+
 static uint8_t Hex2Byte(const char *src)
 {
     int hi, lo;
diff --git a/hw/acpi/cpufreq.c b/hw/acpi/cpufreq.c
new file mode 100644
index 00000000..c123a22b
--- /dev/null
+++ b/hw/acpi/cpufreq.c
@@ -0,0 +1,278 @@
+/*
+ * ACPI CPPC register device
+ *
+ * Support for showing CPU frequency in guest OS.
+ *
+ * Copyright (c) 2019 HUAWEI TECHNOLOGIES CO.,LTD.
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * 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.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "chardev/char.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "qemu/option.h"
+#include "sysemu/sysemu.h"
+#include "hw/acpi/acpi-defs.h"
+#include "qemu/cutils.h"
+#include "qemu/error-report.h"
+
+#define TYPE_CPUFREQ "cpufreq"
+#define CPUFREQ(obj) OBJECT_CHECK(CpuhzState, (obj), TYPE_CPUFREQ)
+#define NOMINAL_FREQ_FILE "/sys/devices/system/cpu/cpu0/acpi_cppc/nominal_freq"
+#define CPU_MAX_FREQ_FILE "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
+#define HZ_MAX_LENGTH 1024
+#define MAX_SUPPORT_SPACE 0x10000
+
+/*
+ * Since Hi1616 will not support CPPC, we simply use its nominal frequency as
+ * the default.
+ */
+#define DEFAULT_HZ 2400
+
+
+int cppc_regs_offset[CPPC_REG_COUNT] = {
+    [HIGHEST_PERF] = 0,
+    [NOMINAL_PERF] = 4,
+    [LOW_NON_LINEAR_PERF] = 8,
+    [LOWEST_PERF] = 12,
+    [GUARANTEED_PERF] = 16,
+    [DESIRED_PERF] = 20,
+    [MIN_PERF] = -1,
+    [MAX_PERF] = -1,
+    [PERF_REDUC_TOLERANCE] = -1,
+    [TIME_WINDOW] = -1,
+    [CTR_WRAP_TIME] = -1,
+    [REFERENCE_CTR] = 24,
+    [DELIVERED_CTR] = 32,
+    [PERF_LIMITED] = 40,
+    [ENABLE] = -1,
+    [AUTO_SEL_ENABLE] = -1,
+    [AUTO_ACT_WINDOW] = -1,
+    [ENERGY_PERF] = -1,
+    [REFERENCE_PERF] = -1,
+    [LOWEST_FREQ] = 44,
+    [NOMINAL_FREQ] = 48,
+};
+
+typedef struct CpuhzState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    uint32_t HighestPerformance;
+    uint32_t NominalPerformance;
+    uint32_t LowestNonlinearPerformance;
+    uint32_t LowestPerformance;
+    uint32_t GuaranteedPerformance;
+    uint32_t DesiredPerformance;
+    uint64_t ReferencePerformanceCounter;
+    uint64_t DeliveredPerformanceCounter;
+    uint32_t PerformanceLimited;
+    uint32_t LowestFreq;
+    uint32_t NominalFreq;
+    uint32_t reg_size;
+} CpuhzState;
+
+
+static uint64_t cpufreq_read(void *opaque, hwaddr offset,
+                           unsigned size)
+{
+    CpuhzState *s = (CpuhzState *)opaque;
+    uint64_t r;
+    uint64_t n;
+
+    if (offset >= smp_cpus * CPPC_REG_PER_CPU_STRIDE) {
+        warn_report("cpufreq_read: offset 0x%lx out of range", offset);
+        return 0;
+    }
+
+    n = offset % CPPC_REG_PER_CPU_STRIDE;
+    switch (n) {
+    case 0:
+        r = s->HighestPerformance;
+        break;
+    case 4:
+        r = s->NominalPerformance;
+        break;
+    case 8:
+        r = s->LowestNonlinearPerformance;
+        break;
+    case 12:
+        r = s->LowestPerformance;
+        break;
+    case 16:
+        r = s->GuaranteedPerformance;
+        break;
+    case 20:
+        r = s->DesiredPerformance;
+        break;
+    /*
+     * We don't have real counters and it is hard to emulate, so always set the
+     * counter value to 1 to rely on Linux to use the DesiredPerformance value
+     * directly.
+     */
+    case 24:
+        r = s->ReferencePerformanceCounter;
+        break;
+    /*
+     * Guest may still access the register by 32bit; add the process to
+     * eliminate unnecessary warnings
+     */
+    case 28:
+        r = s->ReferencePerformanceCounter >> 32;
+        break;
+    case 32:
+        r = s->DeliveredPerformanceCounter;
+        break;
+    case 36:
+        r = s->DeliveredPerformanceCounter >> 32;
+        break;
+
+    case 40:
+        r = s->PerformanceLimited;
+        break;
+    case 44:
+        r = s->LowestFreq;
+        break;
+    case 48:
+        r = s->NominalFreq;
+        break;
+    default:
+        error_printf("cpufreq_read: Bad offset 0x%lx\n", offset);
+        r = 0;
+        break;
+    }
+    return r;
+}
+
+
+static void cpufreq_write(void *opaque, hwaddr offset,
+                           uint64_t value, unsigned size)
+{
+    uint64_t n;
+
+    if (offset >= smp_cpus * CPPC_REG_PER_CPU_STRIDE) {
+        error_printf("cpufreq_write: offset 0x%lx out of range", offset);
+        return;
+    }
+
+    n = offset % CPPC_REG_PER_CPU_STRIDE;
+
+    switch (n) {
+    case 20:
+        break;
+    default:
+        error_printf("cpufreq_write: Bad offset 0x%lx\n", offset);
+    }
+}
+
+static uint32_t CPPC_Read(const char *hostpath)
+{
+    int fd;
+    char buffer[HZ_MAX_LENGTH] = { 0 };
+    uint64_t hz;
+    int len;
+    const char *endptr = NULL;
+    int ret;
+
+    fd = qemu_open(hostpath, O_RDONLY);
+    if (fd < 0) {
+        return 0;
+    }
+
+    len = read(fd, buffer, HZ_MAX_LENGTH);
+    qemu_close(fd);
+    if (len <= 0) {
+        return 0;
+    }
+    ret = qemu_strtoul(buffer, &endptr, 0, &hz);
+    if (ret < 0) {
+        return 0;
+    }
+    return (uint32_t)hz;
+}
+
+static const MemoryRegionOps cpufreq_ops = {
+    .read = cpufreq_read,
+    .write = cpufreq_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void hz_init(CpuhzState *s)
+{
+    uint32_t hz;
+
+    hz = CPPC_Read(NOMINAL_FREQ_FILE);
+    if (hz == 0) {
+        hz = CPPC_Read(CPU_MAX_FREQ_FILE);
+        if (hz == 0) {
+            hz = DEFAULT_HZ;
+        } else {
+            /* Value in CpuMaxFrequency is in KHz unit; convert to MHz */
+            hz = hz / 1000;
+        }
+    }
+
+    s->HighestPerformance = hz;
+    s->NominalPerformance = hz;
+    s->LowestNonlinearPerformance = hz;
+    s->LowestPerformance = hz;
+    s->GuaranteedPerformance = hz;
+    s->DesiredPerformance = hz;
+    s->ReferencePerformanceCounter = 1;
+    s->DeliveredPerformanceCounter = 1;
+    s->PerformanceLimited = 0;
+    s->LowestFreq = hz;
+    s->NominalFreq = hz;
+}
+
+static void cpufreq_init(Object *obj)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    CpuhzState *s = CPUFREQ(obj);
+
+    s->reg_size = smp_cpus * CPPC_REG_PER_CPU_STRIDE;
+    if (s->reg_size > MAX_SUPPORT_SPACE) {
+        error_report("Required space 0x%x excesses the max support 0x%x",
+                 s->reg_size, MAX_SUPPORT_SPACE);
+        goto err_end;
+    }
+
+    memory_region_init_io(&s->iomem, OBJECT(s), &cpufreq_ops, s, "cpufreq",
+                          s->reg_size);
+    sysbus_init_mmio(sbd, &s->iomem);
+    hz_init(s);
+    return;
+
+err_end:
+    /* Set desired perf register offset to -1 to indicate no support for CPPC */
+    cppc_regs_offset[DESIRED_PERF] = -1;
+}
+
+static const TypeInfo cpufreq_arm_info = {
+    .name          = TYPE_CPUFREQ,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(CpuhzState),
+    .instance_init = cpufreq_init,
+};
+
+static void cpufreq_register_types(void)
+{
+    type_register_static(&cpufreq_arm_info);
+}
+
+type_init(cpufreq_register_types)
+
diff --git a/hw/arm/virt-acpi-build.c b/hw/arm/virt-acpi-build.c
index bf9c0bc2..33a8e2e3 100644
--- a/hw/arm/virt-acpi-build.c
+++ b/hw/arm/virt-acpi-build.c
@@ -45,11 +45,73 @@
 #include "hw/arm/virt.h"
 #include "sysemu/numa.h"
 #include "kvm_arm.h"
+#include "hw/acpi/acpi-defs.h"
 
 #define ARM_SPI_BASE 32
 #define ACPI_POWER_BUTTON_DEVICE "PWRB"
 
-static void acpi_dsdt_add_cpus(Aml *scope, int smp_cpus)
+static void acpi_dsdt_add_psd(Aml *dev, int cpus)
+{
+    Aml *pkg;
+    Aml *sub;
+
+    sub = aml_package(5);
+    aml_append(sub, aml_int(5));
+    aml_append(sub, aml_int(0));
+    /* Assume all vCPUs belong to the same domain */
+    aml_append(sub, aml_int(0));
+    /* SW_ANY: OSPM coordinate, initiate on any processor */
+    aml_append(sub, aml_int(0xFD));
+    aml_append(sub, aml_int(cpus));
+
+    pkg = aml_package(1);
+    aml_append(pkg, sub);
+
+    aml_append(dev, aml_name_decl("_PSD", pkg));
+}
+
+static void acpi_dsdt_add_cppc(Aml *dev, uint64_t cpu_base, int *regs_offset)
+{
+    Aml *cpc;
+    int i;
+
+    /* Use version 3 of CPPC table from ACPI 6.3 */
+    cpc = aml_package(23);
+    aml_append(cpc, aml_int(23));
+    aml_append(cpc, aml_int(3));
+
+    for (i = 0; i < CPPC_REG_COUNT; i++) {
+        Aml *res;
+        uint8_t reg_width;
+        uint8_t acc_type;
+        uint64_t addr;
+
+        if (regs_offset[i] == -1) {
+            reg_width = 0;
+            acc_type = AML_ANY_ACC;
+            addr = 0;
+        } else {
+            addr = cpu_base + regs_offset[i];
+            if (i == REFERENCE_CTR || i == DELIVERED_CTR) {
+                reg_width = 64;
+                acc_type = AML_QWORD_ACC;
+            } else {
+                reg_width = 32;
+                acc_type = AML_DWORD_ACC;
+            }
+        }
+
+        res = aml_resource_template();
+        aml_append(res, aml_generic_register(AML_SYSTEM_MEMORY, reg_width, 0,
+                                             acc_type, addr));
+        aml_append(cpc, res);
+    }
+
+    aml_append(dev, aml_name_decl("_CPC", cpc));
+}
+
+static void acpi_dsdt_add_cpus(Aml *scope, int smp_cpus,
+                               const MemMapEntry *cppc_memmap)
 {
     uint16_t i;
 
@@ -57,6 +119,18 @@ static void acpi_dsdt_add_cpus(Aml *scope, int smp_cpus)
         Aml *dev = aml_device("C%.03X", i);
         aml_append(dev, aml_name_decl("_HID", aml_string("ACPI0007")));
         aml_append(dev, aml_name_decl("_UID", aml_int(i)));
+
+        /*
+         * Append _CPC and _PSD to support CPU frequence show
+         * Check CPPC available by DESIRED_PERF register
+         */
+        if (cppc_regs_offset[DESIRED_PERF] != -1) {
+            acpi_dsdt_add_cppc(dev,
+                               cppc_memmap->base + i * CPPC_REG_PER_CPU_STRIDE,
+                               cppc_regs_offset);
+            acpi_dsdt_add_psd(dev, smp_cpus);
+        }
+
         aml_append(scope, dev);
     }
 }
@@ -740,7 +814,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms)
      * the RTC ACPI device at all when using UEFI.
      */
     scope = aml_scope("\\_SB");
-    acpi_dsdt_add_cpus(scope, vms->smp_cpus);
+    acpi_dsdt_add_cpus(scope, vms->smp_cpus, &memmap[VIRT_CPUFREQ]);
     acpi_dsdt_add_uart(scope, &memmap[VIRT_UART],
                        (irqmap[VIRT_UART] + ARM_SPI_BASE));
     acpi_dsdt_add_flash(scope, &memmap[VIRT_FLASH]);
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index ce2664a3..ec6f00ab 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -132,6 +132,7 @@ static const MemMapEntry base_memmap[] = {
     [VIRT_SECURE_UART] =        { 0x09040000, 0x00001000 },
     [VIRT_SMMU] =               { 0x09050000, 0x00020000 },
     [VIRT_MMIO] =               { 0x0a000000, 0x00000200 },
+    [VIRT_CPUFREQ] =              { 0x0b000000, 0x00010000 },
     /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */
     [VIRT_PLATFORM_BUS] =       { 0x0c000000, 0x02000000 },
     [VIRT_SECURE_MEM] =         { 0x0e000000, 0x01000000 },
@@ -725,6 +726,16 @@ static void create_uart(const VirtMachineState *vms, qemu_irq *pic, int uart,
     g_free(nodename);
 }
 
+static void create_cpufreq(const VirtMachineState *vms, MemoryRegion *mem)
+{
+    hwaddr base = vms->memmap[VIRT_CPUFREQ].base;
+    DeviceState *dev = qdev_create(NULL, "cpufreq");
+    SysBusDevice *s = SYS_BUS_DEVICE(dev);
+
+    qdev_init_nofail(dev);
+    memory_region_add_subregion(mem, base, sysbus_mmio_get_region(s, 0));
+}
+
 static void create_rtc(const VirtMachineState *vms, qemu_irq *pic)
 {
     char *nodename;
@@ -1618,6 +1629,8 @@ static void machvirt_init(MachineState *machine)
 
     create_uart(vms, pic, VIRT_UART, sysmem, serial_hd(0));
 
+    create_cpufreq(vms, sysmem);
+
     if (vms->secure) {
         create_secure_ram(vms, secure_sysmem);
         create_uart(vms, pic, VIRT_SECURE_UART, secure_sysmem, serial_hd(1));
diff --git a/hw/char/Kconfig b/hw/char/Kconfig
index 6360c9ff..8cc3ae2a 100644
--- a/hw/char/Kconfig
+++ b/hw/char/Kconfig
@@ -40,3 +40,7 @@ config SCLPCONSOLE
 
 config TERMINAL3270
     bool
+
+config CPUFREQ
+   bool
+   default y
diff --git a/include/hw/acpi/acpi-defs.h b/include/hw/acpi/acpi-defs.h
index f9aa4bd3..b4899a32 100644
--- a/include/hw/acpi/acpi-defs.h
+++ b/include/hw/acpi/acpi-defs.h
@@ -652,4 +652,42 @@ struct AcpiIortRC {
 } QEMU_PACKED;
 typedef struct AcpiIortRC AcpiIortRC;
 
+/*
+ * CPPC register definition from kernel header
+ * include/acpi/cppc_acpi.h
+ * The last element is newly added for easy use
+ */
+enum cppc_regs {
+    HIGHEST_PERF,
+    NOMINAL_PERF,
+    LOW_NON_LINEAR_PERF,
+    LOWEST_PERF,
+    GUARANTEED_PERF,
+    DESIRED_PERF,
+    MIN_PERF,
+    MAX_PERF,
+    PERF_REDUC_TOLERANCE,
+    TIME_WINDOW,
+    CTR_WRAP_TIME,
+    REFERENCE_CTR,
+    DELIVERED_CTR,
+    PERF_LIMITED,
+    ENABLE,
+    AUTO_SEL_ENABLE,
+    AUTO_ACT_WINDOW,
+    ENERGY_PERF,
+    REFERENCE_PERF,
+    LOWEST_FREQ,
+    NOMINAL_FREQ,
+    CPPC_REG_COUNT,
+};
+
+#define CPPC_REG_PER_CPU_STRIDE     0x40
+
+/*
+ * Offset for each CPPC register; -1 for unavailable
+ * The whole register space is unavailable if desired perf offset is -1.
+ */
+extern int cppc_regs_offset[CPPC_REG_COUNT];
+
 #endif
diff --git a/include/hw/acpi/aml-build.h b/include/hw/acpi/aml-build.h
index 1a563ad7..375335ab 100644
--- a/include/hw/acpi/aml-build.h
+++ b/include/hw/acpi/aml-build.h
@@ -347,6 +347,9 @@ Aml *aml_qword_memory(AmlDecode dec, AmlMinFixed min_fixed,
 Aml *aml_dma(AmlDmaType typ, AmlDmaBusMaster bm, AmlTransferSize sz,
              uint8_t channel);
 Aml *aml_sleep(uint64_t msec);
+Aml *aml_generic_register(AmlRegionSpace rs, uint8_t reg_width,
+                          uint8_t reg_offset, AmlAccessType type,
+                          uint64_t addr);
 
 /* Block AML object primitives */
 Aml *aml_scope(const char *name_format, ...) GCC_FMT_ATTR(1, 2);
diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h
index 507517c6..8465f9bd 100644
--- a/include/hw/arm/virt.h
+++ b/include/hw/arm/virt.h
@@ -66,6 +66,7 @@ enum {
     VIRT_GIC_REDIST,
     VIRT_SMMU,
     VIRT_UART,
+    VIRT_CPUFREQ,
     VIRT_MMIO,
     VIRT_RTC,
     VIRT_FW_CFG,
-- 
2.19.1

