Message ID | 20210823192542.1899768-1-colin.king@canonical.com |
---|---|
State | Superseded |
Headers | show |
Series | ARM64 SMCCC firmware API tests | expand |
On 2021-08-23 1:25 p.m., Colin King wrote: > From: Colin Ian King <colin.king@canonical.com> > > Add SMCCC firmware API test. This initial release adds tests to > exercise the API in the following ways: > > 1. Test the API version (and check conduit number) > 2. Test the API function ID features > 3. Test the bus get segment information > > Currently the PCI_READ and PCI_WRITE API calls are not yet > being tested, these will be tested in later releases of the > SMCCC test. > > For more information about ARM SMCCC firmware APIs, please > refer to: > https://developer.arm.com/documentation/den0115/latest > > Signed-off-by: Colin Ian King <colin.king@canonical.com> > --- > debian/control | 9 + > debian/fwts-smccc-dkms.dkms | 6 + > debian/rules | 9 +- > smccc_test/Makefile | 29 ++++ > smccc_test/smccc_test.c | 244 ++++++++++++++++++++++++++ > smccc_test/smccc_test.h | 44 +++++ > src/Makefile.am | 2 + > src/pci/smccc/smccc.c | 333 ++++++++++++++++++++++++++++++++++++ > 8 files changed, 673 insertions(+), 3 deletions(-) > create mode 100644 debian/fwts-smccc-dkms.dkms > create mode 100644 smccc_test/Makefile > create mode 100644 smccc_test/smccc_test.c > create mode 100644 smccc_test/smccc_test.h > create mode 100644 src/pci/smccc/smccc.c > > diff --git a/debian/control b/debian/control > index 9325c96e..7d6213ba 100644 > --- a/debian/control > +++ b/debian/control > @@ -86,3 +86,12 @@ Depends: ${misc:Depends}, > Description: Firmware Test Suite UEFI Runtime Service kernel driver > This package provides the efi_runtime kernel driver in DKMS format, > which is required for accessing UEFI Runtime Services. > + > +Package: fwts-smccc-dkms > +Architecture: arm64 > +Priority: optional > +Depends: ${misc:Depends}, > + dkms > +Description: Firmware Test Suite SMCCC firmware kernel driver > + This package provides the ARM64 SMCCC kernel driver in DKMS format, > + which is required for accessing the ARM64 SMCCC firmware API. > diff --git a/debian/fwts-smccc-dkms.dkms b/debian/fwts-smccc-dkms.dkms > new file mode 100644 > index 00000000..4a1a87cd > --- /dev/null > +++ b/debian/fwts-smccc-dkms.dkms > @@ -0,0 +1,6 @@ > +PACKAGE_NAME="fwts-smccc-dkms" > +PACKAGE_VERSION="#MODULE_VERSION#" > +MAKE[0]="KVER=$kernelver make" > +BUILT_MODULE_NAME[0]="smccc_test" > +DEST_MODULE_LOCATION[0]="/updates" > +AUTOINSTALL="yes" > diff --git a/debian/rules b/debian/rules > index c24df00f..01b952af 100755 > --- a/debian/rules > +++ b/debian/rules > @@ -9,11 +9,14 @@ DEBVERS := $(shell dpkg-parsechangelog | grep ^Version: | cut -d' ' -f2 \ > > VERSION := $(shell echo '$(DEBVERS)' | sed -e 's/[+-].*//' -e 's/~//g') > > -DKMS_SRC_DIR := $(CURDIR)/debian/fwts-efi-runtime-dkms/usr/src/fwts-efi-runtime-dkms-$(VERSION) > +DKMS_EFI_RUNTIME_SRC_DIR := $(CURDIR)/debian/fwts-efi-runtime-dkms/usr/src/fwts-efi-runtime-dkms-$(VERSION) > +DKMS_SMCCC_SRC_DIR := $(CURDIR)/debian/fwts-smccc-dkms/usr/src/fwts-smccc-dkms-$(VERSION) > > override_dh_auto_install: > - install -d $(DKMS_SRC_DIR) > - cp -a efi_runtime/* $(DKMS_SRC_DIR) > + install -d $(DKMS_EFI_RUNTIME_SRC_DIR) > + cp -a efi_runtime/* $(DKMS_EFI_RUNTIME_SRC_DIR) > + install -d $(DKMS_SMCCC_SRC_DIR) > + cp -a smccc_test/* $(DKMS_SMCCC_SRC_DIR) > dh_auto_install > > override_dh_dkms: > diff --git a/smccc_test/Makefile b/smccc_test/Makefile > new file mode 100644 > index 00000000..f2faf76b > --- /dev/null > +++ b/smccc_test/Makefile > @@ -0,0 +1,29 @@ > +# > +# Copyright (C) 2012-2021 Canonical, Ltd. Just 2021? > +# > +# 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, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. > +# > + > +KVER ?= `uname -r` > +KBUILD_MODPOST_WARN=y > +obj-m += smccc_test.o > +all: > + make -C /lib/modules/$(KVER)/build M=`pwd` modules > + > +install: > + make -C /lib/modules/$(KVER)/build M=`pwd` modules_install > + > +clean: > + make -C /lib/modules/$(KVER)/build M=`pwd` clean > diff --git a/smccc_test/smccc_test.c b/smccc_test/smccc_test.c > new file mode 100644 > index 00000000..563e3c08 > --- /dev/null > +++ b/smccc_test/smccc_test.c > @@ -0,0 +1,244 @@ > +/* > + * SMCCC test driver > + * > + * Copyright(C) 2021 Canonical 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, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, > + * USA. > + */ > + > +#include <linux/version.h> > +#include <linux/miscdevice.h> > +#include <linux/module.h> > +#include <linux/init.h> > +#include <linux/proc_fs.h> > +#include <linux/slab.h> > +#include <linux/uaccess.h> > + > +/* > + * ARM SMCCC kernel test driver > + * https://developer.arm.com/documentation/den0115/latest > + */ > + > +#if defined(__aarch64__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)) > +#include <linux/arm-smccc.h> > +#endif > + > +#include "smccc_test.h" > + > +#define MODULE_NAME "smccc_test" > +#define SMCCC_TEST_VERSION (0x00000001) > + > +MODULE_AUTHOR("Colin Ian King"); > +MODULE_DESCRIPTION("SMCCC Test Driver"); > +MODULE_LICENSE("GPL"); > + > +#if defined(__aarch64__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)) > + > +/* PCI ECAM conduit (defined by ARM DEN0115A) */ > +#define SMCCC_PCI_CALL_VAL(val) \ > + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ > + ARM_SMCCC_SMC_32, \ > + ARM_SMCCC_OWNER_STANDARD, val) > + > +#ifndef SMCCC_PCI_VERSION > +#define SMCCC_PCI_VERSION SMCCC_PCI_CALL_VAL(0x0130) > +#endif > + > +#ifndef SMCCC_PCI_FEATURES > +#define SMCCC_PCI_FEATURES SMCCC_PCI_CALL_VAL(0x131) > +#endif > + > +#ifndef SMCCC_PCI_READ > +#define SMCCC_PCI_READ SMCCC_PCI_CALL_VAL(0x132) > +#endif > + > +#ifndef SMCCC_PCI_WRITE > +#define SMCCC_PCI_WRITE SMCCC_PCI_CALL_VAL(0x133) > +#endif > + > +#ifndef SMCCC_PCI_SEG_INFO > +#define SMCCC_PCI_SEG_INFO SMCCC_PCI_CALL_VAL(0x134) > +#endif > + > +/* > + * smccc_test_copy_to_user() > + * copy arm_res a* registers to user space test_arg w array > + */ > +static int smccc_test_copy_to_user(unsigned long arg, struct arm_smccc_res *arm_res, int conduit) > +{ > + struct smccc_test_arg test_arg = { }, __user *test_arg_user; > + > + test_arg_user = (struct smccc_test_arg __user *)arg; > + > + test_arg.size = sizeof(test_arg); > + test_arg.conduit = conduit; > + test_arg.w[0] = arm_res->a0; > + test_arg.w[1] = arm_res->a1; > + test_arg.w[2] = arm_res->a2; > + test_arg.w[3] = arm_res->a3; > + > + if (copy_to_user(test_arg_user, &test_arg, sizeof(*test_arg_user))) > + return -EFAULT; > + > + return 0; > +} > + > +/* > + * smccc_test_copy_from_user() > + * copy user space test_arg data to test_arg > + */ > +static int smccc_test_copy_from_user(struct smccc_test_arg *test_arg, unsigned long arg) > +{ > + struct smccc_test_arg __user *user; > + > + user = (struct smccc_test_arg __user *)arg; > + return copy_from_user(test_arg, user, sizeof(*test_arg)); > +} > + > +static long smccc_test_pci_version(unsigned long arg) > +{ > + struct arm_smccc_res arm_res = { }; > + int conduit; > + > + conduit = arm_smccc_1_1_invoke(SMCCC_PCI_VERSION, 0, 0, 0, 0, 0, 0, 0, &arm_res); > + > + return smccc_test_copy_to_user(arg, &arm_res, conduit); > +} > + > +static long smccc_test_pci_features(unsigned long arg) > +{ > + struct arm_smccc_res arm_res = { }; > + struct smccc_test_arg test_arg; > + int ret, conduit; > + > + ret = smccc_test_copy_from_user(&test_arg, arg); > + if (ret) > + return ret; > + conduit = arm_smccc_1_1_invoke(SMCCC_PCI_FEATURES, test_arg.w[0], 0, 0, 0, 0, 0, 0, &arm_res); > + > + return smccc_test_copy_to_user(arg, &arm_res, conduit); > +} > + > +static long smccc_test_pci_get_seg_info(unsigned long arg) > +{ > + struct arm_smccc_res arm_res = { }; > + struct smccc_test_arg test_arg; > + int ret, conduit; > + > + ret = smccc_test_copy_from_user(&test_arg, arg); > + if (ret) > + return ret; > + > + conduit = arm_smccc_1_1_invoke(SMCCC_PCI_SEG_INFO, test_arg.w[1], 0, 0, 0, 0, 0, 0, &arm_res); > + > + return smccc_test_copy_to_user(arg, &arm_res, conduit); > +} > + > +static long smccc_test_ioctl(struct file *file, unsigned int cmd, > + unsigned long arg) > +{ > + u32 size; > + u32 __user *user_size = (u32 __user *)arg; > + > + if (get_user(size, user_size)) > + return -EFAULT; > + if (size != sizeof(struct smccc_test_arg)) > + return -EINVAL; > + > + switch (cmd) { > + case SMCCC_TEST_PCI_VERSION: > + return smccc_test_pci_version(arg); > + case SMCCC_TEST_PCI_FEATURES: > + return smccc_test_pci_features(arg); > + case SMCCC_TEST_PCI_READ: > + /* TODO */ > + return -ENOTSUPP; > + case SMCCC_TEST_PCI_WRITE: > + /* TODO */ > + return -ENOTSUPP; > + case SMCCC_TEST_PCI_GET_SEG_INFO: > + return smccc_test_pci_get_seg_info(arg); > + default: > + break; > + } > + > + return -ENOTTY; > +} > + > +static int smccc_test_open(struct inode *inode, struct file *file) > +{ > + return 0; > +} > + > +static int smccc_test_close(struct inode *inode, struct file *file) > +{ > + return 0; > +} > + > +static const struct file_operations smccc_test_fops = { > + .owner = THIS_MODULE, > + .unlocked_ioctl = smccc_test_ioctl, > + .open = smccc_test_open, > + .release = smccc_test_close, > + .llseek = no_llseek, > +}; > + > +static struct miscdevice smccc_test_dev = { > + MISC_DYNAMIC_MINOR, > + "smccc_test", > + &smccc_test_fops > +}; > + > +static int __init smccc_test_init(void) > +{ > + int ret; > + > + ret = arm_smccc_get_version(); > + pr_info(MODULE_NAME ": ARM SMCCC version %d.%d.%d\n", > + (ret >> 16) & 0xff, (ret >> 8) & 0xff, ret & 0xff); > + > + ret = misc_register(&smccc_test_dev); > + if (ret) { > + pr_err(MODULE_NAME ": can't misc_register on minor=%d\n", > + MISC_DYNAMIC_MINOR); > + return ret; > + } > + > + return 0; > +} > + > +static void __exit smccc_test_exit(void) > +{ > + misc_deregister(&smccc_test_dev); > +} > + > +#else > + > +static int __init smccc_test_init(void) > +{ > + pr_info(MODULE_NAME ": ARM SMCCC not supported on this kernel and architecture\n", > + > + return -ENODEV; > +} > + > +static void __exit smccc_test_exit(void) > +{ > +} > + > +#endif > + > +module_init(smccc_test_init); > +module_exit(smccc_test_exit); > diff --git a/smccc_test/smccc_test.h b/smccc_test/smccc_test.h > new file mode 100644 > index 00000000..355f50d3 > --- /dev/null > +++ b/smccc_test/smccc_test.h > @@ -0,0 +1,44 @@ > +/* > + * SMCCC test driver > + * > + * Copyright(C) 2012-2021 Canonical Ltd. Just 2021? > + * > + * 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, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, > + * USA. > + */ > +#ifndef _SMCCC_TEST_H_ > +#define _SMCCC_TEST_H_ > + > +#include <linux/types.h> > + > +struct smccc_test_arg { > + __u32 size; > + __u32 conduit; > + __u32 w[8]; > +}; > + > +#define SMCCC_TEST_PCI_VERSION \ > + _IOWR('p', 0x01, struct smccc_test_arg) > +#define SMCCC_TEST_PCI_FEATURES \ > + _IOWR('p', 0x02, struct smccc_test_arg) > +#define SMCCC_TEST_PCI_READ \ > + _IOWR('p', 0x03, struct smccc_test_arg) > +#define SMCCC_TEST_PCI_WRITE \ > + _IOWR('p', 0x04, struct smccc_test_arg) > +#define SMCCC_TEST_PCI_GET_SEG_INFO \ > + _IOWR('p', 0x05, struct smccc_test_arg) > + > +#endif > + > diff --git a/src/Makefile.am b/src/Makefile.am > index f8066aff..9a26af86 100644 > --- a/src/Makefile.am > +++ b/src/Makefile.am > @@ -11,6 +11,7 @@ AM_CPPFLAGS = \ > -I$(top_srcdir)/src/acpica/source/include \ > -I$(top_srcdir)/src/acpica/source/compiler \ > -I$(top_srcdir)/efi_runtime \ > + -I$(top_srcdir)/smccc_test \ > -pthread `pkg-config --cflags glib-2.0 gio-2.0` \ > -Wall -Werror -Wextra \ > -Wno-address-of-packed-member \ > @@ -186,6 +187,7 @@ fwts_SOURCES = main.c \ > pci/aspm/aspm.c \ > pci/crs/crs.c \ > pci/maxreadreq/maxreadreq.c \ > + pci/smccc/smccc.c \ > tpm/tpmevlog/tpmevlog.c \ > tpm/tpmevlogdump/tpmevlogdump.c \ > uefi/csm/csm.c \ > diff --git a/src/pci/smccc/smccc.c b/src/pci/smccc/smccc.c > new file mode 100644 > index 00000000..a94d0b39 > --- /dev/null > +++ b/src/pci/smccc/smccc.c > @@ -0,0 +1,333 @@ > +/* > + * > + * Copyright (C) 2021 Canonical > + * > + * 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, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. > + * > + */ > +#include "fwts.h" > + > +#ifdef FWTS_ARCH_AARCH64 > + > +#include <errno.h> > +#include <inttypes.h> > +#include <stdio.h> > +#include <stddef.h> > +#include <stdbool.h> > +#include <string.h> > +#include <sys/ioctl.h> > +#include <fcntl.h> > + > +#include "smccc_test.h" > + > +/* > + * ARM SMCCC tests, > + * https://developer.arm.com/documentation/den0115/latest > + */ > + > +/* SMCCC conduit types */ > +enum { > + FWTS_SMCCC_CONDUIT_NONE, > + FWTS_SMCCC_CONDUIT_SMC, > + FWTS_SMCCC_CONDUIT_HVC, > +}; > + > +/* SMCCC API function ids */ > +#define PCI_VERSION (0x84000130) > +#define PCI_FEATURES (0x84000131) > +#define PCI_READ (0x84000132) > +#define PCI_WRITE (0x84000133) > +#define PCI_GET_SEG_INFO (0x84000134) > + > +/* SMCCC API id to name mapping */ > +typedef struct { > + const uint32_t pci_func_id; > + const char *pci_func_id_name; > + bool implemented; > +} pci_func_id_t; > + > +static pci_func_id_t pci_func_ids[] = { > + { PCI_VERSION, "PCI_VERSION", false }, > + { PCI_FEATURES, "PCI_FEATURES", false }, > + { PCI_READ, "PCI_READ", false }, > + { PCI_WRITE, "PCI_WRITE", false }, > + { PCI_GET_SEG_INFO, "PCI_GET_SEG_INFO", false }, > +}; > + > +static const char *module_name = "smccc_test"; > +static const char *dev_name = "/dev/smccc_test"; > +static bool module_loaded; > +static int smccc_fd = -1; > + > +static int smccc_init(fwts_framework *fw) > +{ > + if (fwts_module_load(fw, module_name) != FWTS_OK) { > + module_loaded = false; > + smccc_fd = -1; > + return FWTS_ERROR; > + } > + > + smccc_fd = open(dev_name, O_RDWR); > + if (smccc_fd < 0) { > + smccc_fd = -1; > + fwts_log_error(fw, "Cannot open %s, errno=%d (%s)\n", > + dev_name, errno, strerror(errno)); > + } > + > + return FWTS_OK; > +} > + > +static int smccc_deinit(fwts_framework *fw) > +{ > + if (smccc_fd >= 0) { > + close(smccc_fd); > + smccc_fd = -1; > + } > + > + if (module_loaded && fwts_module_unload(fw, module_name) != FWTS_OK) > + return FWTS_ERROR; > + > + module_loaded = true; > + > + return FWTS_OK; > +} > + > +/* > + * smccc_pci_conduit_name() > + * map the conduit number to human readable string > + */ > +static char *smccc_pci_conduit_name(struct smccc_test_arg *arg) > +{ > + static char unknown[32]; > + > + switch (arg->conduit) { > + case FWTS_SMCCC_CONDUIT_NONE: > + return "SMCCC_CONDUIT_NONE"; > + case FWTS_SMCCC_CONDUIT_HVC: > + return "SMCCC_CONDUIT_HVC"; > + case FWTS_SMCCC_CONDUIT_SMC: > + return "SMCCC_CONDUIT_SMC"; > + default: > + break; > + } > + > + snprintf(unknown, sizeof(unknown), "Unknown: 0x%x", arg->conduit); > + return unknown; > +} > + > +/* > + * smccc_pci_conduit_check() > + * check if conduit number is valid > + */ > +static int smccc_pci_conduit_check(fwts_framework *fw, struct smccc_test_arg *arg) > +{ > + switch (arg->conduit) { > + case FWTS_SMCCC_CONDUIT_HVC: > + return FWTS_OK; > + case FWTS_SMCCC_CONDUIT_SMC: > + return FWTS_OK; > + default: > + fwts_log_error(fw, "Invalid SMCCC conduit used: %s\n", > + smccc_pci_conduit_name(arg)); > + return FWTS_ERROR; > + } > + return FWTS_OK; > +} > + > +/* > + * smccc_pci_func_implemented() > + * return true if function has been implemented, only valid > + * once smccc_pci_features_test has been run. > + */ > +static bool smccc_pci_func_implemented(const uint32_t pci_func_id) > +{ > + size_t i; > + > + for (i = 0; i < FWTS_ARRAY_SIZE(pci_func_ids); i++) { > + if (pci_func_ids[i].pci_func_id == pci_func_id) > + return pci_func_ids[i].implemented; > + } > + return false; > +} > + > +/* > + * smccc_pci_version_test() > + * test SMCCC function PCI_VERSION > + */ > +static int smccc_pci_version_test(fwts_framework *fw) > +{ > + int ret; > + struct smccc_test_arg arg = { }; > + > + arg.size = sizeof(arg); > + arg.w[0] = PCI_VERSION; > + > + ret = ioctl(smccc_fd, SMCCC_TEST_PCI_VERSION, &arg); > + if (ret < 0) { > + fwts_log_error(fw, "SMCCC test driver ioctl SMCCC_TEST_PCI_VERSION " > + "failed, errno=%d (%s)\n", errno, strerror(errno)); > + return FWTS_ERROR; > + } > + if (smccc_pci_conduit_check(fw, &arg) != FWTS_OK) > + return FWTS_ERROR; > + > + fwts_log_info_verbatim(fw, " SMCCC conduit type: 0x%x ('%s')", > + arg.conduit, smccc_pci_conduit_name(&arg)); > + > + fwts_log_info_verbatim(fw, " Major Rev: 0x%" PRIx16 ", Minor Rev: 0x%" PRIx16, > + (arg.w[0] >> 16) & 0xffff, arg.w[0] & 0xffff); > + fwts_passed(fw, "SMCCC v1.0 PCI_VERSION passed"); > + > + return FWTS_OK; > +} > + > +/* > + * smccc_pci_features_test() > + * test SMCCC function PCI_FEATURES > + */ > +static int smccc_pci_features_test(fwts_framework *fw) > +{ > + struct smccc_test_arg arg = { }; > + int ret, implemented_funcs = 0; > + bool passed = true; > + static const char *test = "SMCCC v1.0 PCI_FEATURES"; > + size_t i; > + > + /* > + * Check SMCCC functions are implemented in the firmware > + */ > + for (i = 0; i < FWTS_ARRAY_SIZE(pci_func_ids); i++) { > + memset(&arg, 0, sizeof(arg)); > + > + /* Assume it is not implemented */ > + pci_func_ids[i].implemented = false; > + > + arg.size = sizeof(arg); > + arg.w[0] = PCI_FEATURES; > + arg.w[1] = pci_func_ids[i].pci_func_id; > + ret = ioctl(smccc_fd, SMCCC_TEST_PCI_FEATURES, &arg); > + if (ret < 0) { > + passed = false; > + fwts_failed(fw, LOG_LEVEL_HIGH, "SMCCC_PCI_VERSION", > + "SMCCC test driver ioctl SMCCC_TEST_PCI_FEATURES " > + "failed, errno=%d (%s)\n", errno, strerror(errno)); > + } else { > + const bool implemented = (int)arg.w[0] >= 0; > + > + fwts_log_info_verbatim(fw, " function 0x%x %-18.18s: %simplemented (%x)", > + pci_func_ids[i].pci_func_id, > + pci_func_ids[i].pci_func_id_name, > + implemented ? "" : "not ", > + arg.w[0]); > + if (implemented) { > + pci_func_ids[i].implemented = true; > + implemented_funcs++; > + } > + } > + } > + if (implemented_funcs == 0) > + fwts_log_warning(fw, "Note: No PCI functions were implemented"); > + > + if (passed) > + fwts_passed(fw, "%s", test); > + > + return FWTS_OK; > +} > + > +/* > + * smccc_pci_get_seg_info() > + * test SMCCC function PCI_GET_SEG_INFO > + */ > +static int smccc_pci_get_seg_info(fwts_framework *fw) > +{ > + struct smccc_test_arg arg = { }; > + int ret, segments = 0; > + bool passed = true; > + static const char *test = "SMCCC v1.0 PCI_GET_SEG_INFO"; > + int i; > + > + if (!smccc_pci_func_implemented(PCI_GET_SEG_INFO)) { > + fwts_skipped(fw, "%s: not enabled on this platform", test); > + return EXIT_SUCCESS; > + } > + > + /* > + * Scan over all potential 65536 segment infos.. > + */ > + for (i = 0; i <= 0xffff; i++) { > + memset(&arg, 0, sizeof(arg)); > + > + arg.size = sizeof(arg); > + arg.w[0] = PCI_GET_SEG_INFO; > + arg.w[1] = i & 0xffff; > + ret = ioctl(smccc_fd, SMCCC_TEST_PCI_GET_SEG_INFO, &arg); > + if (ret < 0) { > + passed = false; > + fwts_failed(fw, LOG_LEVEL_HIGH, "SMCCC_PCI_VERSION", > + "SMCCC test driver ioctl PCI_GET_SEG_INFO " > + "failed, errno=%d (%s)\n", errno, strerror(errno)); > + break; > + } else { > + if (arg.w[0] == 0) { > + const uint8_t pci_bus_start = arg.w[1] & 0xff; > + const uint8_t pci_bus_end = (arg.w[1] >> 8) & 0xff; > + const uint32_t next_seg = arg.w[2]; > + > + fwts_log_info_verbatim(fw, " PCI segment %4x: Bus 0x%2.2x .. 0x%2.2x", > + i, (int)pci_bus_start, (int)pci_bus_end); > + segments++; > + > + /* > + * a zero next segment id marks the end > + * of the segment information structs > + */ > + if (next_seg == 0) > + break; > + > + /* if next_seg is valid skip to this */ > + if (next_seg <= 0xffff) > + i = next_seg; > + } else { > + fwts_log_info_verbatim(fw, " PCI segment %4x: error return: %x\n", i, arg.w[0]); > + break; > + } > + } > + } > + if (segments == 0) > + fwts_log_warning(fw, "No PCI segments were found"); > + > + if (passed) > + fwts_passed(fw, "%s", test); > + > + return FWTS_OK; > +} > + > +static fwts_framework_minor_test smccc_tests[] = { > + { smccc_pci_version_test, "Test PCI_VERSION" }, > + { smccc_pci_features_test, "Test PCI_FEATURES" }, > + { smccc_pci_get_seg_info, "Test PCI_GET_SEG_INFO" }, > + { NULL, NULL } > +}; > + > +static fwts_framework_ops smcccops = { > + .description = "ARM64 PCI SMMCCC tests.", > + .init = smccc_init, > + .deinit = smccc_deinit, > + .minor_tests = smccc_tests > +}; > + > +FWTS_REGISTER("smccc", &smcccops, FWTS_TEST_ANYTIME, FWTS_FLAG_UNSAFE | FWTS_FLAG_ROOT_PRIV) > + > +#endif >
diff --git a/debian/control b/debian/control index 9325c96e..7d6213ba 100644 --- a/debian/control +++ b/debian/control @@ -86,3 +86,12 @@ Depends: ${misc:Depends}, Description: Firmware Test Suite UEFI Runtime Service kernel driver This package provides the efi_runtime kernel driver in DKMS format, which is required for accessing UEFI Runtime Services. + +Package: fwts-smccc-dkms +Architecture: arm64 +Priority: optional +Depends: ${misc:Depends}, + dkms +Description: Firmware Test Suite SMCCC firmware kernel driver + This package provides the ARM64 SMCCC kernel driver in DKMS format, + which is required for accessing the ARM64 SMCCC firmware API. diff --git a/debian/fwts-smccc-dkms.dkms b/debian/fwts-smccc-dkms.dkms new file mode 100644 index 00000000..4a1a87cd --- /dev/null +++ b/debian/fwts-smccc-dkms.dkms @@ -0,0 +1,6 @@ +PACKAGE_NAME="fwts-smccc-dkms" +PACKAGE_VERSION="#MODULE_VERSION#" +MAKE[0]="KVER=$kernelver make" +BUILT_MODULE_NAME[0]="smccc_test" +DEST_MODULE_LOCATION[0]="/updates" +AUTOINSTALL="yes" diff --git a/debian/rules b/debian/rules index c24df00f..01b952af 100755 --- a/debian/rules +++ b/debian/rules @@ -9,11 +9,14 @@ DEBVERS := $(shell dpkg-parsechangelog | grep ^Version: | cut -d' ' -f2 \ VERSION := $(shell echo '$(DEBVERS)' | sed -e 's/[+-].*//' -e 's/~//g') -DKMS_SRC_DIR := $(CURDIR)/debian/fwts-efi-runtime-dkms/usr/src/fwts-efi-runtime-dkms-$(VERSION) +DKMS_EFI_RUNTIME_SRC_DIR := $(CURDIR)/debian/fwts-efi-runtime-dkms/usr/src/fwts-efi-runtime-dkms-$(VERSION) +DKMS_SMCCC_SRC_DIR := $(CURDIR)/debian/fwts-smccc-dkms/usr/src/fwts-smccc-dkms-$(VERSION) override_dh_auto_install: - install -d $(DKMS_SRC_DIR) - cp -a efi_runtime/* $(DKMS_SRC_DIR) + install -d $(DKMS_EFI_RUNTIME_SRC_DIR) + cp -a efi_runtime/* $(DKMS_EFI_RUNTIME_SRC_DIR) + install -d $(DKMS_SMCCC_SRC_DIR) + cp -a smccc_test/* $(DKMS_SMCCC_SRC_DIR) dh_auto_install override_dh_dkms: diff --git a/smccc_test/Makefile b/smccc_test/Makefile new file mode 100644 index 00000000..f2faf76b --- /dev/null +++ b/smccc_test/Makefile @@ -0,0 +1,29 @@ +# +# Copyright (C) 2012-2021 Canonical, 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +KVER ?= `uname -r` +KBUILD_MODPOST_WARN=y +obj-m += smccc_test.o +all: + make -C /lib/modules/$(KVER)/build M=`pwd` modules + +install: + make -C /lib/modules/$(KVER)/build M=`pwd` modules_install + +clean: + make -C /lib/modules/$(KVER)/build M=`pwd` clean diff --git a/smccc_test/smccc_test.c b/smccc_test/smccc_test.c new file mode 100644 index 00000000..563e3c08 --- /dev/null +++ b/smccc_test/smccc_test.c @@ -0,0 +1,244 @@ +/* + * SMCCC test driver + * + * Copyright(C) 2021 Canonical 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <linux/version.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +/* + * ARM SMCCC kernel test driver + * https://developer.arm.com/documentation/den0115/latest + */ + +#if defined(__aarch64__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)) +#include <linux/arm-smccc.h> +#endif + +#include "smccc_test.h" + +#define MODULE_NAME "smccc_test" +#define SMCCC_TEST_VERSION (0x00000001) + +MODULE_AUTHOR("Colin Ian King"); +MODULE_DESCRIPTION("SMCCC Test Driver"); +MODULE_LICENSE("GPL"); + +#if defined(__aarch64__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)) + +/* PCI ECAM conduit (defined by ARM DEN0115A) */ +#define SMCCC_PCI_CALL_VAL(val) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_STANDARD, val) + +#ifndef SMCCC_PCI_VERSION +#define SMCCC_PCI_VERSION SMCCC_PCI_CALL_VAL(0x0130) +#endif + +#ifndef SMCCC_PCI_FEATURES +#define SMCCC_PCI_FEATURES SMCCC_PCI_CALL_VAL(0x131) +#endif + +#ifndef SMCCC_PCI_READ +#define SMCCC_PCI_READ SMCCC_PCI_CALL_VAL(0x132) +#endif + +#ifndef SMCCC_PCI_WRITE +#define SMCCC_PCI_WRITE SMCCC_PCI_CALL_VAL(0x133) +#endif + +#ifndef SMCCC_PCI_SEG_INFO +#define SMCCC_PCI_SEG_INFO SMCCC_PCI_CALL_VAL(0x134) +#endif + +/* + * smccc_test_copy_to_user() + * copy arm_res a* registers to user space test_arg w array + */ +static int smccc_test_copy_to_user(unsigned long arg, struct arm_smccc_res *arm_res, int conduit) +{ + struct smccc_test_arg test_arg = { }, __user *test_arg_user; + + test_arg_user = (struct smccc_test_arg __user *)arg; + + test_arg.size = sizeof(test_arg); + test_arg.conduit = conduit; + test_arg.w[0] = arm_res->a0; + test_arg.w[1] = arm_res->a1; + test_arg.w[2] = arm_res->a2; + test_arg.w[3] = arm_res->a3; + + if (copy_to_user(test_arg_user, &test_arg, sizeof(*test_arg_user))) + return -EFAULT; + + return 0; +} + +/* + * smccc_test_copy_from_user() + * copy user space test_arg data to test_arg + */ +static int smccc_test_copy_from_user(struct smccc_test_arg *test_arg, unsigned long arg) +{ + struct smccc_test_arg __user *user; + + user = (struct smccc_test_arg __user *)arg; + return copy_from_user(test_arg, user, sizeof(*test_arg)); +} + +static long smccc_test_pci_version(unsigned long arg) +{ + struct arm_smccc_res arm_res = { }; + int conduit; + + conduit = arm_smccc_1_1_invoke(SMCCC_PCI_VERSION, 0, 0, 0, 0, 0, 0, 0, &arm_res); + + return smccc_test_copy_to_user(arg, &arm_res, conduit); +} + +static long smccc_test_pci_features(unsigned long arg) +{ + struct arm_smccc_res arm_res = { }; + struct smccc_test_arg test_arg; + int ret, conduit; + + ret = smccc_test_copy_from_user(&test_arg, arg); + if (ret) + return ret; + conduit = arm_smccc_1_1_invoke(SMCCC_PCI_FEATURES, test_arg.w[0], 0, 0, 0, 0, 0, 0, &arm_res); + + return smccc_test_copy_to_user(arg, &arm_res, conduit); +} + +static long smccc_test_pci_get_seg_info(unsigned long arg) +{ + struct arm_smccc_res arm_res = { }; + struct smccc_test_arg test_arg; + int ret, conduit; + + ret = smccc_test_copy_from_user(&test_arg, arg); + if (ret) + return ret; + + conduit = arm_smccc_1_1_invoke(SMCCC_PCI_SEG_INFO, test_arg.w[1], 0, 0, 0, 0, 0, 0, &arm_res); + + return smccc_test_copy_to_user(arg, &arm_res, conduit); +} + +static long smccc_test_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + u32 size; + u32 __user *user_size = (u32 __user *)arg; + + if (get_user(size, user_size)) + return -EFAULT; + if (size != sizeof(struct smccc_test_arg)) + return -EINVAL; + + switch (cmd) { + case SMCCC_TEST_PCI_VERSION: + return smccc_test_pci_version(arg); + case SMCCC_TEST_PCI_FEATURES: + return smccc_test_pci_features(arg); + case SMCCC_TEST_PCI_READ: + /* TODO */ + return -ENOTSUPP; + case SMCCC_TEST_PCI_WRITE: + /* TODO */ + return -ENOTSUPP; + case SMCCC_TEST_PCI_GET_SEG_INFO: + return smccc_test_pci_get_seg_info(arg); + default: + break; + } + + return -ENOTTY; +} + +static int smccc_test_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int smccc_test_close(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations smccc_test_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = smccc_test_ioctl, + .open = smccc_test_open, + .release = smccc_test_close, + .llseek = no_llseek, +}; + +static struct miscdevice smccc_test_dev = { + MISC_DYNAMIC_MINOR, + "smccc_test", + &smccc_test_fops +}; + +static int __init smccc_test_init(void) +{ + int ret; + + ret = arm_smccc_get_version(); + pr_info(MODULE_NAME ": ARM SMCCC version %d.%d.%d\n", + (ret >> 16) & 0xff, (ret >> 8) & 0xff, ret & 0xff); + + ret = misc_register(&smccc_test_dev); + if (ret) { + pr_err(MODULE_NAME ": can't misc_register on minor=%d\n", + MISC_DYNAMIC_MINOR); + return ret; + } + + return 0; +} + +static void __exit smccc_test_exit(void) +{ + misc_deregister(&smccc_test_dev); +} + +#else + +static int __init smccc_test_init(void) +{ + pr_info(MODULE_NAME ": ARM SMCCC not supported on this kernel and architecture\n", + + return -ENODEV; +} + +static void __exit smccc_test_exit(void) +{ +} + +#endif + +module_init(smccc_test_init); +module_exit(smccc_test_exit); diff --git a/smccc_test/smccc_test.h b/smccc_test/smccc_test.h new file mode 100644 index 00000000..355f50d3 --- /dev/null +++ b/smccc_test/smccc_test.h @@ -0,0 +1,44 @@ +/* + * SMCCC test driver + * + * Copyright(C) 2012-2021 Canonical 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +#ifndef _SMCCC_TEST_H_ +#define _SMCCC_TEST_H_ + +#include <linux/types.h> + +struct smccc_test_arg { + __u32 size; + __u32 conduit; + __u32 w[8]; +}; + +#define SMCCC_TEST_PCI_VERSION \ + _IOWR('p', 0x01, struct smccc_test_arg) +#define SMCCC_TEST_PCI_FEATURES \ + _IOWR('p', 0x02, struct smccc_test_arg) +#define SMCCC_TEST_PCI_READ \ + _IOWR('p', 0x03, struct smccc_test_arg) +#define SMCCC_TEST_PCI_WRITE \ + _IOWR('p', 0x04, struct smccc_test_arg) +#define SMCCC_TEST_PCI_GET_SEG_INFO \ + _IOWR('p', 0x05, struct smccc_test_arg) + +#endif + diff --git a/src/Makefile.am b/src/Makefile.am index f8066aff..9a26af86 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/acpica/source/include \ -I$(top_srcdir)/src/acpica/source/compiler \ -I$(top_srcdir)/efi_runtime \ + -I$(top_srcdir)/smccc_test \ -pthread `pkg-config --cflags glib-2.0 gio-2.0` \ -Wall -Werror -Wextra \ -Wno-address-of-packed-member \ @@ -186,6 +187,7 @@ fwts_SOURCES = main.c \ pci/aspm/aspm.c \ pci/crs/crs.c \ pci/maxreadreq/maxreadreq.c \ + pci/smccc/smccc.c \ tpm/tpmevlog/tpmevlog.c \ tpm/tpmevlogdump/tpmevlogdump.c \ uefi/csm/csm.c \ diff --git a/src/pci/smccc/smccc.c b/src/pci/smccc/smccc.c new file mode 100644 index 00000000..a94d0b39 --- /dev/null +++ b/src/pci/smccc/smccc.c @@ -0,0 +1,333 @@ +/* + * + * Copyright (C) 2021 Canonical + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include "fwts.h" + +#ifdef FWTS_ARCH_AARCH64 + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stddef.h> +#include <stdbool.h> +#include <string.h> +#include <sys/ioctl.h> +#include <fcntl.h> + +#include "smccc_test.h" + +/* + * ARM SMCCC tests, + * https://developer.arm.com/documentation/den0115/latest + */ + +/* SMCCC conduit types */ +enum { + FWTS_SMCCC_CONDUIT_NONE, + FWTS_SMCCC_CONDUIT_SMC, + FWTS_SMCCC_CONDUIT_HVC, +}; + +/* SMCCC API function ids */ +#define PCI_VERSION (0x84000130) +#define PCI_FEATURES (0x84000131) +#define PCI_READ (0x84000132) +#define PCI_WRITE (0x84000133) +#define PCI_GET_SEG_INFO (0x84000134) + +/* SMCCC API id to name mapping */ +typedef struct { + const uint32_t pci_func_id; + const char *pci_func_id_name; + bool implemented; +} pci_func_id_t; + +static pci_func_id_t pci_func_ids[] = { + { PCI_VERSION, "PCI_VERSION", false }, + { PCI_FEATURES, "PCI_FEATURES", false }, + { PCI_READ, "PCI_READ", false }, + { PCI_WRITE, "PCI_WRITE", false }, + { PCI_GET_SEG_INFO, "PCI_GET_SEG_INFO", false }, +}; + +static const char *module_name = "smccc_test"; +static const char *dev_name = "/dev/smccc_test"; +static bool module_loaded; +static int smccc_fd = -1; + +static int smccc_init(fwts_framework *fw) +{ + if (fwts_module_load(fw, module_name) != FWTS_OK) { + module_loaded = false; + smccc_fd = -1; + return FWTS_ERROR; + } + + smccc_fd = open(dev_name, O_RDWR); + if (smccc_fd < 0) { + smccc_fd = -1; + fwts_log_error(fw, "Cannot open %s, errno=%d (%s)\n", + dev_name, errno, strerror(errno)); + } + + return FWTS_OK; +} + +static int smccc_deinit(fwts_framework *fw) +{ + if (smccc_fd >= 0) { + close(smccc_fd); + smccc_fd = -1; + } + + if (module_loaded && fwts_module_unload(fw, module_name) != FWTS_OK) + return FWTS_ERROR; + + module_loaded = true; + + return FWTS_OK; +} + +/* + * smccc_pci_conduit_name() + * map the conduit number to human readable string + */ +static char *smccc_pci_conduit_name(struct smccc_test_arg *arg) +{ + static char unknown[32]; + + switch (arg->conduit) { + case FWTS_SMCCC_CONDUIT_NONE: + return "SMCCC_CONDUIT_NONE"; + case FWTS_SMCCC_CONDUIT_HVC: + return "SMCCC_CONDUIT_HVC"; + case FWTS_SMCCC_CONDUIT_SMC: + return "SMCCC_CONDUIT_SMC"; + default: + break; + } + + snprintf(unknown, sizeof(unknown), "Unknown: 0x%x", arg->conduit); + return unknown; +} + +/* + * smccc_pci_conduit_check() + * check if conduit number is valid + */ +static int smccc_pci_conduit_check(fwts_framework *fw, struct smccc_test_arg *arg) +{ + switch (arg->conduit) { + case FWTS_SMCCC_CONDUIT_HVC: + return FWTS_OK; + case FWTS_SMCCC_CONDUIT_SMC: + return FWTS_OK; + default: + fwts_log_error(fw, "Invalid SMCCC conduit used: %s\n", + smccc_pci_conduit_name(arg)); + return FWTS_ERROR; + } + return FWTS_OK; +} + +/* + * smccc_pci_func_implemented() + * return true if function has been implemented, only valid + * once smccc_pci_features_test has been run. + */ +static bool smccc_pci_func_implemented(const uint32_t pci_func_id) +{ + size_t i; + + for (i = 0; i < FWTS_ARRAY_SIZE(pci_func_ids); i++) { + if (pci_func_ids[i].pci_func_id == pci_func_id) + return pci_func_ids[i].implemented; + } + return false; +} + +/* + * smccc_pci_version_test() + * test SMCCC function PCI_VERSION + */ +static int smccc_pci_version_test(fwts_framework *fw) +{ + int ret; + struct smccc_test_arg arg = { }; + + arg.size = sizeof(arg); + arg.w[0] = PCI_VERSION; + + ret = ioctl(smccc_fd, SMCCC_TEST_PCI_VERSION, &arg); + if (ret < 0) { + fwts_log_error(fw, "SMCCC test driver ioctl SMCCC_TEST_PCI_VERSION " + "failed, errno=%d (%s)\n", errno, strerror(errno)); + return FWTS_ERROR; + } + if (smccc_pci_conduit_check(fw, &arg) != FWTS_OK) + return FWTS_ERROR; + + fwts_log_info_verbatim(fw, " SMCCC conduit type: 0x%x ('%s')", + arg.conduit, smccc_pci_conduit_name(&arg)); + + fwts_log_info_verbatim(fw, " Major Rev: 0x%" PRIx16 ", Minor Rev: 0x%" PRIx16, + (arg.w[0] >> 16) & 0xffff, arg.w[0] & 0xffff); + fwts_passed(fw, "SMCCC v1.0 PCI_VERSION passed"); + + return FWTS_OK; +} + +/* + * smccc_pci_features_test() + * test SMCCC function PCI_FEATURES + */ +static int smccc_pci_features_test(fwts_framework *fw) +{ + struct smccc_test_arg arg = { }; + int ret, implemented_funcs = 0; + bool passed = true; + static const char *test = "SMCCC v1.0 PCI_FEATURES"; + size_t i; + + /* + * Check SMCCC functions are implemented in the firmware + */ + for (i = 0; i < FWTS_ARRAY_SIZE(pci_func_ids); i++) { + memset(&arg, 0, sizeof(arg)); + + /* Assume it is not implemented */ + pci_func_ids[i].implemented = false; + + arg.size = sizeof(arg); + arg.w[0] = PCI_FEATURES; + arg.w[1] = pci_func_ids[i].pci_func_id; + ret = ioctl(smccc_fd, SMCCC_TEST_PCI_FEATURES, &arg); + if (ret < 0) { + passed = false; + fwts_failed(fw, LOG_LEVEL_HIGH, "SMCCC_PCI_VERSION", + "SMCCC test driver ioctl SMCCC_TEST_PCI_FEATURES " + "failed, errno=%d (%s)\n", errno, strerror(errno)); + } else { + const bool implemented = (int)arg.w[0] >= 0; + + fwts_log_info_verbatim(fw, " function 0x%x %-18.18s: %simplemented (%x)", + pci_func_ids[i].pci_func_id, + pci_func_ids[i].pci_func_id_name, + implemented ? "" : "not ", + arg.w[0]); + if (implemented) { + pci_func_ids[i].implemented = true; + implemented_funcs++; + } + } + } + if (implemented_funcs == 0) + fwts_log_warning(fw, "Note: No PCI functions were implemented"); + + if (passed) + fwts_passed(fw, "%s", test); + + return FWTS_OK; +} + +/* + * smccc_pci_get_seg_info() + * test SMCCC function PCI_GET_SEG_INFO + */ +static int smccc_pci_get_seg_info(fwts_framework *fw) +{ + struct smccc_test_arg arg = { }; + int ret, segments = 0; + bool passed = true; + static const char *test = "SMCCC v1.0 PCI_GET_SEG_INFO"; + int i; + + if (!smccc_pci_func_implemented(PCI_GET_SEG_INFO)) { + fwts_skipped(fw, "%s: not enabled on this platform", test); + return EXIT_SUCCESS; + } + + /* + * Scan over all potential 65536 segment infos.. + */ + for (i = 0; i <= 0xffff; i++) { + memset(&arg, 0, sizeof(arg)); + + arg.size = sizeof(arg); + arg.w[0] = PCI_GET_SEG_INFO; + arg.w[1] = i & 0xffff; + ret = ioctl(smccc_fd, SMCCC_TEST_PCI_GET_SEG_INFO, &arg); + if (ret < 0) { + passed = false; + fwts_failed(fw, LOG_LEVEL_HIGH, "SMCCC_PCI_VERSION", + "SMCCC test driver ioctl PCI_GET_SEG_INFO " + "failed, errno=%d (%s)\n", errno, strerror(errno)); + break; + } else { + if (arg.w[0] == 0) { + const uint8_t pci_bus_start = arg.w[1] & 0xff; + const uint8_t pci_bus_end = (arg.w[1] >> 8) & 0xff; + const uint32_t next_seg = arg.w[2]; + + fwts_log_info_verbatim(fw, " PCI segment %4x: Bus 0x%2.2x .. 0x%2.2x", + i, (int)pci_bus_start, (int)pci_bus_end); + segments++; + + /* + * a zero next segment id marks the end + * of the segment information structs + */ + if (next_seg == 0) + break; + + /* if next_seg is valid skip to this */ + if (next_seg <= 0xffff) + i = next_seg; + } else { + fwts_log_info_verbatim(fw, " PCI segment %4x: error return: %x\n", i, arg.w[0]); + break; + } + } + } + if (segments == 0) + fwts_log_warning(fw, "No PCI segments were found"); + + if (passed) + fwts_passed(fw, "%s", test); + + return FWTS_OK; +} + +static fwts_framework_minor_test smccc_tests[] = { + { smccc_pci_version_test, "Test PCI_VERSION" }, + { smccc_pci_features_test, "Test PCI_FEATURES" }, + { smccc_pci_get_seg_info, "Test PCI_GET_SEG_INFO" }, + { NULL, NULL } +}; + +static fwts_framework_ops smcccops = { + .description = "ARM64 PCI SMMCCC tests.", + .init = smccc_init, + .deinit = smccc_deinit, + .minor_tests = smccc_tests +}; + +FWTS_REGISTER("smccc", &smcccops, FWTS_TEST_ANYTIME, FWTS_FLAG_UNSAFE | FWTS_FLAG_ROOT_PRIV) + +#endif