diff mbox series

ARM64 SMCCC firmware API tests

Message ID 20210823192542.1899768-1-colin.king@canonical.com
State Superseded
Headers show
Series ARM64 SMCCC firmware API tests | expand

Commit Message

Colin Ian King Aug. 23, 2021, 7:25 p.m. UTC
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

Comments

Alex Hung Aug. 23, 2021, 9:38 p.m. UTC | #1
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 mbox series

Patch

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