]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ACPI: Introduce Platform Firmware Runtime Telemetry driver
authorChen Yu <yu.c.chen@intel.com>
Wed, 22 Dec 2021 04:32:02 +0000 (12:32 +0800)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 27 Dec 2021 16:12:58 +0000 (17:12 +0100)
This driver allows user space to fetch telemetry data from the
firmware with the help of the Platform Firmware Runtime Telemetry
interface.

Both PFRU and PFRT are based on ACPI _DSM interfaces located under
special device objects in the ACPI Namespace, but these interfaces
are different from each other, so it is better to provide a separate
driver from each of them, even though they share some common
definitions and naming conventions.

Tested-by: Hongyu Ning <hongyu.ning@intel.com>
Signed-off-by: Chen Yu <yu.c.chen@intel.com>
[ rjw: Subject and changelog edits ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/acpi/Kconfig
drivers/acpi/Makefile
drivers/acpi/pfr_telemetry.c [new file with mode: 0644]
include/uapi/linux/pfrut.h

index d0b3ca9d4a974ff0dfba390806620856dc86f2bf..91f1da16934db4ecca9a102b9a7cf03563787644 100644 (file)
@@ -532,8 +532,12 @@ config ACPI_PFRUT
          The existing firmware code can be modified (driver update) or
          extended by adding new code to the firmware (code injection).
 
-         To compile this driver as module, choose M here:
-         the module will be called pfr_update.
+         Besides, the telemetry driver allows user space to fetch telemetry
+         data from the firmware with the help of the Platform Firmware Runtime
+         Telemetry interface.
+
+         To compile the drivers as modules, choose M here:
+         the modules will be called pfr_update and pfr_telemetry.
 
 if ARM64
 source "drivers/acpi/arm64/Kconfig"
index 2ad2e821cc08eded4dace82fbb643e6400b49b69..d3dc79298ce3c1018c343fddb6325e2b13c04dfa 100644 (file)
@@ -102,7 +102,7 @@ obj-$(CONFIG_ACPI_CPPC_LIB) += cppc_acpi.o
 obj-$(CONFIG_ACPI_SPCR_TABLE)  += spcr.o
 obj-$(CONFIG_ACPI_DEBUGGER_USER) += acpi_dbg.o
 obj-$(CONFIG_ACPI_PPTT)        += pptt.o
-obj-$(CONFIG_ACPI_PFRUT)       += pfr_update.o
+obj-$(CONFIG_ACPI_PFRUT)       += pfr_update.o pfr_telemetry.o
 
 # processor has its own "processor." module_param namespace
 processor-y                    := processor_driver.o
diff --git a/drivers/acpi/pfr_telemetry.c b/drivers/acpi/pfr_telemetry.c
new file mode 100644 (file)
index 0000000..da50dd8
--- /dev/null
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ACPI Platform Firmware Runtime Telemetry driver
+ *
+ * Copyright (C) 2021 Intel Corporation
+ * Author: Chen Yu <yu.c.chen@intel.com>
+ *
+ * This driver allows user space to fetch telemetry data from the
+ * firmware with the help of the Platform Firmware Runtime Telemetry
+ * interface.
+ */
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/uio.h>
+#include <linux/uuid.h>
+
+#include <uapi/linux/pfrut.h>
+
+#define PFRT_LOG_EXEC_IDX      0
+#define PFRT_LOG_HISTORY_IDX   1
+
+#define PFRT_LOG_ERR           0
+#define PFRT_LOG_WARN  1
+#define PFRT_LOG_INFO  2
+#define PFRT_LOG_VERB  4
+
+#define PFRT_FUNC_SET_LEV              1
+#define PFRT_FUNC_GET_LEV              2
+#define PFRT_FUNC_GET_DATA             3
+
+#define PFRT_REVID_1           1
+#define PFRT_REVID_2           2
+#define PFRT_DEFAULT_REV_ID    PFRT_REVID_1
+
+enum log_index {
+       LOG_STATUS_IDX = 0,
+       LOG_EXT_STATUS_IDX = 1,
+       LOG_MAX_SZ_IDX = 2,
+       LOG_CHUNK1_LO_IDX = 3,
+       LOG_CHUNK1_HI_IDX = 4,
+       LOG_CHUNK1_SZ_IDX = 5,
+       LOG_CHUNK2_LO_IDX = 6,
+       LOG_CHUNK2_HI_IDX = 7,
+       LOG_CHUNK2_SZ_IDX = 8,
+       LOG_ROLLOVER_CNT_IDX = 9,
+       LOG_RESET_CNT_IDX = 10,
+       LOG_NR_IDX
+};
+
+struct pfrt_log_device {
+       int index;
+       struct pfrt_log_info info;
+       struct device *parent_dev;
+       struct miscdevice miscdev;
+};
+
+/* pfrt_guid is the parameter for _DSM method */
+static const guid_t pfrt_log_guid =
+       GUID_INIT(0x75191659, 0x8178, 0x4D9D, 0xB8, 0x8F, 0xAC, 0x5E,
+                 0x5E, 0x93, 0xE8, 0xBF);
+
+static DEFINE_IDA(pfrt_log_ida);
+
+static inline struct pfrt_log_device *to_pfrt_log_dev(struct file *file)
+{
+       return container_of(file->private_data, struct pfrt_log_device, miscdev);
+}
+
+static int get_pfrt_log_data_info(struct pfrt_log_data_info *data_info,
+                                 struct pfrt_log_device *pfrt_log_dev)
+{
+       acpi_handle handle = ACPI_HANDLE(pfrt_log_dev->parent_dev);
+       union acpi_object *out_obj, in_obj, in_buf;
+       int ret = -EBUSY;
+
+       memset(&in_obj, 0, sizeof(in_obj));
+       memset(&in_buf, 0, sizeof(in_buf));
+       in_obj.type = ACPI_TYPE_PACKAGE;
+       in_obj.package.count = 1;
+       in_obj.package.elements = &in_buf;
+       in_buf.type = ACPI_TYPE_INTEGER;
+       in_buf.integer.value = pfrt_log_dev->info.log_type;
+
+       out_obj = acpi_evaluate_dsm_typed(handle, &pfrt_log_guid,
+                                         pfrt_log_dev->info.log_revid, PFRT_FUNC_GET_DATA,
+                                         &in_obj, ACPI_TYPE_PACKAGE);
+       if (!out_obj)
+               return -EINVAL;
+
+       if (out_obj->package.count < LOG_NR_IDX ||
+           out_obj->package.elements[LOG_STATUS_IDX].type != ACPI_TYPE_INTEGER ||
+           out_obj->package.elements[LOG_EXT_STATUS_IDX].type != ACPI_TYPE_INTEGER ||
+           out_obj->package.elements[LOG_MAX_SZ_IDX].type != ACPI_TYPE_INTEGER ||
+           out_obj->package.elements[LOG_CHUNK1_LO_IDX].type != ACPI_TYPE_INTEGER ||
+           out_obj->package.elements[LOG_CHUNK1_HI_IDX].type != ACPI_TYPE_INTEGER ||
+           out_obj->package.elements[LOG_CHUNK1_SZ_IDX].type != ACPI_TYPE_INTEGER ||
+           out_obj->package.elements[LOG_CHUNK2_LO_IDX].type != ACPI_TYPE_INTEGER ||
+           out_obj->package.elements[LOG_CHUNK2_HI_IDX].type != ACPI_TYPE_INTEGER ||
+           out_obj->package.elements[LOG_CHUNK2_SZ_IDX].type != ACPI_TYPE_INTEGER ||
+           out_obj->package.elements[LOG_ROLLOVER_CNT_IDX].type != ACPI_TYPE_INTEGER ||
+           out_obj->package.elements[LOG_RESET_CNT_IDX].type != ACPI_TYPE_INTEGER)
+               goto free_acpi_buffer;
+
+       data_info->status = out_obj->package.elements[LOG_STATUS_IDX].integer.value;
+       data_info->ext_status =
+               out_obj->package.elements[LOG_EXT_STATUS_IDX].integer.value;
+       if (data_info->status != DSM_SUCCEED) {
+               dev_dbg(pfrt_log_dev->parent_dev, "Error Status:%d\n", data_info->status);
+               dev_dbg(pfrt_log_dev->parent_dev, "Error Extend Status:%d\n",
+                       data_info->ext_status);
+               goto free_acpi_buffer;
+       }
+
+       data_info->max_data_size =
+               out_obj->package.elements[LOG_MAX_SZ_IDX].integer.value;
+       data_info->chunk1_addr_lo =
+               out_obj->package.elements[LOG_CHUNK1_LO_IDX].integer.value;
+       data_info->chunk1_addr_hi =
+               out_obj->package.elements[LOG_CHUNK1_HI_IDX].integer.value;
+       data_info->chunk1_size =
+               out_obj->package.elements[LOG_CHUNK1_SZ_IDX].integer.value;
+       data_info->chunk2_addr_lo =
+               out_obj->package.elements[LOG_CHUNK2_LO_IDX].integer.value;
+       data_info->chunk2_addr_hi =
+               out_obj->package.elements[LOG_CHUNK2_HI_IDX].integer.value;
+       data_info->chunk2_size =
+               out_obj->package.elements[LOG_CHUNK2_SZ_IDX].integer.value;
+       data_info->rollover_cnt =
+               out_obj->package.elements[LOG_ROLLOVER_CNT_IDX].integer.value;
+       data_info->reset_cnt =
+               out_obj->package.elements[LOG_RESET_CNT_IDX].integer.value;
+
+       ret = 0;
+
+free_acpi_buffer:
+       kfree(out_obj);
+
+       return ret;
+}
+
+static int set_pfrt_log_level(int level, struct pfrt_log_device *pfrt_log_dev)
+{
+       acpi_handle handle = ACPI_HANDLE(pfrt_log_dev->parent_dev);
+       union acpi_object *out_obj, *obj, in_obj, in_buf;
+       enum pfru_dsm_status status, ext_status;
+       int ret = 0;
+
+       memset(&in_obj, 0, sizeof(in_obj));
+       memset(&in_buf, 0, sizeof(in_buf));
+       in_obj.type = ACPI_TYPE_PACKAGE;
+       in_obj.package.count = 1;
+       in_obj.package.elements = &in_buf;
+       in_buf.type = ACPI_TYPE_INTEGER;
+       in_buf.integer.value = level;
+
+       out_obj = acpi_evaluate_dsm_typed(handle, &pfrt_log_guid,
+                                         pfrt_log_dev->info.log_revid, PFRT_FUNC_SET_LEV,
+                                         &in_obj, ACPI_TYPE_PACKAGE);
+       if (!out_obj)
+               return -EINVAL;
+
+       obj = &out_obj->package.elements[0];
+       status = obj->integer.value;
+       if (status != DSM_SUCCEED) {
+               obj = &out_obj->package.elements[1];
+               ext_status = obj->integer.value;
+               dev_dbg(pfrt_log_dev->parent_dev, "Error Status:%d\n", status);
+               dev_dbg(pfrt_log_dev->parent_dev, "Error Extend Status:%d\n", ext_status);
+               ret = -EBUSY;
+       }
+
+       kfree(out_obj);
+
+       return ret;
+}
+
+static int get_pfrt_log_level(struct pfrt_log_device *pfrt_log_dev)
+{
+       acpi_handle handle = ACPI_HANDLE(pfrt_log_dev->parent_dev);
+       union acpi_object *out_obj, *obj;
+       enum pfru_dsm_status status, ext_status;
+       int ret = -EBUSY;
+
+       out_obj = acpi_evaluate_dsm_typed(handle, &pfrt_log_guid,
+                                         pfrt_log_dev->info.log_revid, PFRT_FUNC_GET_LEV,
+                                         NULL, ACPI_TYPE_PACKAGE);
+       if (!out_obj)
+               return -EINVAL;
+
+       obj = &out_obj->package.elements[0];
+       if (obj->type != ACPI_TYPE_INTEGER)
+               goto free_acpi_buffer;
+
+       status = obj->integer.value;
+       if (status != DSM_SUCCEED) {
+               obj = &out_obj->package.elements[1];
+               ext_status = obj->integer.value;
+               dev_dbg(pfrt_log_dev->parent_dev, "Error Status:%d\n", status);
+               dev_dbg(pfrt_log_dev->parent_dev, "Error Extend Status:%d\n", ext_status);
+               goto free_acpi_buffer;
+       }
+
+       obj = &out_obj->package.elements[2];
+       if (obj->type != ACPI_TYPE_INTEGER)
+               goto free_acpi_buffer;
+
+       ret = obj->integer.value;
+
+free_acpi_buffer:
+       kfree(out_obj);
+
+       return ret;
+}
+
+static int valid_log_level(u32 level)
+{
+       return level == PFRT_LOG_ERR || level == PFRT_LOG_WARN ||
+              level == PFRT_LOG_INFO || level == PFRT_LOG_VERB;
+}
+
+static int valid_log_type(u32 type)
+{
+       return type == PFRT_LOG_EXEC_IDX || type == PFRT_LOG_HISTORY_IDX;
+}
+
+static inline int valid_log_revid(u32 id)
+{
+       return id == PFRT_REVID_1 || id == PFRT_REVID_2;
+}
+
+static long pfrt_log_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       struct pfrt_log_device *pfrt_log_dev = to_pfrt_log_dev(file);
+       struct pfrt_log_data_info data_info;
+       struct pfrt_log_info info;
+       void __user *p;
+       int ret = 0;
+
+       p = (void __user *)arg;
+
+       switch (cmd) {
+       case PFRT_LOG_IOC_SET_INFO:
+               if (copy_from_user(&info, p, sizeof(info)))
+                       return -EFAULT;
+
+               if (valid_log_revid(info.log_revid))
+                       pfrt_log_dev->info.log_revid = info.log_revid;
+
+               if (valid_log_level(info.log_level)) {
+                       ret = set_pfrt_log_level(info.log_level, pfrt_log_dev);
+                       if (ret < 0)
+                               return ret;
+
+                       pfrt_log_dev->info.log_level = info.log_level;
+               }
+
+               if (valid_log_type(info.log_type))
+                       pfrt_log_dev->info.log_type = info.log_type;
+
+               return 0;
+
+       case PFRT_LOG_IOC_GET_INFO:
+               info.log_level = get_pfrt_log_level(pfrt_log_dev);
+               if (ret < 0)
+                       return ret;
+
+               info.log_type = pfrt_log_dev->info.log_type;
+               info.log_revid = pfrt_log_dev->info.log_revid;
+               if (copy_to_user(p, &info, sizeof(info)))
+                       return -EFAULT;
+
+               return 0;
+
+       case PFRT_LOG_IOC_GET_DATA_INFO:
+               ret = get_pfrt_log_data_info(&data_info, pfrt_log_dev);
+               if (ret)
+                       return ret;
+
+               if (copy_to_user(p, &data_info, sizeof(struct pfrt_log_data_info)))
+                       return -EFAULT;
+
+               return 0;
+
+       default:
+               return -ENOTTY;
+       }
+}
+
+static int
+pfrt_log_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       struct pfrt_log_device *pfrt_log_dev;
+       struct pfrt_log_data_info info;
+       unsigned long psize, vsize;
+       phys_addr_t base_addr;
+       int ret;
+
+       if (vma->vm_flags & VM_WRITE)
+               return -EROFS;
+
+       /* changing from read to write with mprotect is not allowed */
+       vma->vm_flags &= ~VM_MAYWRITE;
+
+       pfrt_log_dev = to_pfrt_log_dev(file);
+
+       ret = get_pfrt_log_data_info(&info, pfrt_log_dev);
+       if (ret)
+               return ret;
+
+       base_addr = (phys_addr_t)((info.chunk2_addr_hi << 32) | info.chunk2_addr_lo);
+       /* pfrt update has not been launched yet */
+       if (!base_addr)
+               return -ENODEV;
+
+       psize = info.max_data_size;
+       /* base address and total buffer size must be page aligned */
+       if (!PAGE_ALIGNED(base_addr) || !PAGE_ALIGNED(psize))
+               return -ENODEV;
+
+       vsize = vma->vm_end - vma->vm_start;
+       if (vsize > psize)
+               return -EINVAL;
+
+       vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+       if (io_remap_pfn_range(vma, vma->vm_start, PFN_DOWN(base_addr),
+                              vsize, vma->vm_page_prot))
+               return -EAGAIN;
+
+       return 0;
+}
+
+static const struct file_operations acpi_pfrt_log_fops = {
+       .owner          = THIS_MODULE,
+       .mmap           = pfrt_log_mmap,
+       .unlocked_ioctl = pfrt_log_ioctl,
+       .llseek         = noop_llseek,
+};
+
+static int acpi_pfrt_log_remove(struct platform_device *pdev)
+{
+       struct pfrt_log_device *pfrt_log_dev = platform_get_drvdata(pdev);
+
+       misc_deregister(&pfrt_log_dev->miscdev);
+
+       return 0;
+}
+
+static void pfrt_log_put_idx(void *data)
+{
+       struct pfrt_log_device *pfrt_log_dev = data;
+
+       ida_free(&pfrt_log_ida, pfrt_log_dev->index);
+}
+
+static int acpi_pfrt_log_probe(struct platform_device *pdev)
+{
+       acpi_handle handle = ACPI_HANDLE(&pdev->dev);
+       struct pfrt_log_device *pfrt_log_dev;
+       int ret;
+
+       if (!acpi_has_method(handle, "_DSM")) {
+               dev_dbg(&pdev->dev, "Missing _DSM\n");
+               return -ENODEV;
+       }
+
+       pfrt_log_dev = devm_kzalloc(&pdev->dev, sizeof(*pfrt_log_dev), GFP_KERNEL);
+       if (!pfrt_log_dev)
+               return -ENOMEM;
+
+       ret = ida_alloc(&pfrt_log_ida, GFP_KERNEL);
+       if (ret < 0)
+               return ret;
+
+       pfrt_log_dev->index = ret;
+       ret = devm_add_action_or_reset(&pdev->dev, pfrt_log_put_idx, pfrt_log_dev);
+       if (ret)
+               return ret;
+
+       pfrt_log_dev->info.log_revid = PFRT_DEFAULT_REV_ID;
+       pfrt_log_dev->parent_dev = &pdev->dev;
+
+       pfrt_log_dev->miscdev.minor = MISC_DYNAMIC_MINOR;
+       pfrt_log_dev->miscdev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+                                                   "pfrt%d",
+                                                   pfrt_log_dev->index);
+       if (!pfrt_log_dev->miscdev.name)
+               return -ENOMEM;
+
+       pfrt_log_dev->miscdev.nodename = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+                                                       "acpi_pfr_telemetry%d",
+                                                       pfrt_log_dev->index);
+       if (!pfrt_log_dev->miscdev.nodename)
+               return -ENOMEM;
+
+       pfrt_log_dev->miscdev.fops = &acpi_pfrt_log_fops;
+       pfrt_log_dev->miscdev.parent = &pdev->dev;
+
+       ret = misc_register(&pfrt_log_dev->miscdev);
+       if (ret)
+               return ret;
+
+       platform_set_drvdata(pdev, pfrt_log_dev);
+
+       return 0;
+}
+
+static const struct acpi_device_id acpi_pfrt_log_ids[] = {
+       {"INTC1081"},
+       {}
+};
+MODULE_DEVICE_TABLE(acpi, acpi_pfrt_log_ids);
+
+static struct platform_driver acpi_pfrt_log_driver = {
+       .driver = {
+               .name = "pfr_telemetry",
+               .acpi_match_table = acpi_pfrt_log_ids,
+       },
+       .probe = acpi_pfrt_log_probe,
+       .remove = acpi_pfrt_log_remove,
+};
+module_platform_driver(acpi_pfrt_log_driver);
+
+MODULE_DESCRIPTION("Platform Firmware Runtime Update Telemetry driver");
+MODULE_LICENSE("GPL v2");
index fa97e80a93b776015dbcd0f36c2488ec0cfd67de..42fa15f8310d6bca08f678c65a48274efab779af 100644 (file)
@@ -171,4 +171,92 @@ struct pfru_updated_result {
        __u64 high_exec_time;
 };
 
