diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a790d0..aec66a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${C_CXX_FLAGS}") add_subdirectory(src) +if(STANDALONE) + add_subdirectory(cli) +endif() #Building tdi doxygen find_package(Doxygen) diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt new file mode 100644 index 0000000..69dd8e7 --- /dev/null +++ b/cli/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.12) +project(libtdi_cli VERSION 0.1 LANGUAGES C) + +find_package(Python3 COMPONENTS Interpreter Development) + +set(TDI_CLI_SRCS + tdi_cli.c +) + +set(TDI_CLI_EXAMPLES_SRCS + cli_example.c +) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third-party/target-syslibs/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third-party/target-utils/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third-party/target-utils/third-party/klish) + +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +add_library(tdi_cli_o OBJECT ${TDI_CLI_SRCS}) +target_include_directories(tdi_cli_o PRIVATE ${Python3_INCLUDE_DIRS}) +target_link_libraries(tdi_cli_o PUBLIC target_sys tdi clish python3.11) + +add_library(bfshell_plugin_tdi SHARED $) +target_link_libraries(bfshell_plugin_tdi PUBLIC target_sys tdi clish python3.11) + +add_executable(tdi_cli_example ${TDI_CLI_EXAMPLES_SRCS}) +target_link_libraries(tdi_cli_example bfshell_plugin_tdi) + +install(TARGETS tdi_cli_example DESTINATION bin) +install(FILES xml/startup.xml xml/tdi.xml DESTINATION share/cli/xml) diff --git a/cli/cli_example.c b/cli/cli_example.c new file mode 100644 index 0000000..25d6314 --- /dev/null +++ b/cli/cli_example.c @@ -0,0 +1,36 @@ +/* + * Copyright(c) 2024 Intel Corporation. + * + * 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 + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "tdi_cli.h" + +int main() { + char loc[] = "/root"; + char * p4_names[] = { + "Program 1", + "Program 2", + }; + + tdi_shell_start(loc, + p4_names); + sleep(200); +} + diff --git a/cli/tdi_cli.c b/cli/tdi_cli.c new file mode 100644 index 0000000..579170c --- /dev/null +++ b/cli/tdi_cli.c @@ -0,0 +1,320 @@ +/* + * Copyright(c) 2024 Intel Corporation. + * + * 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 + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define MAX_PATH_CFG 4096 + +/* + * Start TDI Runtime CLI. This program heavily borrows from the python C + * interface example program. + */ +PyObject *tdipModule = NULL; +static int tdi_start_cli(int in_fd, + int out_fd, + const char *install_dir, + const char *udf, + bool interactive) { + PyObject *pArgs = NULL, *pValue = NULL; + tdi_dev_id_t *dev_id_list = NULL; + uint32_t array_size = 0; + PyObject *pFunc = NULL; + char *user_cfg = NULL; + int ret_val = 0; + PyConfig config; + + PyConfig_InitPythonConfig(&config); + PyConfig_Read(&config); + Py_InitializeFromConfig(&config); + + // Setting tdi_python search path dynamically + // consider user_cfg as maximum path config(4096) + user_cfg = bf_sys_calloc(1, MAX_PATH_CFG); + if (user_cfg == NULL) { + fprintf(stderr, "Failed to allocate user cfg path variable \n"); + ret_val = 1; + goto cleanup; + } + snprintf(user_cfg, MAX_PATH_CFG - 1, "sys.path.append('%s/lib/python')\n", install_dir); + PyRun_SimpleString("import sys\n"); + PyRun_SimpleString(user_cfg); + + tdi_num_device_id_list_get(&array_size); + if (array_size) { + dev_id_list = bf_sys_malloc(array_size * sizeof(tdi_dev_id_t)); + if (!dev_id_list) { + fprintf(stderr, "Failed tp allocate device id list\n"); + ret_val = 1; + goto cleanup; + } + tdi_device_id_list_get(dev_id_list); + } + + // first run, initialize python interpreter + if (tdipModule == NULL) { + Py_Initialize(); + PyObject *pName; + /* Load the tdiRtcli python program. Py_Initialize loads its libraries from + the install dir we installed Python into. */ + pName = PyUnicode_DecodeFSDefault("tdiRtCli"); + /* Error checking of pName left out */ + tdipModule = PyImport_Import(pName); + Py_DECREF(pName); + if (tdipModule == NULL) { + printf("cannot import module in tdi\n"); + ret_val = 1; + goto cleanup; + } + } + + if (tdipModule != NULL) { + // Create a call to the start_rt_tdi function in tdiRtCli.py + pFunc = PyObject_GetAttrString(tdipModule, "start_tdi_rt"); + /* pFunc is a new reference */ + + if (pFunc && PyCallable_Check(pFunc)) { + // Use a tuple as our argument list + if (udf) { + pArgs = PyTuple_New(6); + } else { + pArgs = PyTuple_New(4); + } + + if (!pArgs) { + fprintf(stderr, "Failed to allocate pArgs\n"); + ret_val = 1; + goto cleanup; + } + // Create python objects from c types. + // Place references to them in the argument tuple. + pValue = PyLong_FromLong(in_fd); + PyTuple_SetItem(pArgs, 0, pValue); + pValue = PyLong_FromLong(out_fd); + PyTuple_SetItem(pArgs, 1, pValue); + /* + * Convert from the filepath c string to a python string using the + * filesystem's default encoding + */ + pValue = PyUnicode_DecodeFSDefault(install_dir); + PyTuple_SetItem(pArgs, 2, pValue); + PyObject *pyList = PyList_New(array_size); + if (!pyList) { + fprintf(stderr, "Failed to allocate python list\n"); + ret_val = 1; + goto cleanup; + } + for (uint32_t i = 0; i < array_size; i++) { + pValue = PyLong_FromLong(dev_id_list[i]); + PyList_SetItem(pyList, i, pValue); + } + PyTuple_SetItem(pArgs, 3, pyList); + if (udf) { + pValue = PyUnicode_DecodeFSDefault(udf); + PyTuple_SetItem(pArgs, 4, pValue); + pValue = PyBool_FromLong(interactive); + PyTuple_SetItem(pArgs, 5, pValue); + } + + // Actually make the function call. + // This is where we block until the CLI exits + pValue = PyObject_CallObject(pFunc, pArgs); + Py_DECREF(pArgs); + + // Handle exit codes and decrement references to free our python objects + if (pValue != NULL) { + long ret = PyLong_AsLong(pValue); + if (ret == 0) { + printf("tdi cli exited normally.\n"); + } else { + printf("tdi cli exited with error: %ld\n", ret); + } + Py_DECREF(pValue); + } else { + Py_DECREF(pFunc); + PyErr_Print(); + fprintf(stderr, "tdi cli python call failed\n"); + ret_val = 1; + goto cleanup; + } + } else { + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "Cannot find start_tdi_rt function.\n"); + } + Py_XDECREF(pFunc); + } else { + PyErr_Print(); + fprintf(stderr, "Failed to load tdicli python library\n"); + ret_val = 1; + goto cleanup; + } +cleanup: + // After execution of Py_Fynalize will be needed call + // Py_Initialize and PyImport_Import that leads to a memory leak + // because of previous imported lib will not be removed + PyGC_Collect(); + if (dev_id_list) { + bf_sys_free(dev_id_list); + } + if (user_cfg) + bf_sys_free(user_cfg); + return ret_val; +} + +// A klish plugin allowing TDI Runtime CLI to be started from tdishell. +CLISH_PLUGIN_SYM(tdi_cli_cmd) { + (void)script; + (void)out; + tdi_status_t sts; + clish_shell_t *tdishell = clish_context__get_shell(clish_context); + //bool success = TRY_PYTHON_SHL_LOCK(); + bool success = true; + if (!success) { + bfshell_printf(clish_context, + "Only one Python shell instance allowed at a time. tdi " + "python and debug python share the python shell " + "resource.\n"); + return 0; + } + + clish_pargv_t *pargv = clish_context__get_pargv(clish_context); + const clish_parg_t *parg = clish_pargv_find_arg(pargv, "py_file"); + const char *udf = NULL; + if (parg) { + udf = clish_parg__get_value(parg); + } + parg = clish_pargv_find_arg(pargv, "interactive"); + const char *i_str = NULL; + bool interactive = false; + if (parg) { + i_str = clish_parg__get_value(parg); + if (i_str && strcmp(i_str, "1") == 0) { + interactive = true; + } + } + + tinyrl_t *bftinyrl = clish_shell__get_tinyrl(tdishell); + sts = tdi_start_cli(fileno(tinyrl__get_istream(bftinyrl)), + fileno(tinyrl__get_ostream(bftinyrl)), + clish_context__get_install_dir(clish_context), + udf, + interactive); + if (sts != TDI_SUCCESS) { + bfshell_printf(clish_context, + "%s:%d could not initialize tdi for the cli. err: %d\n", + __func__, + __LINE__, + sts); + } +// RELEASE_PYTHON_SHL_LOCK(); + return 0; +} + +CLISH_PLUGIN_SYM(tdi_run_file_cmd) { + (void)script; + (void)out; + + clish_shell_t *this = clish_context__get_shell(clish_context); + clish_pargv_t *pargv = clish_context__get_pargv(clish_context); + + const char *filename = NULL; + const clish_parg_t *parg = clish_pargv_find_arg(pargv, "filename"); + if (parg) { + filename = clish_parg__get_value(parg); + } + + int stop_on_error = 1; + parg = clish_pargv_find_arg(pargv, "stop_on_error"); + if (parg) { + const char *i_str = clish_parg__get_value(parg); + if (i_str && strcmp(i_str, "0") == 0) { + stop_on_error = 0; + } + } + + int result = -1; + struct stat fileStat; + + /* + * Check file specified is not a directory + */ + if (filename && (0 == stat((char *)filename, &fileStat)) && + (!S_ISDIR(fileStat.st_mode))) { + /* + * push this file onto the file stack associated with this + * session. This will be closed by clish_shell_pop_file() + * when it is finished with. + */ + result = clish_shell_push_file(this, filename, stop_on_error); + } + + return result ? -1 : 0; +} + +CLISH_PLUGIN_INIT(tdi) { + (void)clish_shell; + clish_plugin_add_sym(plugin, tdi_cli_cmd, "tdi_cli_cmd"); + clish_plugin_add_sym(plugin, tdi_run_file_cmd, "tdi_run_file_cmd"); + return 0; +} + +/** + * User exposed function to start the CLI shell + */ +int tdi_shell_start(char *install_dir, + char **p4_names) { + + /* Note that the CLI thread is responsible for free-ing this data */ + char *install_dir_path = calloc(1024, sizeof(char)); + + /* Start the CLI server thread */ + snprintf(install_dir_path, 1023, "%s/", install_dir); + printf("ipu_p4d: spawning cli server thread. Install path: %s\n", install_dir_path); + cli_thread_main(install_dir_path, p4_names, true); + cli_run_bfshell(); + + printf("ipu_p4d: server started - listening on port 9999\n"); + + return 0; +} + diff --git a/cli/tdi_cli.h b/cli/tdi_cli.h new file mode 100644 index 0000000..33de60c --- /dev/null +++ b/cli/tdi_cli.h @@ -0,0 +1,26 @@ +/* + * Copyright(c) 2024 Intel Corporation. + * + * 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 + * limitations under the License. + */ + +#ifndef _TDI_CLI_H_ +#define _TDI_CLI_H_ + + +/** + * User exposed function to start the CLI shell + */ +int tdi_shell_start(char *install_dir, char **p4_names); + +#endif diff --git a/cli/xml/startup.xml b/cli/xml/startup.xml new file mode 100644 index 0000000..9f8c6b2 --- /dev/null +++ b/cli/xml/startup.xml @@ -0,0 +1,82 @@ + + + + +CONTEXT SENSITIVE HELP +[?] - Display context sensitive help. This is either a list of possible + command completions with summaries, or the full syntax of the + current command. A subsequent repeat of this key, when a command + has been resolved, will display a detailed reference. + +AUTO-COMPLETION +The following keys both perform auto-completion for the current command line. +If the command prefix is not unique then the bell will ring and a subsequent +repeat of the key will display possible completions. + +[enter] - Auto-completes, syntax-checks then executes a command. If there is + a syntax error then offending part of the command line will be + highlighted and explained. + +[space] - Auto-completes, or if the command is already resolved inserts a space. + +MOVEMENT KEY +[CTRL-A] - Move to the start of the line. +[CTRL-E] - Move to the end of the line. +[up] - Move to the previous command line held in history. +[down] - Move to the next command line held in history. +[left] - Move the insertion point left one character. +[right] - Move the insertion point right one character. + +DELETION KEYS +[CTRL-C] - Delete and abort the current line. +[CTRL-D] - Delete the character to the right on the insertion point. +[CTRL-K] - Delete all the characters to the right of the insertion point. +[CTRL-U] - Delete the whole line. +[backspace] - Delete the character to the left of the insertion point. + +ESCAPE SEQUENCES +!! - Subsitute the the last command line. +!N - Substitute the Nth command line (absolute as per 'history' command) +!-N - Substitute the command line entered N lines before (relative) + + + + + + + + + + + ******************************************** + * WARNING: Authorised Access Only * + ******************************************** + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cli/xml/tdi.xml b/cli/xml/tdi.xml new file mode 100644 index 0000000..31ccb2c --- /dev/null +++ b/cli/xml/tdi.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +