|  | /* | 
|  | * Copyright (c) 2020-2022 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <stdlib.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/shell/shell.h> | 
|  |  | 
|  | #include <zephyr/drivers/edac.h> | 
|  | #include "ibecc.h" | 
|  |  | 
|  | /** | 
|  | * EDAC Error Injection interface | 
|  | * | 
|  | * edac inject addr [value]               Physical memory address base | 
|  | * edac inject mask [value]               Physical memory address mask | 
|  | * edac inject error_type                 Show / Set EDAC error type | 
|  | * edac inject trigger                    Trigger injection | 
|  | * | 
|  | * edac inject test_default               Set default injection parameters | 
|  | * | 
|  | * edac disable_nmi                       Experimental disable NMI | 
|  | * edac enable_nmi                        Experimental enable NMI | 
|  | * | 
|  | * EDAC Report interface | 
|  | * | 
|  | * edac info                              Show EDAC ECC / Parity error info | 
|  | * edac info ecc_error [show|clear]       Show ECC Errors | 
|  | * edac info parity_error [show|clear]    Show Parity Errors | 
|  | * | 
|  | * Physical memory access interface using devmem shell module | 
|  | * | 
|  | * devmem [width [value]]       Physical memory read / write | 
|  | */ | 
|  |  | 
|  | static void decode_ecc_error(const struct shell *shell, uint64_t ecc_error) | 
|  | { | 
|  | uint64_t erradd = ECC_ERROR_ERRADD(ecc_error); | 
|  | unsigned long errsynd = ECC_ERROR_ERRSYND(ecc_error); | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "CMI Error address: 0x%llx\n", erradd); | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "Error Syndrome: 0x%lx\n", errsynd); | 
|  |  | 
|  | if (ecc_error & ECC_ERROR_MERRSTS) { | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "Uncorrectable Error (UE)\n"); | 
|  | } | 
|  |  | 
|  | if (ecc_error & ECC_ERROR_CERRSTS) { | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "Correctable Error (CE)\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int ecc_error_show(const struct shell *sh, const struct device *dev) | 
|  | { | 
|  | uint64_t error; | 
|  | int err; | 
|  |  | 
|  | err = edac_ecc_error_log_get(dev, &error); | 
|  | if (err != 0 && err != -ENODATA) { | 
|  | shell_error(sh, "Error getting error log (err %d)", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | shell_fprintf(sh, SHELL_NORMAL, "ECC Error: 0x%llx\n", error); | 
|  |  | 
|  | if (error != 0) { | 
|  | decode_ecc_error(sh, error); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int parity_error_show(const struct shell *sh, const struct device *dev) | 
|  | { | 
|  | uint64_t error; | 
|  | int err; | 
|  |  | 
|  | err = edac_parity_error_log_get(dev, &error); | 
|  | if (err != 0 && err != -ENODATA) { | 
|  | shell_error(sh, "Error getting parity error log (err %d)", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | shell_fprintf(sh, SHELL_NORMAL, "Parity Error: 0x%llx\n", error); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int cmd_edac_info(const struct shell *shell, size_t argc, char **argv) | 
|  | { | 
|  | const struct device *dev; | 
|  | int err; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, "Show EDAC status\n"); | 
|  |  | 
|  | err = ecc_error_show(shell, dev); | 
|  | if (err != 0) { | 
|  | return err; | 
|  | } | 
|  |  | 
|  | err = parity_error_show(shell, dev); | 
|  | if (err != 0) { | 
|  | return err; | 
|  | } | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "Errors correctable: %d Errors uncorrectable %d\n", | 
|  | edac_errors_cor_get(dev), edac_errors_uc_get(dev)); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_EDAC_ERROR_INJECT) | 
|  | static int cmd_inject_addr(const struct shell *shell, size_t argc, char **argv) | 
|  | { | 
|  | const struct device *dev; | 
|  | int err; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | if (argc > 2) { | 
|  | /* Usage */ | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "Usage: edac inject %s [addr]\n", argv[0]); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | if (argc == 1) { | 
|  | uint64_t addr; | 
|  |  | 
|  | err = edac_inject_get_param1(dev, &addr); | 
|  | if (err != 0) { | 
|  | shell_error(shell, "Error getting address (err %d)", | 
|  | err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "Injection address base: 0x%llx\n", addr); | 
|  | } else { | 
|  | unsigned long value = strtoul(argv[1], NULL, 16); | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "Set injection address base to: %s\n", argv[1]); | 
|  |  | 
|  | err = edac_inject_set_param1(dev, value); | 
|  | if (err != 0) { | 
|  | shell_error(shell, "Error setting address (err %d)", | 
|  | err); | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int cmd_inject_mask(const struct shell *shell, size_t argc, char **argv) | 
|  | { | 
|  | const struct device *dev; | 
|  | int err; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | if (argc > 2) { | 
|  | /* Usage */ | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "Usage: edac inject %s [mask]\n", argv[0]); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | if (argc == 1) { | 
|  | uint64_t mask; | 
|  |  | 
|  | err = edac_inject_get_param2(dev, &mask); | 
|  | if (err != 0) { | 
|  | shell_error(shell, "Error getting mask (err %d)", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "Injection address mask: 0x%llx\n", mask); | 
|  | } else { | 
|  | uint64_t value = strtoul(argv[1], NULL, 16); | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, | 
|  | "Set injection address mask to %llx\n", value); | 
|  |  | 
|  | err = edac_inject_set_param2(dev, value); | 
|  | if (err != 0) { | 
|  | shell_error(shell, "Error setting mask (err %d)", err); | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int cmd_inject_trigger(const struct shell *shell, size_t argc, | 
|  | char **argv) | 
|  | { | 
|  | const struct device *dev; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, "Triggering injection\n"); | 
|  |  | 
|  | edac_inject_error_trigger(dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int cmd_inject_disable_nmi(const struct shell *shell, size_t argc, | 
|  | char **argv) | 
|  | { | 
|  | sys_out8((sys_in8(0x70) | 0x80), 0x70); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int cmd_inject_enable_nmi(const struct shell *shell, size_t argc, | 
|  | char **argv) | 
|  | { | 
|  | sys_out8((sys_in8(0x70) & 0x7F), 0x70); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const char *get_error_type(uint32_t type) | 
|  | { | 
|  | switch (type) { | 
|  | case EDAC_ERROR_TYPE_DRAM_COR: | 
|  | return "correctable"; | 
|  | case EDAC_ERROR_TYPE_DRAM_UC: | 
|  | return "uncorrectable"; | 
|  | default: | 
|  | return "unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int cmd_inject_error_type_show(const struct shell *shell, size_t argc, | 
|  | char **argv) | 
|  | { | 
|  | const struct device *dev; | 
|  | uint32_t error_type; | 
|  | int err; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | err = edac_inject_get_error_type(dev, &error_type); | 
|  | if (err != 0) { | 
|  | shell_error(shell, "Error getting error type (err %d)", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, "Injection error type: %s\n", | 
|  | get_error_type(error_type)); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int set_error_type(const struct shell *shell, uint32_t error_type) | 
|  | { | 
|  | const struct device *dev; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, "Set injection error type: %s\n", | 
|  | get_error_type(error_type)); | 
|  |  | 
|  | return edac_inject_set_error_type(dev, error_type); | 
|  | } | 
|  |  | 
|  | static int cmd_inject_error_type_cor(const struct shell *shell, size_t argc, | 
|  | char **argv) | 
|  | { | 
|  | return set_error_type(shell, EDAC_ERROR_TYPE_DRAM_COR); | 
|  | } | 
|  |  | 
|  | static int cmd_inject_error_type_uc(const struct shell *shell, size_t argc, | 
|  | char **argv) | 
|  | { | 
|  | return set_error_type(shell, EDAC_ERROR_TYPE_DRAM_UC); | 
|  | } | 
|  |  | 
|  | static int cmd_inject_test(const struct shell *shell, size_t argc, char **argv) | 
|  | { | 
|  | const struct device *dev; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | edac_inject_set_param1(dev, 0x1000); | 
|  | edac_inject_set_param2(dev, INJ_ADDR_BASE_MASK_MASK); | 
|  | edac_inject_set_error_type(dev, EDAC_ERROR_TYPE_DRAM_COR); | 
|  | edac_inject_error_trigger(dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | SHELL_STATIC_SUBCMD_SET_CREATE(sub_inject_error_type_cmds, | 
|  | SHELL_CMD(correctable, NULL, "Set correctable error type", | 
|  | cmd_inject_error_type_cor), | 
|  | SHELL_CMD(uncorrectable, NULL, "Set uncorrectable error type", | 
|  | cmd_inject_error_type_uc), | 
|  | SHELL_SUBCMD_SET_END /* Array terminated */ | 
|  | ); | 
|  |  | 
|  | /* EDAC Error Injection shell commands */ | 
|  | SHELL_STATIC_SUBCMD_SET_CREATE(sub_inject_cmds, | 
|  | SHELL_CMD(addr, NULL, "Get / Set physical address", cmd_inject_addr), | 
|  | SHELL_CMD(mask, NULL, "Get / Set address mask", cmd_inject_mask), | 
|  | SHELL_CMD_ARG(trigger, NULL, "Trigger injection", cmd_inject_trigger, | 
|  | 1, 0), | 
|  | SHELL_CMD(error_type, &sub_inject_error_type_cmds, | 
|  | "Get / Set injection error type", | 
|  | cmd_inject_error_type_show), | 
|  | SHELL_CMD(disable_nmi, NULL, "Disable NMI", cmd_inject_disable_nmi), | 
|  | SHELL_CMD(enable_nmi, NULL, "Enable NMI", cmd_inject_enable_nmi), | 
|  | SHELL_CMD_ARG(test_default, NULL, "Test default injection parameters", | 
|  | cmd_inject_test, 1, 0), | 
|  | SHELL_SUBCMD_SET_END /* Array terminated */ | 
|  | ); | 
|  | #endif /* CONFIG_EDAC_ERROR_INJECT */ | 
|  |  | 
|  | static int cmd_ecc_error_show(const struct shell *shell, size_t argc, | 
|  | char **argv) | 
|  | { | 
|  | const struct device *dev; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | return ecc_error_show(shell, dev); | 
|  | } | 
|  |  | 
|  | static int cmd_ecc_error_clear(const struct shell *shell, size_t argc, | 
|  | char **argv) | 
|  | { | 
|  | const struct device *dev; | 
|  | int err; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | err = edac_ecc_error_log_clear(dev); | 
|  | if (err != 0) { | 
|  | shell_error(shell, "Error clear ecc error log (err %d)", | 
|  | err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, "ECC Error Log cleared\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | SHELL_STATIC_SUBCMD_SET_CREATE(sub_ecc_error_cmds, | 
|  | SHELL_CMD(show, NULL, "Show ECC errors", cmd_ecc_error_show), | 
|  | SHELL_CMD(clear, NULL, "Clear ECC errors", cmd_ecc_error_clear), | 
|  | SHELL_SUBCMD_SET_END /* Array terminated */ | 
|  | ); | 
|  |  | 
|  | static int cmd_parity_error_show(const struct shell *shell, size_t argc, | 
|  | char **argv) | 
|  | { | 
|  | const struct device *dev; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | return parity_error_show(shell, dev); | 
|  | } | 
|  |  | 
|  | static int cmd_parity_error_clear(const struct shell *shell, size_t argc, | 
|  | char **argv) | 
|  | { | 
|  | const struct device *dev; | 
|  | int err; | 
|  |  | 
|  | dev = DEVICE_DT_GET(DT_NODELABEL(ibecc)); | 
|  | if (!device_is_ready(dev)) { | 
|  | shell_error(shell, "IBECC device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | err = edac_parity_error_log_clear(dev); | 
|  | if (err != 0) { | 
|  | shell_error(shell, "Error clear parity error log (err %d)", | 
|  | err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | shell_fprintf(shell, SHELL_NORMAL, "Parity Error Log cleared\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | SHELL_STATIC_SUBCMD_SET_CREATE(sub_parity_error_cmds, | 
|  | SHELL_CMD(show, NULL, "Show Parity errors", cmd_parity_error_show), | 
|  | SHELL_CMD(clear, NULL, "Clear Parity errors", cmd_parity_error_clear), | 
|  | SHELL_SUBCMD_SET_END /* Array terminated */ | 
|  | ); | 
|  |  | 
|  | /* EDAC Info shell commands */ | 
|  | SHELL_STATIC_SUBCMD_SET_CREATE(sub_info_cmds, | 
|  | SHELL_CMD(ecc_error, &sub_ecc_error_cmds, | 
|  | "ECC Error Show / Clear commands", cmd_ecc_error_show), | 
|  | SHELL_CMD(parity_error, &sub_parity_error_cmds, | 
|  | "Parity Error Show / Clear commands", cmd_parity_error_show), | 
|  | SHELL_SUBCMD_SET_END /* Array terminated */ | 
|  | ); | 
|  |  | 
|  | SHELL_STATIC_SUBCMD_SET_CREATE(sub_edac_cmds, | 
|  | SHELL_CMD(info, &sub_info_cmds, | 
|  | "Show EDAC information\n" | 
|  | "edac info <subcommands>", cmd_edac_info), | 
|  | #if defined(CONFIG_EDAC_ERROR_INJECT) | 
|  | /* This does not work with SHELL_COND_CMD */ | 
|  | SHELL_CMD(inject, &sub_inject_cmds, | 
|  | "Inject ECC error commands\n" | 
|  | "edac inject <subcommands>", NULL), | 
|  | #endif | 
|  | SHELL_SUBCMD_SET_END /* Array terminated. */ | 
|  | ); | 
|  |  | 
|  | SHELL_CMD_REGISTER(edac, &sub_edac_cmds, "EDAC information", cmd_edac_info); |