+/**
+ * struct pfrt_log_data_info - Log Data from telemetry service.
+ * @status: Indicator of whether this update succeed.
+ * @ext_status: Implementation specific update result.
+ * @chunk1_addr_lo: Low 32bit physical address of the telemetry data chunk1
+ *                  starting address.
+ * @chunk1_addr_hi: High 32bit physical address of the telemetry data chunk1
+ *                  starting address.
+ * @chunk2_addr_lo: Low 32bit physical address of the telemetry data chunk2
+ *                  starting address.
+ * @chunk2_addr_hi: High 32bit physical address of the telemetry data chunk2
+ *                  starting address.
+ * @max_data_size: Maximum supported size of data of all data chunks combined.
+ * @chunk1_size: Data size in bytes of the telemetry data chunk1 buffer.
+ * @chunk2_size: Data size in bytes of the telemetry data chunk2 buffer.
+ * @rollover_cnt: Number of times telemetry data buffer is overwritten
+ *                since telemetry buffer reset.
+ * @reset_cnt: Number of times telemetry services resets that results in
+ *             rollover count and data chunk buffers are reset.
+ */
+struct pfrt_log_data_info {
+       __u32 status;
+       __u32 ext_status;
+       __u64 chunk1_addr_lo;
+       __u64 chunk1_addr_hi;
+       __u64 chunk2_addr_lo;
+       __u64 chunk2_addr_hi;
+       __u32 max_data_size;
+       __u32 chunk1_size;
+       __u32 chunk2_size;
+       __u32 rollover_cnt;
+       __u32 reset_cnt;
+};
+
+/**
+ * struct pfrt_log_info - Telemetry log information.
+ * @log_level: The telemetry log level.
+ * @log_type: The telemetry log type(history and execution).
+ * @log_revid: The telemetry log revision id.
+ */
+struct pfrt_log_info {
+       __u32 log_level;
+       __u32 log_type;
+       __u32 log_revid;
+};
+
+/**
+ * PFRT_LOG_IOC_SET_INFO - _IOW(PFRUT_IOCTL_MAGIC, 0x06,
+ *                             struct pfrt_log_info)
+ *
+ * Return:
+ * * 0                 - success
+ * * -EFAULT           - fail to get the setting parameter
+ * * -EINVAL           - fail to set the log level
+ *
+ * Set the PFRT log level and log type. The input information is
+ * a struct pfrt_log_info.
+ */
+#define PFRT_LOG_IOC_SET_INFO _IOW(PFRUT_IOCTL_MAGIC, 0x06, struct pfrt_log_info)
+
+/**
+ * PFRT_LOG_IOC_GET_INFO - _IOR(PFRUT_IOCTL_MAGIC, 0x07,
+ *                             struct pfrt_log_info)
+ *
+ * Return:
+ * * 0                 - success
+ * * -EINVAL           - fail to get the log level
+ * * -EFAULT           - fail to copy the result back to userspace
+ *
+ * Retrieve log level and log type of the telemetry. The information is
+ * a struct pfrt_log_info.
+ */
+#define PFRT_LOG_IOC_GET_INFO _IOR(PFRUT_IOCTL_MAGIC, 0x07, struct pfrt_log_info)
+
+/**
+ * PFRT_LOG_IOC_GET_DATA_INFO - _IOR(PFRUT_IOCTL_MAGIC, 0x08,
+ *                                  struct pfrt_log_data_info)
+ *
+ * Return:
+ * * 0                 - success
+ * * -EINVAL           - fail to get the log buffer information
+ * * -EFAULT           - fail to copy the log buffer information to userspace
+ *
+ * Retrieve data information about the telemetry. The information
+ * is a struct pfrt_log_data_info.
+ */
+#define PFRT_LOG_IOC_GET_DATA_INFO _IOR(PFRUT_IOCTL_MAGIC, 0x08, struct pfrt_log_data_info)
+
 #endif /* __PFRUT_H__ */