@@ -11,6 +11,7 @@ AM_CFLAGS += -I$(top_srcdir)/include
helperincludedir = $(includedir)/odp/helper/
helperinclude_HEADERS = \
$(srcdir)/include/odp/helper/linux.h \
+ $(srcdir)/include/odp/helper/linux_isolation.h \
$(srcdir)/include/odp/helper/chksum.h\
$(srcdir)/include/odp/helper/eth.h\
$(srcdir)/include/odp/helper/icmp.h\
@@ -31,6 +32,7 @@ __LIB__libodphelper_la_SOURCES = \
eth.c \
ip.c \
linux.c \
+ cpumask_cli.c \
hashtable.c \
lineartable.c
new file mode 100644
@@ -0,0 +1,954 @@
+/**
+ * This file contains declarations and definitions of functions and
+ * data structures which are useful for specifying and/or determining
+ * the CPU resources available to OpenDataPlane (ODP) applications.
+ *
+ * NOTE:
+ *
+ * The functions and data structures included here are responsible for
+ * providing optimized maps of the CPU resources which are present on the
+ * host system - independent of any isolation setup.
+ * They are required as prerequisites to isolation setup but do not require
+ * isolation support.
+ *
+ * Copyright (c) 2016, Linaro Limited
+ * All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <pthread.h>
+#include <sched.h>
+#include <semaphore.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/prctl.h>
+
+#include <odp/api/init.h>
+#include <odp_internal.h>
+#include <odp/api/cpumask.h>
+#include <odp/api/debug.h>
+#include <odp_debug_internal.h>
+#include <odp/helper/linux.h>
+#include "odph_debug.h"
+
+/*
+ * Flag indicating if command line options specified support
+ * for CPU isolation in this ODP instance / application.
+ * Used as a run-time switch to enable or disable isolation setup
+ */
+int isolation_requested;
+
+/*
+ * Number of logical CPUs installed on the system
+ */
+int numcpus;
+
+/*
+ * Shared path construction buffers
+ * Used to construct absolute paths to directories and files in the
+ * cpuset file hierarchy. Shared in order to reduce stack usage.
+ * RESERVED FOR INTERNAL USE - DO NOT ACCESS DIRECTLY FROM APPLICATIONS
+ */
+char pathname_buf[257];
+char cpuname[5];
+
+/*
+ * The following constants are important for determining isolation capacities
+ * MAX_CPU_NUMBER is used to dimension arrays and some loops in the
+ * isolation helper code.
+ * The HOUSEKEEPING_RATIO_* constants define the ratio of housekeeping CPUs
+ * (i.e. 'control plane' CPUs) - see MULTIPLIER
+ * versus isolated CPUs (i.e. 'data plane CPUs) -
+ * see DIVISOR
+ * The calculation is:
+ * NUMBER OF HOUSEKEEPING CPUs =
+ * (NUMBER OF CPUs * HOUSEKEEPING_RATIO_MULTIPLIER)
+ * divided by HOUSEKEEPING_RATIO_DIVISOR.
+ * If NUMBER OF HOUSEKEEPING CPUs < 1, NUMBER OF HOUSEKEEPING CPUs ++
+ * NUMBER OF ISOLATED CPUs =
+ * NUMBER OF CPUs - NUMBER OF HOUSEKEEPING CPUs
+ */
+#define HOUSEKEEPING_RATIO_MULTIPLIER 1
+#define HOUSEKEEPING_RATIO_DIVISOR 4
+
+/*
+ * ODP initialization cpumasks
+ *
+ * These cpumasks are globally accessible and are used to enable external
+ * entities to specify the CPU resources available to a given ODP instance
+ * or application. In our case they are initialized from the application
+ * command line if present and left unpopulated otherwise.
+ * If isolation was requested but one or both of these masks were left
+ * unspecified then the isolation setup will populate the mask(s)
+ * automatically.
+ * RESERVED FOR INTERNAL USE - DO NOT ACCESS DIRECTLY FROM APPLICATIONS
+ */
+odp_cpumask_t init_control_cpus;
+odp_cpumask_t init_worker_cpus;
+
+/*
+ * Pointers to cpumasks passed in from command line arguments
+ * NULL if associated cpumask not specified on command line or
+ * derived automatically due to isolation setup requested on command line.
+ * Used to determine contents of ODP initialization cpumasks prior to
+ * calling odp_init_global().
+ * RESERVED FOR INTERNAL USE - DO NOT ACCESS DIRECTLY FROM APPLICATIONS
+ */
+cpu_set_t *cli_control;
+cpu_set_t *cli_worker;
+
+/* CPUSET masks passed in from command line arguments */
+static cpu_set_t cli_control_mask;
+static cpu_set_t cli_worker_mask;
+
+/* cpumask of logical CPUs representing physical CPU cores */
+static cpu_set_t primary_cpus;
+
+/*
+ * Array of cpumasks showing thread siblings for each primary CPU
+ * RESERVED FOR INTERNAL USE - DO NOT ACCESS DIRECTLY FROM APPLICATIONS
+ */
+cpu_set_t hyperthread_cpus[MAX_CPU_NUMBER];
+
+/*
+ * Create a cpuset bitmask from an input string containing a comma-separated
+ * list of decimal CPU numbers and / or dash-separated CPU ranges
+ */
+static int cpumask_from_list(cpu_set_t *mask, char *list)
+{
+ int retval = 0;
+ char *cur_token;
+ char *endptr;
+ int cpu, end_cpu;
+
+ if (mask && list) {
+ if (mask == cli_control)
+ printf("\rCreating control mask from list %s\n", list);
+ else
+ printf("\rCreating worker mask from list %s\n", list);
+ /*
+ * Break the input list into (comma-separated) tokens and
+ * process one token at a time
+ */
+ CPU_ZERO(mask);
+ cur_token = strtok(list, ",");
+ /* Specifying an empty CPU list is strange but permissible */
+ while (cur_token && !retval) {
+ /*
+ * The current token string should contain either
+ * a single decimal CPU number or a range of CPUs
+ * separated by a dash character '-'
+ * Get the first / only CPU number from the string
+ */
+ errno = 0;
+ cpu = (int)strtoul(cur_token, &endptr, 10);
+ if (errno || (endptr == cur_token) ||
+ (cpu >= CPU_SETSIZE)) {
+ /* No valid decimal number found - abort */
+ retval = -1;
+ break;
+ }
+ /*
+ * Mark the first / only specified CPU
+ * from this token into the caller's bitmask.
+ * Then check to see if this token specifies
+ * a range of CPUS instead of a single CPU.
+ */
+ CPU_SET(cpu, mask);
+ if (*endptr == '-') {
+ /*
+ * Looks like this token is a range...
+ * Get the ending CPU number
+ */
+ cur_token = ++endptr;
+ end_cpu = (int)strtoul(cur_token, &endptr, 10);
+ if (errno || (endptr == cur_token) ||
+ (cpu > end_cpu) ||
+ (end_cpu >= CPU_SETSIZE)) {
+ /* Range end CPU is invalid - abort */
+ retval = -1;
+ break;
+ }
+ /*
+ * Mark the remaining CPUs within the
+ * specified range into the caller's
+ * CPU bitmask.
+ */
+ for (++cpu; cpu <= end_cpu; cpu++)
+ CPU_SET(cpu, mask);
+ }
+ /*
+ * Now pick up the next comma-separated token
+ * (if any remains) from the caller's CPU list
+ */
+ if (!retval)
+ cur_token = strtok((char *)NULL, ",");
+ }
+ } else
+ /* One or both input pointers were NIL */
+ retval = -1;
+ return retval;
+}
+
+/*
+ * Create a cpuset bitmask from an input string containing an ODP-compliant
+ * hexadecimal number representing the bitmask contents.
+ */
+static int cpumask_from_number(cpu_set_t *mask, char *number)
+{
+ int retval = 0;
+ int cpu;
+ static odp_cpumask_t temp;
+
+ if (mask && number) {
+ if (mask == cli_control)
+ printf("\rCreating control mask from number %s\n",
+ number);
+ else
+ printf("\rCreating worker mask from number %s\n",
+ number);
+
+ /*
+ * Decode the bitmask string per ODP specifications,
+ * creating a temporary ODP CPU bitmask.
+ */
+ odp_cpumask_from_str(&temp, (const char *)number);
+
+ /*
+ * Copy CPU bits from the opaque ODP CPU bitmask data type
+ * into the opaque Linux CPU bitmask type which we need here
+ * for evaluation, cleanup, and isolation setup.
+ */
+ CPU_ZERO(mask);
+ for (cpu = 0; cpu < CPU_SETSIZE; cpu++)
+ if (odp_cpumask_isset(&temp, cpu))
+ CPU_SET(cpu, mask);
+ } else
+ /* One or both input pointers were NIL */
+ retval = -1;
+ return retval;
+}
+
+/*
+ * Populate a cpumask of 'primary' CPUs and a cpumask of
+ * hyperthread siblings for the current 'primary' CPU.
+ */
+static inline void process_sibling_list(void)
+{
+ char *endptr;
+ char *remaining;
+ char *cur_token;
+ long int cpu_long;
+ int cpu;
+
+ cur_token = strtok_r(pathname_buf, ",", &remaining);
+ if (cur_token) {
+ errno = 0;
+ cpu_long = strtol(cur_token, &endptr, 10);
+ if (!(errno || (endptr == cur_token))) {
+ /*
+ * Mark the first sibling as a 'primary' CPU
+ */
+ cpu = (int)cpu_long;
+ CPU_SET(cpu, &primary_cpus);
+
+ /* Check for hyperthread siblings */
+ cur_token = strtok_r((char *)NULL, ",", &remaining);
+ /*
+ * For default CPU availability we only care about
+ * siblings for CPU 0 - the only 'primary' CPU
+ * used for 'control' tasks
+ */
+ while (cur_token) {
+ errno = 0;
+ cpu_long = strtol(cur_token, &endptr, 10);
+ if (!(errno || (endptr == cur_token))) {
+ /*
+ * Mark any other siblings found
+ * as 'hyperthread' CPUs
+ */
+ CPU_SET(cpu_long,
+ &hyperthread_cpus[cpu]);
+ }
+ cur_token = strtok_r((char *)NULL, ",",
+ &remaining);
+ }
+ }
+ }
+}
+
+/*
+ * Examine the topology information for the current configured 'logical CPU'
+ * and populate a cpumask of 'primary' CPUs and a cpumask of
+ * hyperthread siblings for the current 'primary' CPU.
+ */
+static void process_cpu_info_dir(long int cpu_idnum)
+{
+ FILE *cpulist_file;
+ char *unused;
+
+ /* Track number of logical CPUs discovered */
+ if (numcpus < (int)(cpu_idnum + 1))
+ numcpus = (int)(cpu_idnum + 1);
+
+ if (cpu_idnum < MAX_CPU_NUMBER) {
+ /* Build a pathname to the CPU siblings list */
+ strcpy(pathname_buf, "/sys/devices/system/cpu/cpu");
+ sprintf(cpuname, "%ld", cpu_idnum);
+ strcat(pathname_buf, cpuname);
+ strcat(pathname_buf, "/topology/thread_siblings_list");
+
+ /* Open the siblings list file */
+ cpulist_file = fopen(pathname_buf, "r");
+ if (cpulist_file) {
+ /* Read and process the thread sibling list */
+ unused = fgets(pathname_buf,
+ (int)(sizeof(pathname_buf) - 1),
+ cpulist_file);
+ /* Make the C compiler happy - use 'unused' */
+ if (unused)
+ unused = (char *)NULL;
+ process_sibling_list();
+ fclose(cpulist_file);
+ }
+ }
+}
+
+/*
+ * To prepare for setup of isolated CPU environments we need to know about
+ * all CPUs which were discovered at boot time.
+ * Furthermore, on platforms with 'hyperthreading' enabled, each physical
+ * CPU core shows up as two (or more) logical CPUs despite the fact that
+ * the 'logical CPUs' share some of the hardware in the physical CPU core.
+ * Consequently performance of an isolated CPU may be compromised if its
+ * 'logical CPU' siblings are also running tasks.
+ * The code below populates a set of available physical CPU cores as well as
+ * an array of hyperthreaded siblings. These data may then be used
+ * to derive optimal sets of isolated CPUs and housekeeping CPUs.
+ */
+static void get_cpu_topology(void)
+{
+ char *numptr;
+ char *endptr;
+ long int cpu_idnum;
+ int cur_cpu;
+ DIR *d;
+ struct dirent *dir;
+
+ CPU_ZERO(&primary_cpus);
+ for (cur_cpu = 0; cur_cpu < MAX_CPU_NUMBER; cur_cpu++)
+ CPU_ZERO(&hyperthread_cpus[cur_cpu]);
+ numcpus = 0;
+
+ /*
+ * Scan the /sysfs pseudo-filesystem for CPU info directories.
+ * There should be one subdirectory for each installed logical CPU
+ */
+ d = opendir("/sys/devices/system/cpu");
+ if (d) {
+ while ((dir = readdir(d)) != NULL) {
+ cpu_idnum = CPU_SETSIZE;
+
+ /*
+ * If the current directory entry doesn't represent
+ * a CPU info subdirectory then skip to the next entry.
+ */
+ if (dir->d_type == DT_DIR) {
+ if (!strncmp(dir->d_name, "cpu", 3)) {
+ /*
+ * Directory name starts with "cpu"...
+ * Try to extract a CPU ID number
+ * from the remainder of the dirname.
+ */
+ errno = 0;
+ numptr = dir->d_name;
+ numptr += 3;
+ cpu_idnum = strtol(numptr, &endptr,
+ 10);
+ if (errno || (endptr == numptr))
+ continue;
+ } else {
+ continue;
+ }
+ } else {
+ continue;
+ }
+ /*
+ * If we get here the current directory entry specifies
+ * a CPU info subdir for the CPU indexed by cpu_idnum.
+ */
+ process_cpu_info_dir(cpu_idnum);
+ }
+ closedir(d);
+ }
+
+ if (numcpus > MAX_CPU_NUMBER) {
+ ODPH_ERR("\rNOTE: MAX_CPU_NUMBER defined as: %d,\n",
+ MAX_CPU_NUMBER);
+ ODPH_ERR("\r but number of CPU cores detected is: %d\n",
+ numcpus);
+ ODPH_ERR("\r Change MAX_CPU_NUMBER in odp_internal.h and rebuild\n");
+ ODPH_ERR("\r to support use of all CPU cores on this platform\n");
+ }
+ numcpus = (numcpus > MAX_CPU_NUMBER) ? MAX_CPU_NUMBER : numcpus;
+}
+
+/*
+ * Determine what CPU resources are available to this ODP instance
+ *
+ * First the CPU topology of the underlying system is discovered and installed
+ * logical CPUs are both counted and identified as either 'primary' or
+ * 'thread sibling' CPUs.
+ *
+ * If one or more cpumasks were specified via command line arguments,
+ * those arguments are used as suggestions and limiting constraints as to
+ * what CPU resources may be used for control tasks, worker tasks, or both.
+ *
+ * If isolated operation is either not enabled or not requested on the
+ * command line, then any cpumasks specified are passed directly as
+ * initialization cpumasks to odp_init_global().
+ *
+ * If one or both cpumasks were not specified on the command line,
+ * but isolated operation is enabled and was requested on the command line,
+ * the missing cpu availability data are obtained automatically as needed.
+ * The cpumasks derived from command line input or automatic algorithms
+ * are then passed as initialization cpumasks to odp_init_global().
+ *
+ * If either control or worker cpumask was neither specified nor derived,
+ * the corresponding initialization cpumask pointer is set to NULL so that
+ * odp_init_global() will select the default configuration for that cpumask.
+ * This ensures a 'default' command line will result in a 'default' setup
+ * for control and worker cpumasks.
+ *
+ * @param my_parms Pointer to ODP initialization data structure to be
+ * passed to odp_init_global()
+ */
+void get_available_cpus(odp_init_t *my_parms)
+{
+#ifdef ISOLATION_ENABLED
+ int num_siblings, num_control_cpus, num_worker_cpus;
+#endif /* ISOLATION_ENABLED */
+
+ int cpu;
+
+ odp_cpumask_zero(&init_control_cpus);
+ odp_cpumask_zero(&init_worker_cpus);
+
+ /*
+ * Derive cpumasks of configured 'primary' CPU cores and
+ * a cpumask of thread siblings for each 'primary' CPU configured.
+ */
+ get_cpu_topology();
+
+#ifdef ISOLATION_ENABLED
+ /*
+ * If isolation enabled but not requested for this ODP instance,
+ * then skip cpumask optimization and pass any CLI input as-is.
+ */
+ if (!isolation_requested)
+ goto init_cpumask_parms;
+
+ /*
+ * First ensure that only 'primary' CPUs are considered from those
+ * specified for the 'worker' scheduling cpumask.
+ * Also ensure CPU 0 is not included in the worker mask.
+ */
+ if (cli_worker) {
+ num_worker_cpus = CPU_COUNT(cli_worker);
+ if (num_worker_cpus) {
+ CPU_AND(cli_worker, cli_worker, &primary_cpus);
+ CPU_CLR(0, cli_worker);
+ }
+ } else {
+ CPU_ZERO(&cli_worker_mask);
+ }
+
+ /* Get the number of control CPUs - specified or derived */
+ if (!cli_control) {
+ /*
+ * control cpuset is unspecified... create it
+ * Allocate available CPUs to either the control set or the
+ * worker set according to the defined housekeeping CPU ratio
+ */
+ num_control_cpus = (numcpus * HOUSEKEEPING_RATIO_MULTIPLIER) /
+ HOUSEKEEPING_RATIO_DIVISOR;
+ /* A minimum of one control CPU is required */
+ if (num_control_cpus < 1)
+ num_control_cpus++;
+
+ /*
+ * Initialize storage for the control cpuset mask,
+ * ensuring that at least CPU 0 is in this mask.
+ */
+ cli_control = &cli_control_mask;
+ CPU_ZERO(cli_control);
+ for (cpu = 0; num_control_cpus > 0; cpu++) {
+ if (CPU_ISSET(cpu, &primary_cpus) &&
+ (!cpu || !CPU_ISSET(cpu, &cli_worker_mask))) {
+ CPU_SET(cpu, cli_control);
+ num_control_cpus--;
+ /*
+ * If present, the thread sibling(s) of this
+ * physical CPU may also be used to run
+ * control tasks.
+ */
+ num_siblings =
+ CPU_COUNT(&hyperthread_cpus[cpu]);
+ if (num_siblings) {
+ CPU_OR(cli_control,
+ &hyperthread_cpus[cpu],
+ cli_control);
+ num_control_cpus -= num_siblings;
+ }
+ }
+ }
+ } else {
+ /*
+ * control cpuset was specified... clean it up as necessary
+ * and ensure at least CPU 0 is in this mask
+ */
+ num_control_cpus = CPU_COUNT(cli_control);
+ if (!CPU_ISSET(0, cli_control)) {
+ CPU_SET(0, cli_control);
+ num_control_cpus++;
+ }
+
+ for (cpu = 0; cpu < numcpus; cpu++) {
+ /* Eliminate any specified data plane CPUs */
+ if (CPU_ISSET(cpu, &cli_worker_mask)) {
+ CPU_CLR(cpu, cli_control);
+ num_control_cpus--;
+ }
+ if (CPU_ISSET(cpu, cli_control)) {
+ /*
+ * If present, the thread sibling(s) of this
+ * physical CPU may also be used to run
+ * control tasks.
+ */
+ num_siblings =
+ CPU_COUNT(&hyperthread_cpus[cpu]);
+ if (num_siblings) {
+ CPU_OR(cli_control,
+ &hyperthread_cpus[cpu],
+ cli_control);
+ }
+ }
+ }
+ }
+
+ /*
+ * If specified, the worker cpuset mask has been pruned to
+ * include only 'primary' CPUs for best isolation performance.
+ * The control cpuset mask has been created and / or optimized.
+ * If the worker cpuset mask was not specified, create it now
+ * using all primary CPUs not previously reserved by the control set.
+ */
+ if (!cli_worker) {
+ cli_worker = &cli_worker_mask;
+ for (cpu = 1; cpu < numcpus; cpu++)
+ if (CPU_ISSET(cpu, &primary_cpus) &&
+ !CPU_ISSET(cpu, cli_control)) {
+ CPU_SET(cpu, cli_worker);
+ }
+ }
+#endif /* ISOLATION_ENABLED */
+init_cpumask_parms:
+
+ if (cli_control) {
+ /*
+ * Copy the 'normalized' control cpuset mask into the
+ * ODP initialization control cpumask.
+ */
+ for (cpu = 0; cpu < numcpus; cpu++)
+ if (CPU_ISSET(cpu, cli_control))
+ odp_cpumask_set(&init_control_cpus, cpu);
+ } else {
+ /*
+ * Do not use the specified control cpumask
+ */
+ my_parms->control_cpus = (odp_cpumask_t *)NULL;
+ }
+ if (cli_worker) {
+ /*
+ * Copy the 'normalized' worker cpuset mask into the
+ * ODP initialization worker cpumask.
+ */
+ for (cpu = 0; cpu < numcpus; cpu++)
+ if (CPU_ISSET(cpu, cli_worker))
+ odp_cpumask_set(&init_worker_cpus, cpu);
+ } else {
+ /*
+ * Do not use the specified worker cpumask
+ */
+ my_parms->worker_cpus = (odp_cpumask_t *)NULL;
+ }
+}
+
+/**
+ * Parse ODP command line options relevant to isolation setup
+ *
+ * Parses command line options and captures any CPU masks from the CLI.
+ * Initializes a flag indicating if isolation is requested.
+ *
+ * Allows cpumasks to be specified either as lists or as hexadecimal numbers.
+ * CPU mask lists are comprised of comma-separated elements which may include:
+ * individual CPU numbers,
+ * contiguous ranges of CPU numbers expressed as: starting CPU,
+ * a dash separator,
+ * and final cpu,
+ * or combinations of both of the above.
+ * numeric bit masks are hexadecimal numbers compatible with ODP specifications
+ *
+ * Also allows a means of indicating that isolation is desired with or
+ * without specifying preselected cpumasks
+ * If the isolation-requested argument is absent no isolation setup occurs.
+ *
+ * This function was designed to be called from the 'payload' application's
+ * main() function without requiring further modification to that function's
+ * command line argument parsing. As such it must be called prior to any
+ * previously existing argument parsing logic so it may 'intercept' the
+ * command line arguments and remove any arguments belonging to this function.
+ * This allows the isolation setup arguments to be added to the command line
+ * and processed transparently to the existing command line specifications.
+ *
+ * All arguments recognized as belonging exclusively to this function are
+ * removed from the command line after processing them. If any errors are
+ * encountered during processing of a 'recognized' argument an error result
+ * is returned and the argument is left on the command line.
+ * Any unrecognized arguments are left on the command line.
+ * The above behavior is intended to avoid accidentally deleting an argument
+ * which was intended for the 'payload' command line, and to pass the
+ * responsibility for error handling to that 'payload' command line parsing
+ * logic.
+ * Standard "help" arguments are parsed locally to display help for this
+ * function's command line - but are left intact on the command line so
+ * the 'payload' command line parsing logic may respond as well.
+ *
+ * @param argc Count of space-separated command line argumets to process
+ * @param argv Array of pointers to individual command line arguments
+ *
+ * @return On success: 0
+ * On failure: -1
+ */
+int parse_isolation_options(int *argc, char **argv)
+{
+ /* We must inherit the command line arguments from main() */
+ int error = 0;
+ int retval = 0;
+ int arg_ix = 0;
+ int nargs = *argc;
+ int help_requested = 0;
+ int n, n2;
+
+ /* Default to non-isolated operation if no options specified */
+ isolation_requested = 0;
+ cli_control = (cpu_set_t *)NULL;
+ cli_worker = (cpu_set_t *)NULL;
+ if (nargs > 1) {
+ /*
+ * In order to make this function transparent to the
+ * 'payload' application's main() function we have to
+ * call this function prior to processing of any command line
+ * arguments expected by main() and remove our arguments
+ * as we find them in order to 'sanitize' the command line
+ * to contain only the arguments expected by the 'payload'
+ * main() function's argument parding logic. Therefore
+ * we must process all arguments on the command line
+ * even if we encounter errors in our arguments - and must
+ * simply ignore any arguments we don't recognize.
+ */
+ while (++arg_ix < nargs) {
+ /*
+ * Please excuse the non-standard indentation below.
+ * This sequence of 'if-else' statements substitutes
+ * for a 'switch' statement which would be awkward
+ * to construct from string matching results performed
+ * on the option argument strings. The nesting
+ * gives better runtime efficiency. Properly nested
+ * indentations would quickly hit page width -
+ * so I formatted it more like a 'switch' statement.
+ */
+ if ((!strcmp(argv[arg_ix], "-cl")) ||
+ (!strcmp(argv[arg_ix], "--clist")) ||
+ (!strcmp(argv[arg_ix], "-CL")) ||
+ (!strcmp(argv[arg_ix], "--CLIST"))) {
+ /* Consume this argument */
+ for (n = 0; (arg_ix + n + 1) < nargs; n++) {
+ n2 = n + 1;
+ argv[arg_ix + n] = argv[arg_ix + n2];
+ }
+ nargs--;
+ argv[nargs] = (char *)NULL;
+ cli_control = &cli_control_mask;
+ retval = cpumask_from_list(cli_control,
+ argv[arg_ix]);
+ if (!retval) {
+ /* Consume this argument */
+ for (n = 0; (arg_ix + n + 1) < nargs;
+ n++) {
+ n2 = n + 1;
+ argv[arg_ix + n] =
+ argv[arg_ix + n2];
+ }
+ nargs--;
+ argv[nargs] = (char *)NULL;
+ } else {
+ error = -1;
+ }
+ /* Compensate for the removal of argument(s) */
+ arg_ix--;
+ } else { /* !clist_specified */
+ if ((!strcmp(argv[arg_ix], "-wl")) ||
+ (!strcmp(argv[arg_ix], "--wlist")) ||
+ (!strcmp(argv[arg_ix], "-WL")) ||
+ (!strcmp(argv[arg_ix], "--WLIST"))) {
+ /* Consume this argument */
+ for (n = 0; (arg_ix + n + 1) < nargs; n++) {
+ n2 = n + 1;
+ argv[arg_ix + n] = argv[arg_ix + n2];
+ }
+ nargs--;
+ argv[nargs] = (char *)NULL;
+ cli_worker = &cli_worker_mask;
+ retval = cpumask_from_list(cli_worker,
+ argv[arg_ix]);
+ if (!retval) {
+ /* Consume this argument */
+ for (n = 0; (arg_ix + n + 1) < nargs;
+ n++) {
+ n2 = n + 1;
+ argv[arg_ix + n] =
+ argv[arg_ix + n2];
+ }
+ nargs--;
+ argv[nargs] = (char *)NULL;
+ arg_ix--;
+ } else {
+ error = -1;
+ }
+ } else { /* !wlist_specified */
+ if ((!strcmp(argv[arg_ix], "-cm")) ||
+ (!strcmp(argv[arg_ix], "--cmask")) ||
+ (!strcmp(argv[arg_ix], "-CM")) ||
+ (!strcmp(argv[arg_ix], "--CMASK"))) {
+ /* Consume this argument */
+ for (n = 0; (arg_ix + n + 1) < nargs; n++) {
+ n2 = n + 1;
+ argv[arg_ix + n] = argv[arg_ix + n2];
+ }
+ nargs--;
+ argv[nargs] = (char *)NULL;
+ cli_control = &cli_control_mask;
+ retval = cpumask_from_number(cli_control,
+ argv[arg_ix]);
+ if (!retval) {
+ /* Consume this argument */
+ for (n = 0; (arg_ix + n + 1) < nargs;
+ n++) {
+ n2 = n + 1;
+ argv[arg_ix + n] =
+ argv[arg_ix + n2];
+ }
+ nargs--;
+ argv[nargs] = (char *)NULL;
+ arg_ix--;
+ } else {
+ error = -1;
+ }
+ } else { /* !cmask_specified */
+ if ((!strcmp(argv[arg_ix], "-wm")) ||
+ (!strcmp(argv[arg_ix], "--wmask")) ||
+ (!strcmp(argv[arg_ix], "-WM")) ||
+ (!strcmp(argv[arg_ix], "--WMASK"))) {
+ /* Consume this argument */
+ for (n = 0; (arg_ix + n + 1) < nargs; n++) {
+ n2 = n + 1;
+ argv[arg_ix + n] = argv[arg_ix + n2];
+ }
+ nargs--;
+ argv[nargs] = (char *)NULL;
+ cli_worker = &cli_worker_mask;
+ retval = cpumask_from_number(cli_worker,
+ argv[arg_ix]);
+ if (!retval) {
+ /* Consume this argument */
+ argv[arg_ix] = (char *)NULL;
+ for (n = 0; (arg_ix + n + 1) < nargs;
+ n++) {
+ n2 = n + 1;
+ argv[arg_ix + n] =
+ argv[arg_ix + n2];
+ }
+ nargs--;
+ argv[nargs] = (char *)NULL;
+ arg_ix--;
+ } else {
+ error = -1;
+ }
+ } else { /* !wmask_specified */
+#ifdef ISOLATION_ENABLED
+ if ((!strcmp(argv[arg_ix], "-iz")) ||
+ (!strcmp(argv[arg_ix], "--isolate")) ||
+ (!strcmp(argv[arg_ix], "-IZ")) ||
+ (!strcmp(argv[arg_ix], "--ISOLATE"))) {
+ /* Consume this argument */
+ for (n = 0; (arg_ix + n + 1) < nargs; n++) {
+ n2 = n + 1;
+ argv[arg_ix + n] = argv[arg_ix + n2];
+ }
+ nargs--;
+ argv[nargs] = (char *)NULL;
+ isolation_requested++;
+ /* Compensate for the removal of argument(s) */
+ arg_ix--;
+ } else { /* !isolation_requested */
+#endif /* ISOLATION_ENABLED */
+ /* Check for help requested */
+ if ((!strcmp(argv[arg_ix], "-?")) ||
+ (!strcmp(argv[arg_ix], "-h")) ||
+ (!strcmp(argv[arg_ix], "--help")) ||
+ (!strcmp(argv[arg_ix], "-H")) ||
+ (!strcmp(argv[arg_ix], "--HELP"))) {
+ help_requested++;
+ } /* help requested */
+#ifdef ISOLATION_ENABLED
+ } /* isolation_requested */
+#endif /* ISOLATION_ENABLED */
+ } /* !wmask_specified */
+ } /* !cmask_specified */
+ } /* !wlist_specified */
+ } /* !clist_specified */
+ }
+ }
+ if (!isolation_requested)
+ /* No command line arguments specified - default behavior */
+ printf("\r\nDefaulting to non-isolated operation\n");
+
+ /*
+ * If a problem was found with the arguments then clear the cpumasks,
+ * issue the usage prompt and return a nonzero result.
+ */
+ if (error || help_requested) {
+ cli_control = (cpu_set_t *)NULL;
+ cli_worker = (cpu_set_t *)NULL;
+
+ printf("\r\nUsage: %s [options]\n", argv[0]);
+#ifdef ISOLATION_ENABLED
+ printf("\r -iz, --isolate request isolated operation\n");
+#endif /* ISOLATION_ENABLED */
+ printf("\r -cm, --cmask <mask-digits> specify control cpumask as number\n");
+ printf("\r -wm, --wmask <mask-digits> specify worker cpumask as number\n");
+ printf("\r -cl, --clist <cpulist> specify control cpumask as list\n");
+ printf("\r -wl, --wlist <cpulist> specify worker cpumask as list\n");
+ printf(" where <mask-digits> is an ODP hexadecimal bitmask string and\n");
+ printf(" <cpulist> is a comma-separated list of cpus and/or dash-separated cpu ranges\n");
+ printf(" All arguments are optional and may be in short or long form\n");
+#ifdef ISOLATION_ENABLED
+ printf(" Isolation is disabled unless explicitly requested\n");
+#endif /* ISOLATION_ENABLED */
+ printf(" One or both CPU masks may be specified\n");
+ printf(" CPU masks may be specified as bitmasks or lists\n");
+ printf(" or automatically generated if unspecified\n");
+#ifdef ISOLATION_ENABLED
+ printf(" Default with no arguments is non-isolated operation\n");
+#endif /* ISOLATION_ENABLED */
+ error = -1;
+ }
+ *argc = nargs;
+ return error;
+}
+
+#ifndef ISOLATION_ENABLED
+/**
+ * Initialize an ODP instance prior to beginning further operations
+ *
+ * Sets up CPU masks for this process as needed,
+ * including incorporation of any CPU masks specified
+ * on the command line - then performs ODP global initialization
+ *
+ * This is the 'non-isolated' version of this 'wrapper' function,
+ * and is included here to enable use of CPU masks
+ * specified on the command line without requiring the use of
+ * any isolation support helpers.
+ *
+ * @return On success: 0
+ * On failure: -1
+ */
+int odph_init_global(void)
+{
+ int rc = 0;
+ static odp_init_t my_parms;
+
+ my_parms.num_worker = 0;
+ my_parms.num_control = 0;
+ my_parms.control_cpus = &init_control_cpus;
+ my_parms.worker_cpus = &init_worker_cpus;
+ my_parms.log_fn = (odp_log_func_t)NULL;
+ my_parms.abort_fn = (odp_abort_func_t)NULL;
+
+ /*
+ * If cpumasks were specified on the command line, use that input.
+ * Fill in any missing cpu availability information using data
+ * supplied by the underlying operating system.
+ * Filter the results with platform-specific sanity checks and
+ * populate the initialization cpumasks with the results prior to
+ * calling odp_init_global. This assures optimal detection and
+ * selection of available CPU resources independently of the
+ * initial CPU affinity of the ODP application / instance.
+ */
+ get_available_cpus(&my_parms);
+
+ if (odp_init_global(&my_parms, NULL) != 0)
+ ODPH_ABORT("Failed global init.\n");
+
+ rc = (int)odp_cpumask_to_str(&odp_global_data.control_cpus,
+ pathname_buf, 257);
+ printf("\r\nglobal control_cpus bitmask: %s\n", pathname_buf);
+
+ rc = (int)odp_cpumask_to_str(&odp_global_data.worker_cpus,
+ pathname_buf, 257);
+ printf("\r\nglobal worker_cpus bitmask: %s\n", pathname_buf);
+
+ if (rc)
+ rc = 0;
+ return rc;
+}
+
+/**
+ * Terminate an ODP instance
+ *
+ * Wrapper function for terminating and cleaning up when this ODP application
+ * finishes execution. This is the 'non-isolated' version which is basically
+ * just a placeholder.
+ *
+ * @return On success: 0
+ * On failure: -1
+ */
+int odph_term_global(void)
+{
+ return 0;
+}
+
+#endif /* ISOLATION_ENABLED */
@@ -27,6 +27,8 @@ extern "C" {
#include <pthread.h>
#include <sys/types.h>
+#include <odp/helper/linux_isolation.h>
+
/** The thread starting arguments */
typedef struct {
void *(*start_routine) (void *); /**< The function to run */
new file mode 100644
@@ -0,0 +1,191 @@
+/* Copyright (c) 2013, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/**
+ * @file
+ *
+ * ODP Linux isolation helper API
+ *
+ * This file is an optional helper to odp.h APIs. These functions are provided
+ * to ease common setups for isolation using cpusets in a Linux system.
+ * User is free to implement the same setups in other ways (not via this API).
+ */
+
+#ifndef ODP_LINUX_ISOLATION_H_
+#define ODP_LINUX_ISOLATION_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <odp_api.h>
+
+/**
+ * Flag indicating if command line options specified support
+ * for CPU isolation in this ODP instance / application.
+ * Used as a run-time switch to enable or disable isolation setup
+ */
+extern int isolation_requested;
+
+/**
+ * Number of logical CPUs installed on the system
+ * (including any 'thread' siblings')
+ */
+extern int numcpus;
+
+/**
+ * Shared path construction buffers
+ * Used to construct absolute paths to directories and files in the
+ * cpuset file hierarchy. Shared in order to reduce stack usage.
+ * RESERVED FOR INTERNAL USE - DO NOT ACCESS DIRECTLY FROM APPLICATIONS
+ */
+extern char pathname_buf[257];
+extern char cpuname[5];
+
+/**
+ * Pointers to cpumasks passed in from command line arguments
+ * NULL if associated cpumask not specified on command line or
+ * derived automatically due to isolation setup requested on command line.
+ * Used to determine contents of ODP initialization cpumasks prior to
+ * calling odp_init_global().
+ * RESERVED FOR INTERNAL USE - DO NOT ACCESS DIRECTLY FROM APPLICATIONS
+ */
+extern cpu_set_t *cli_control;
+extern cpu_set_t *cli_worker;
+
+/**
+ * ODP initialization cpumasks
+ *
+ * These cpumasks are globally accessible and are used to enable external
+ * entities to specify the CPU resources available to a given ODP instance
+ * or application. In our case they are initialized from the application
+ * command line if present and left unpopulated otherwise.
+ * If isolation was requested but one or both of these masks were left
+ * unspecified then the isolation setup will populate the mask(s)
+ * automatically.
+ * RESERVED FOR INTERNAL USE - DO NOT ACCESS DIRECTLY FROM APPLICATIONS
+ */
+extern odp_cpumask_t init_control_cpus;
+extern odp_cpumask_t init_worker_cpus;
+
+/**
+ * Array of cpumasks showing thread siblings for each primary CPU
+ * RESERVED FOR INTERNAL USE - DO NOT ACCESS DIRECTLY FROM APPLICATIONS
+ */
+extern cpu_set_t hyperthread_cpus[];
+
+#ifdef ISOLATION_ENABLED
+/**
+ * Clean up isolation support and abort
+ *
+ * Cleans up isolation environment for this ODP instance and aborts
+ *
+ * @warning this function shall not return
+ */
+void odph_abort(void);
+#endif
+
+/**
+ * Determine what CPU resources are available to this ODP instance
+ *
+ * First the CPU topology of the underlying system is discovered and installed
+ * logical CPUs are both counted and identified as either 'primary' or
+ * 'thread sibling' CPUs.
+ *
+ * If one or more cpumasks were specified via command line arguments,
+ * those arguments are used as suggestions and limiting constraints as to
+ * what CPU resources may be used for control tasks, worker tasks, or both.
+ *
+ * If isolated operation is either not enabled or not requested on the
+ * command line, then any cpumasks specified are passed directly as
+ * initialization cpumasks to odp_init_global().
+ *
+ * If one or both cpumasks were not specified on the command line,
+ * but isolated operation is enabled and was requested on the command line,
+ * the missing cpu availability data are obtained automatically as needed.
+ * The cpumasks derived from command line input or automatic algorithms
+ * are then passed as initialization cpumasks to odp_init_global().
+ *
+ * If either control or worker cpumask was neither specified nor derived,
+ * the corresponding initialization cpumask pointer is set to NULL so that
+ * odp_init_global() will select the default configuration for that cpumask.
+ * This ensures a 'default' command line will result in a 'default' setup
+ * for control and worker cpumasks.
+ *
+ * @param my_parms Pointer to ODP initialization data structure to be
+ * passed to odp_init_global()
+ */
+void get_available_cpus(odp_init_t *my_parms);
+
+/**
+ * Parse ODP command line options relevant to isolation setup
+ *
+ * Parses command line options and capture any CPU masks from the CLI.
+ * Initializes a flag indicating if isolation is requested.
+ *
+ * Allows cpumasks to be specified either as lists or as hexadecimal numbers.
+ * CPU mask lists are comprised of comma-separated elements which may include:
+ * individual CPU numbers,
+ * contiguous ranges of CPU numbers expressed as: starting CPU,
+ * a dash separator,
+ * and final cpu,
+ * or combinations of both of the above.
+ * numeric bit masks are hexadecimal numbers compatible with ODP specifications
+ * Alternatively, allows a means of indicating that isolation is desired
+ * without specifying preselected cpumasks - in which case the appropriate
+ * cpumasks will be determined by our application's initialization logic.
+ * If no preselected cpumasks are specified and the isolation-requested
+ * argument is also absent then no isolation setup will be attempted.
+ *
+ * This function was designed to be called from the 'payload' application's
+ * main() function without requiring further modification to that function's
+ * command line argument parsing. As such it must be called prior to any
+ * previously existing argument parsing logic so it may 'intercept' the
+ * command line arguments and remove any arguments belonging to this function.
+ * This allows the isolation setup arguments to be added to the command line
+ * and processed transparently to the existing command line specifications.
+ *
+ * @param argc Count of space-separated command line argumets to process
+ * @param argv Array of pointers to individual command line arguments
+ *
+ * @return On success: 0
+ * On failure: -1
+ */
+int parse_isolation_options(int *argc, char **argv);
+
+/**
+ * Initialize an isolated ODP instance prior to beginning further operations
+ *
+ * Performs ODP global initialization and then initializes isolation setup
+ * as needed.
+ * If isolation requested then
+ * Verifies the level of underlying operating system support.
+ * (Returns with error if the OS does not at least support cpusets)
+ * Sets up system-wide CPU masks and cpusets as needed
+ * Sets up CPU masks and cpusets for this process as needed
+ *
+ * @return On success: 0
+ * On failure: -1
+ */
+int odph_init_global(void);
+
+/**
+ * Terminate an isolated ODP instance
+ *
+ * Migrates all tasks from cpusets created for isolation support of this
+ * process to the generic boot-level single cpuset.
+ * Removes all isolated CPU environments and cpusets created for this process
+ *
+ * @return On success: 0
+ * On failure: -1
+ */
+int odph_term_global(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Adds helper code to support specification on the command line of an ODP instance or application process of control and/or worker cpumasks - as well as enabling or disabling isolation support for the process at runtime. This command line argument parsing code is designed to be inserted into the application source ahead of any local command line parsing logic, and cleans any arguments it recognizes from the command line prior to returning to the application, so the command line presented to the application does not receive unexpected argumemts. Unrecognized arguments are left intact without generating errors, and arguments which appear recognized but incur parsing errors are also left intact in case they belong to the client application - but in this case an error flag is also returned. All recognized options may be specified either in long or short format. cpumasks may be specified as hex numbers or as lists and the formats of both are consistent with ODP specifications and current practice. If specified, cpumasks override any default cpumask settings. A wrapper for odp_init_global() is included for processing CLI cpumasks in a 'non-isolated' environment. Conditional compilation is used to select non-isolated code or its isolated counterparts - most of which will be furnished in helper code not yet submitted. The feature selection .configure switch which activates the 'isolation-enabled' code will be included with the isolation helper patch series later. Some 'isolation-enabled' code fragments are included in this patch series in order to break the introduction of the isolation helper code into smaller, more easily reviewed segments. The fragments included deal with default cpumask setup for isolated environments. Isolation support must be both configured in at build time and explicitly requested at run-time if desired. If isolation support is requested but one or both cpumasks are omitted, default cpumasks appropriate for isolated use are created based on discoverable CPU topology. If isolation support is either not configured or requested, then the default cpumasks generated by the bug 2027 fix are used in lieu of any omitted CLI cpumasks. Signed-off-by: Gary S. Robertson <gary.robertson@linaro.org> --- helper/Makefile.am | 2 + helper/cpumask_cli.c | 954 ++++++++++++++++++++++++++++ helper/include/odp/helper/linux.h | 2 + helper/include/odp/helper/linux_isolation.h | 191 ++++++ 4 files changed, 1149 insertions(+) create mode 100644 helper/cpumask_cli.c create mode 100644 helper/include/odp/helper/linux_isolation.h