[Skiboot] [RFC 2/2] external/opal-occ: Add usersapce wrapper for OPAL-OCC cmd/rsp interface

Cyril Bur cyrilbur at gmail.com
Tue Jun 6 15:50:54 AEST 2017


On Mon, 2017-06-05 at 10:10 +0530, Shilpasri G Bhat wrote:
> This patch adds userspace application to issue OCC commands. The
> opal-occ utility is included within the opal-prd utility.
> 

Hi Shilpasri,

Overall, looks good - just one thing really, see below.

Cyril

> Signed-off-by: Shilpasri G Bhat <shilpa.bhat at linux.vnet.ibm.com>
> ---
>  external/opal-prd/Makefile   |   8 +-
>  external/opal-prd/opal-occ.c | 290 +++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 296 insertions(+), 2 deletions(-)
>  create mode 100644 external/opal-prd/opal-occ.c
> 
> diff --git a/external/opal-prd/Makefile b/external/opal-prd/Makefile
> index ccd3401..6389656 100644
> --- a/external/opal-prd/Makefile
> +++ b/external/opal-prd/Makefile
> @@ -8,7 +8,7 @@ sbindir = $(prefix)/sbin
>  datadir = $(prefix)/share
>  mandir = $(datadir)/man
>  
> -all: links arch_links | opal-prd
> +all: links arch_links | opal-prd opal-occ
>  
>  GET_ARCH = ../../external/common/get_arch.sh
>  include ../../external/common/rules.mk
> @@ -43,6 +43,9 @@ $(LIBFLASH_OBJS): libflash-%.o : libflash/%.c
>  opal-prd: $(OBJS)
>  	$(Q_LINK)$(LINK.o) -o $@ $^
>  
> +opal-occ: opal-occ.o
> +	$(Q_LINK)$(LINK.o) -o $@ $^
> +
>  version.c: ../../make_version.sh .version
>  	@(if [ "a$(OPAL_PRD_VERSION)" = "a" ]; then \
>  	echo "#error You need to set OPAL_PRD_VERSION environment variable" > $@ ;\
> @@ -63,10 +66,11 @@ test/test_pnor: test/test_pnor.o pnor.o $(LIBFLASH_OBJS) common-arch_flash.o
>  
>  install: all
>  	install -D opal-prd $(DESTDIR)$(sbindir)/opal-prd
> +	install -D opal-occ $(DESTDIR)$(sbindir)/opal-occ
>  	install -D -m 0644 opal-prd.8 $(DESTDIR)$(mandir)/man8/opal-prd.8
>  
>  clean:
> -	$(RM) *.[odsa] opal-prd
> +	$(RM) *.[odsa] opal-prd opal-occ
>  	$(RM) test/*.[odsa] test/test_pnor
>  
>  distclean: clean
> diff --git a/external/opal-prd/opal-occ.c b/external/opal-prd/opal-occ.c
> new file mode 100644
> index 0000000..72460d2
> --- /dev/null
> +++ b/external/opal-prd/opal-occ.c
> @@ -0,0 +1,290 @@
> +/* Copyright 2017 IBM Corp.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at
> + *
> + *	http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> + * implied.
> + * See the License for the specific language governing permissions and
> + * imitations under the License.
> + */
> +
> +#define _GNU_SOURCE
> +
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <getopt.h>
> +#include <fcntl.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <errno.h>
> +
> +#include <opal-api.h>
> +
> +struct command_info {
> +	enum occ_cmd cmd;
> +	enum occ_cmd_data_length csize;
> +	enum occ_rsp_data_length rsize;
> +};
> +
> +static struct command_info cmds[] = {
> +	{	OCC_CMD_AMESTER_PASS_THRU,
> +		OCC_CMD_DL_AMESTER_PASS_THRU,
> +		OCC_RSP_DL_AMESTER_PASS_THRU
> +	},
> +	{	OCC_CMD_CLEAR_SENSOR_DATA,
> +		OCC_CMD_DL_CLEAR_SENSOR_DATA,
> +		OCC_RSP_DL_CLEAR_SENSOR_DATA
> +	},
> +	{	OCC_CMD_SET_POWER_CAP,
> +		OCC_CMD_DL_SET_POWER_CAP,
> +		OCC_RSP_DL_SET_POWER_CAP
> +	},
> +	{	OCC_CMD_SET_POWER_SHIFTING_RATIO,
> +		OCC_CMD_DL_SET_POWER_SHIFTING_RATIO,
> +		OCC_RSP_DL_SET_POWER_SHIFTING_RATIO
> +	},
> +};
> +
> +static void usage(const char *progname)
> +{
> +	printf("Usage:\n");
> +	printf("\t%s -c <chipid> -p|--power-cap <value>\n", progname);
> +	printf("\t%s -c <chipid> -s|--power-shift <value>\n", progname);
> +	printf("\t%s -c <chipid> -l|--clear-sensor-limits "
> +			"<CSM|Profiler|Job-Scheduler>\n", progname);
> +	printf("\t%s -c <chipid> -a|--amester-passthru "
> +			"<hex bytes...>\n", progname);
> +}
> +
> +static struct option occ_diag_options[] = {
> +	{"chip", required_argument, NULL, 'c'},
> +	{"power-cap", required_argument, NULL, 'p'},
> +	{"power-shift", required_argument, NULL, 's'},
> +	{"clear-sensor-limits", required_argument, NULL, 'l'},
> +	{"amester-passthru", required_argument, NULL, 'a'},
> +	{"help", no_argument, NULL, 'h'},
> +	{0},
> +};
> +
> +int main(int argc, char *argv[])
> +{
> +	char occ_node[15];
> +	struct opal_occ_cmd_data *cmd;
> +	struct opal_occ_rsp_data *rsp;
> +	char *str;
> +	u64 cmd_data;
> +	int fd, rsp_size, chip = -1, rc, i;
> +	enum occ_cmd ocmd = OCC_CMD_LAST;
> +
> +	/* Parse options */
> +	for (;;) {
> +		int c;
> +
> +		c = getopt_long(argc, argv, "c:p:s:l:a:h", occ_diag_options,
> +				NULL);
> +		if (c == -1)
> +			break;
> +
> +		switch (c) {
> +		case 'c':
> +			chip = atoi(optarg);
> +			break;
> +		case 'p':
> +			ocmd = OCC_CMD_SET_POWER_CAP;
> +			cmd_data = atoi(optarg);
> +			break;
> +		case 's':
> +			ocmd = OCC_CMD_SET_POWER_SHIFTING_RATIO;
> +			cmd_data = atoi(optarg);
> +			break;
> +		case 'l':
> +			ocmd = OCC_CMD_CLEAR_SENSOR_DATA;
> +			str = optarg;
> +			break;
> +		case 'a':
> +			ocmd = OCC_CMD_AMESTER_PASS_THRU;
> +			cmd_data = optind - 1;
> +			break;
> +		case 'h':
> +			usage(argv[0]);
> +			return EXIT_SUCCESS;
> +		case '?':
> +		default:
> +			usage(argv[0]);
> +			return EXIT_FAILURE;
> +		}
> +
> +		if (ocmd != OCC_CMD_LAST)
> +			break;
> +	}
> +
> +	if (ocmd == OCC_CMD_LAST) {
> +		printf("Specify a command\n");
> +		usage(argv[0]);
> +		return EXIT_FAILURE;
> +	}
> +
> +	if (chip == -1) {
> +		printf("Requires -c --chip option to be specified\n");
> +		return EXIT_FAILURE;
> +	}
> +
> +	snprintf(occ_node, sizeof(occ_node), "/dev/occ%d", chip);
> +	if (access(occ_node, 0)) {
> +		printf("Failed to access node %s\n", occ_node);
> +		return EXIT_FAILURE;
> +	}
> +
> +	fd = open(occ_node, O_RDWR);
> +	if (fd < 0) {
> +		printf("Can't open OCC device %s: %m", occ_node);
> +		return EXIT_FAILURE;
> +	}
> +
> +	if (cmds[ocmd].csize) {
> +		cmd = malloc(sizeof(struct opal_occ_cmd_data) +
> +			     cmds[ocmd].csize);
> +		if (!cmd) {
> +			rc = -ENOMEM;
> +			goto close_fd;
> +		}
> +		cmd->cmd = ocmd;
> +		cmd->size = cmds[ocmd].csize;
> +	}
> +
> +	switch (ocmd) {
> +	case OCC_CMD_SET_POWER_CAP:
> +		if (cmd_data >> cmd->size * 8) {
> +			printf("Invalid power cap %d\n", (int)cmd_data);
> +			rc = -EINVAL;
> +			goto free_cdata;
> +		}
> +
> +		memcpy(cmd->data, &cmd_data, cmd->size);
> +		break;
> +	case OCC_CMD_SET_POWER_SHIFTING_RATIO:
> +		if (cmd_data >> cmd->size * 8) {
> +			printf("Invalid power shift ratio %d\n", (int)cmd_data);
> +			rc = -EINVAL;
> +			goto free_cdata;
> +		}
> +
> +		memcpy(cmd->data, &cmd_data, cmd->size);
> +		break;
> +	case OCC_CMD_CLEAR_SENSOR_DATA:
> +		if (!strcmp(str, "CSM")) {
> +			cmd_data = OCC_SENSOR_LIMIT_GROUP_CSM;
> +		} else if (!strcmp(str, "Profiler")) {
> +			cmd_data = OCC_SENSOR_LIMIT_GROUP_PROFILER;
> +		} else if (!strcmp(str, "Job-Scheduler")) {
> +			cmd_data = OCC_SENSOR_LIMIT_GROUP_JOB_SCHED;
> +		} else {
> +			printf("Invalid sensor limit group type\n");
> +			rc = EXIT_FAILURE;
> +			goto free_cdata;
> +		}
> +
> +		memcpy(cmd->data, &cmd_data, cmd->size);
> +		break;
> +	case OCC_CMD_AMESTER_PASS_THRU:
> +		if (argc - cmd_data > MAX_OPAL_CMD_DATA_LENGTH) {
> +			printf("Too large data\n");
> +			rc = -EINVAL;
> +			goto close_fd;
> +		}
> +
> +		cmd = malloc(sizeof(*cmd) + argc - cmd_data);
> +		if (!cmd) {
> +			rc = -ENOMEM;
> +			goto close_fd;
> +		}
> +		cmd->cmd = OCC_CMD_AMESTER_PASS_THRU;
> +		cmd->size = argc - cmd_data;
> +		for (i = 0; i < cmd->size; i++)
> +			sscanf(argv[i + cmd_data], "%hhx", &cmd->data[i]);
> +		break;
> +	default:
> +		return EXIT_FAILURE;
> +	}
> +
> +	rc = write(fd, cmd, sizeof(*cmd) + cmd->size);
> +	if (rc <= 0) {

Probably worth erroring out if rc != sizeof(*cmd) + cmd->size - feels
like you're guaranteed to get an error back from OCC if you couldn't
write the entire command.

Might also be worth printing errno.

> +		printf("Write failed with %d\n", rc);
> +		goto free_cdata;
> +	}
> +
> +	if (cmds[ocmd].rsize)
> +		rsp_size = sizeof(*rsp) + cmds[ocmd].rsize;
> +	else
> +		rsp_size = sizeof(*rsp) + MAX_OCC_RSP_DATA_LENGTH;
> +
> +	rsp = malloc(rsp_size);
> +	if (!rsp)
> +		goto free_cdata;
> +
> +	rc = read(fd, rsp, rsp_size);
> +	if (rc <= 0) {

Same as with the call to write()

> +		printf("Read failed %d\n", rc);
> +		goto out;
> +	}
> +
> +	switch (rsp->status) {
> +	case OCC_SUCCESS:
> +		printf("OCC: Success\n");
> +		break;
> +	case OCC_INVALID_COMMAND:
> +		printf("OCC: Invalid Command\n");
> +		break;
> +	case OCC_INVALID_CMD_DATA_LENGTH:
> +		printf("OCC: Invalid Command Data length\n");
> +		break;
> +	case OCC_INVALID_DATA:
> +		printf("OCC: Invalid Data\n");
> +		break;
> +	case OCC_INTERNAL_ERROR:
> +		printf("OCC: Internal OCC error\n");
> +		break;
> +	default:
> +		printf("OCC: Unrecognized response status\n");
> +	}
> +
> +	if (rsp->status)
> +		goto out;

Rather than break; in the switch statement, you could have goto out;
which removes the need for this statement.

> +
> +	switch (ocmd) {
> +	case OCC_CMD_SET_POWER_CAP:
> +		printf("Power Cap set to %dW\n", *(u16 *)rsp->data);
> +		break;
> +	case OCC_CMD_SET_POWER_SHIFTING_RATIO:
> +		printf("Power Shifting ratio set to %d%%\n", *(u8 *)rsp->data);
> +		break;
> +	case OCC_CMD_CLEAR_SENSOR_DATA:
> +		printf("Sensor limit cleared for %d\n", *(u8 *)rsp->data);
> +		break;
> +	case OCC_CMD_AMESTER_PASS_THRU:
> +		printf("Amester response data size = %d\n", rsp->size);
> +		for (i = 0; i < rsp->size; i++) {
> +			printf("%hhx ", rsp->data[i]);
> +			if (i && !(i % 8))
> +				printf("\n");
> +		}
> +		printf("\n");
> +		break;
> +	default:
> +		break;
> +	}
> +
> +out:
> +	free(rsp);
> +free_cdata:
> +	free(cmd);
> +close_fd:
> +	close(fd);
> +	return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
> +}


More information about the Skiboot mailing list