diff --git a/examples/shv-nxboot-updater/Kconfig b/examples/shv-nxboot-updater/Kconfig new file mode 100644 index 00000000000..384a83e879c --- /dev/null +++ b/examples/shv-nxboot-updater/Kconfig @@ -0,0 +1,30 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +menuconfig EXAMPLES_SHV_NXBOOT_UPDATER + bool "Silicon Heaven Firmware updates for NXBoot" + depends on NETUTILS_LIBSHVC + default n + ---help--- + Enable the shv-nxboot-updater application. + +if EXAMPLES_SHV_NXBOOT_UPDATER + +config EXAMPLES_SHV_NXBOOT_UPDATER_PROGNAME + string "shv-nxboot-updater App Name" + default "shv-nxboot-updater" + ---help--- + This is the name of the program that will be used when the NSH ELF + program is installed. + +config EXAMPLES_SHV_NXBOOT_UPDATER_PRIORITY + int "shv-nxboot-updater task priority" + default 100 + +config EXAMPLES_SHV_NXBOOT_UPDATER_STACKSIZE + int "shv-nxboot-updater task stack size" + default 4096 + +endif # EXAMPLES_NX_SHV_FWUPDATER diff --git a/examples/shv-nxboot-updater/Make.defs b/examples/shv-nxboot-updater/Make.defs new file mode 100644 index 00000000000..745b8622092 --- /dev/null +++ b/examples/shv-nxboot-updater/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/examples/shv-nxboot-updater/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ + +ifneq ($(CONFIG_EXAMPLES_SHV_NXBOOT_UPDATER),) +CONFIGURED_APPS += $(APPDIR)/examples/shv-nxboot-updater +endif diff --git a/examples/shv-nxboot-updater/Makefile b/examples/shv-nxboot-updater/Makefile new file mode 100644 index 00000000000..339199a27f3 --- /dev/null +++ b/examples/shv-nxboot-updater/Makefile @@ -0,0 +1,31 @@ +############################################################################ +# apps/examples/shv-nxboot-updater/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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 $(APPDIR)/Make.defs + +MODULE = $(CONFIG_EXAMPLES_SHV_NXBOOT_UPDATER) + +PROGNAME += $(CONFIG_EXAMPLES_SHV_NXBOOT_UPDATER_PROGNAME) +PRIORITY += $(CONFIG_EXAMPLES_SHV_NXBOOT_UPDATER_PRIORITY) +STACKSIZE += $(CONFIG_EXAMPLES_SHV_NXBOOT_UPDATER_STACKSIZE) + +MAINSRC += shv_nxboot_updater.c + +include $(APPDIR)/Application.mk diff --git a/examples/shv-nxboot-updater/shv_nxboot_updater.c b/examples/shv-nxboot-updater/shv_nxboot_updater.c new file mode 100644 index 00000000000..113679ef4bd --- /dev/null +++ b/examples/shv-nxboot-updater/shv_nxboot_updater.c @@ -0,0 +1,358 @@ +/**************************************************************************** + * apps/examples/shv-nxboot-updater/shv_nxboot_updater.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int shv_nxboot_opener(shv_file_node_t *item); +static int shv_fwstable_confirm(shv_con_ctx_t *shv_ctx, shv_node_t *item, + int rid); +static int shv_fwstable_get(shv_con_ctx_t *shv_ctx, shv_node_t *item, + int rid); +static void quit_handler(int signum); +static void print_help(char *name); + +static shv_node_t *shv_tree_create(void); +static void attention_cb(shv_con_ctx_t *shv_ctx, + enum shv_attention_reason r); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* An execution barrier */ + +static sem_t running; + +/* ------------------------- ROOT METHODS --------------------------------- */ + +static const shv_method_des_t * const shv_dev_root_dmap_items[] = +{ + &shv_dmap_item_dir, + &shv_dmap_item_ls, +}; + +static const shv_dmap_t shv_dev_root_dmap = + SHV_CREATE_NODE_DMAP(root, shv_dev_root_dmap_items); + +/* ------------------------- fwstable METHODS ---------------------------- */ + +static const shv_method_des_t shv_dev_fwstable_dmap_item_confirm = +{ + .name = "confirm", + .method = shv_fwstable_confirm +}; + +static const shv_method_des_t shv_dev_fwstable_dmap_item_get = +{ + .name = "get", + .method = shv_fwstable_get +}; + +static const shv_method_des_t * const shv_dev_fwstable_dmap_items[] = +{ + &shv_dev_fwstable_dmap_item_confirm, + &shv_dmap_item_dir, + &shv_dev_fwstable_dmap_item_get, + &shv_dmap_item_ls +}; + +static const shv_dmap_t shv_dev_fwstable_dmap = + SHV_CREATE_NODE_DMAP(dotdevice, shv_dev_fwstable_dmap_items); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int shv_fwstable_confirm(shv_con_ctx_t *shv_ctx, shv_node_t *item, + int rid) +{ + shv_unpack_data(&shv_ctx->unpack_ctx, 0, 0); + nxboot_confirm(); + shv_send_empty_response(shv_ctx, rid); + return 0; +} + +static int shv_fwstable_get(shv_con_ctx_t *shv_ctx, shv_node_t *item, + int rid) +{ + shv_unpack_data(&shv_ctx->unpack_ctx, 0, 0); + int ret = nxboot_get_confirm(); + if (ret >= 0) + { + shv_send_int(shv_ctx, rid, ret); + } + else + { + shv_send_error(shv_ctx, rid, SHV_RE_PLATFORM_ERROR, + "nxboot_get_confirm failed"); + } + + return 0; +} + +static int shv_nxboot_opener(shv_file_node_t *item) +{ + struct shv_file_node_fctx *fctx = (struct shv_file_node_fctx *)item->fctx; + if (!(fctx->flags & SHV_FILE_POSIX_BITFLAG_OPENED)) + { + fctx->fd = nxboot_open_update_partition(); + if (fctx->fd < 0) + { + return -1; + } + + fctx->flags |= SHV_FILE_POSIX_BITFLAG_OPENED; + } + + return 0; +} + +static shv_node_t *shv_tree_create(void) +{ + shv_node_t *tree_root, *fwstable_node; + shv_dotdevice_node_t *dotdevice_node; + shv_file_node_t *fwupdate_node; + shv_dotapp_node_t *dotapp_node; + + struct mtd_geometry_s geometry; + int flash_fd; + flash_fd = nxboot_open_update_partition(); + if (flash_fd < 0) + { + return NULL; + } + + puts("Creating the SHV Tree root"); + tree_root = shv_tree_node_new("", &shv_dev_root_dmap, 0); + if (tree_root == NULL) + { + close(flash_fd); + return NULL; + } + + fwupdate_node = shv_tree_file_node_new("fwUpdate", + &shv_file_node_dmap, 0); + if (fwupdate_node == NULL) + { + close(flash_fd); + free(tree_root); + return NULL; + } + + if (ioctl(flash_fd, MTDIOC_GEOMETRY, + (unsigned long)((uintptr_t)&geometry)) < 0) + { + close(flash_fd); + free(tree_root); + free(fwupdate_node); + return NULL; + } + + fwupdate_node->file_type = SHV_FILE_MTD; + fwupdate_node->file_maxsize = geometry.erasesize * geometry.neraseblocks; + fwupdate_node->file_pagesize = geometry.blocksize; + fwupdate_node->file_erasesize = geometry.erasesize; + + /* Update the fops table in the file node */ + + fwupdate_node->fops.opener = shv_nxboot_opener; + shv_tree_add_child(tree_root, &fwupdate_node->shv_node); + close(flash_fd); + + dotapp_node = shv_tree_dotapp_node_new(&shv_dotapp_dmap, 0); + if (dotapp_node == NULL) + { + free(tree_root); + free(fwupdate_node); + return NULL; + } + + dotapp_node->name = "NuttX shv-libs4c FW Updater"; + dotapp_node->version = "1.0.0"; + + shv_tree_add_child(tree_root, &dotapp_node->shv_node); + + dotdevice_node = shv_tree_dotdevice_node_new(&shv_dotdevice_dmap, 0); + if (dotdevice_node == NULL) + { + free(tree_root); + free(fwupdate_node); + free(dotapp_node); + return NULL; + } + + dotdevice_node->name = "SHV Compatible Device"; + dotdevice_node->serial_number = "0xDEADBEEF"; + dotdevice_node->version = "0.1.0"; + shv_tree_add_child(tree_root, &dotdevice_node->shv_node); + + fwstable_node = shv_tree_node_new("fwStable", &shv_dev_fwstable_dmap, 0); + if (fwstable_node == NULL) + { + free(tree_root); + free(fwupdate_node); + free(dotapp_node); + return NULL; + } + + shv_tree_add_child(tree_root, fwstable_node); + + return tree_root; +} + +static void quit_handler(int signum) +{ + puts("Stopping SHV FW Updater!"); + sem_post(&running); +} + +static void print_help(char *name) +{ + printf("%s: \n", name); + puts("SHV Firmware Updater for NXBoot"); + puts("The SHV tree is comprised of the following nodes:"); + puts(" - .app // A standard node, shows info about the app"); + puts(" - .device // A standard node, shows info about the device"); + puts(" - fwUpdate // A file node, abstraction of the NXBoot update " + "partition"); + puts(" - fwStable // A standard node, contains the confirm method to " + "confirm"); + puts(" the validity of the newly booted up image."); +} + +static void attention_cb(shv_con_ctx_t *shv_ctx, enum shv_attention_reason r) +{ + if (r == SHV_ATTENTION_ERROR) + { + printf("Error occurred in SHV, the reason is: %s\n", + shv_errno_str(shv_ctx)); + sem_post(&running); + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: main + ****************************************************************************/ + +int main(int argc, char *argv[]) +{ + /* Define the SHV Communication parameters */ + + int ret; + struct shv_connection connection; + shv_node_t *tree_root; + shv_con_ctx_t *ctx; + const int comthrd_prio = CONFIG_EXAMPLES_SHV_NXBOOT_UPDATER_PRIORITY - 1; + + /* Initialize the communication. But only if parameters are passed. */ + + if (argc != 6) + { + print_help(argv[0]); + return 1; + } + + const char *user = argv[1]; + const char *passwd = argv[2]; + const char *mount = argv[3]; + const char *ip = argv[4]; + const char *port_s = argv[5]; + int port = atoi(port_s); + + shv_connection_init(&connection, SHV_TLAYER_TCPIP); + connection.broker_user = user; + connection.broker_password = passwd; + connection.broker_mount = mount; + connection.reconnect_period = 10; + connection.reconnect_retries = 0; + if (shv_connection_tcpip_init(&connection, ip, port) < 0) + { + fprintf(stderr, "Have you supplied valid params to shv_connection?\n"); + return 1; + } + + puts("SHV Connection Init OK"); + + tree_root = shv_tree_create(); + if (tree_root == NULL) + { + fprintf(stderr, "Can't create the SHV tree."); + return 1; + } + + puts("SHV Tree created!"); + ctx = shv_com_init(tree_root, &connection, attention_cb); + if (ctx == NULL) + { + fprintf(stderr, "Can't establish the comm with the broker.\n"); + return 1; + } + + ret = shv_create_process_thread(comthrd_prio, ctx); + if (ret < 0) + { + fprintf(stderr, "%s\n", shv_errno_str(ctx)); + free(ctx); + return 1; + } + + sem_init(&running, 0, 0); + signal(SIGTERM, quit_handler); + + sem_wait(&running); + + puts("Close the communication"); + shv_com_destroy(ctx); + + return 0; +} diff --git a/examples/shv-nxboot-updater/update-script/.gitignore b/examples/shv-nxboot-updater/update-script/.gitignore new file mode 100644 index 00000000000..ed8ebf583f7 --- /dev/null +++ b/examples/shv-nxboot-updater/update-script/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/examples/shv-nxboot-updater/update-script/gui.py b/examples/shv-nxboot-updater/update-script/gui.py new file mode 100755 index 00000000000..0298c2c07cc --- /dev/null +++ b/examples/shv-nxboot-updater/update-script/gui.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 + +############################################################################ +# apps/examples/shv-nxboot-updater/update-script/gui.py +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ + +import argparse +import asyncio +import logging +import sys +from typing import Any + +from PyQt6.QtCore import Qt, QThread +from PyQt6.QtWidgets import ( + QApplication, + QComboBox, + QDialog, + QGridLayout, + QLabel, + QLineEdit, + QProgressBar, + QPushButton, +) +from shvconfirm import shv_confirm +from shvflasher import shv_flasher + +log_levels = ( + logging.DEBUG, + logging.INFO, + logging.WARNING, + logging.ERROR, + logging.CRITICAL, +) + +PROGRESS_STYLE = """ +QProgressBar{ + border: 2px solid grey; + border-radius: 5px; + text-align: center +} + +QProgressBar::chunk { + background-color: green; +} +""" + + +def parse_args() -> argparse.Namespace: + """Parse passed arguments and return result.""" + parser = argparse.ArgumentParser( + description="GUI application for NuttX firmware flash over SHV" + ) + parser.add_argument( + "-v", + action="count", + default=0, + help="Increase verbosity level of logging", + ) + parser.add_argument( + "-q", + action="count", + default=0, + help="Decrease verbosity level of logging", + ) + parser.add_argument( + "-i", + "--image", + dest="image", + type=str, + default="nuttx.nximg", + help="Image path", + ) + parser.add_argument( + "-m", + "--mount", + dest="target_mount", + type=str, + default="test/nuttxdevice", + help="Target mount location on the SHV broker", + ) + parser.add_argument( + "-s", + "--server", + dest="shv_server", + type=str, + default="tcp://xyz@127.0.0.1:3755?password=xyz", + help="SHV server/broker", + ) + return parser.parse_args() + + +async def flashThreadAsync( + url: str, img: str, path_to_root, progress_bar: QProgressBar +) -> None: + queue: asyncio.Queue = asyncio.Queue() + task = asyncio.create_task(shv_flasher(url, img, path_to_root, queue)) + + while True: + progressVal = await queue.get() + progress_bar.setValue(progressVal) + + if progressVal == 100: + break + + await task + + +class flashThread(QThread): + def __init__( + self, + parent: Any, + url: str, + img: str, + path_to_root: str, + progress_bar: QProgressBar, + ) -> None: + QThread.__init__(self, parent) + self.url = url + self.img = img + self.path_to_root = path_to_root + self.progress_bar = progress_bar + + def run(self) -> None: + asyncio.run( + flashThreadAsync(self.url, self.img, self.path_to_root, self.progress_bar) + ) + + +class confirmThread(QThread): + def __init__(self, parent: Any, url: str, path_to_root: str) -> None: + QThread.__init__(self, parent) + self.url = url + self.path_to_root = path_to_root + + def run(self) -> None: + asyncio.run(shv_confirm(self.url, self.path_to_root)) + + +class ShvFlasherGui(QDialog): + def __init__(self, image=None, target_mount=None, shv_server=None) -> None: + super().__init__() + self.setWindowModality(Qt.WindowModality.ApplicationModal) + self.setWindowTitle("NuttX Firmware Flasher over SHV") + + lbl_shv_url = QLabel("SHV RCP URL") + self.set_interface(shv_server) + + lbl_image = QLabel("Image path") + self.image = QLineEdit(image if image is not None else "", self) + + lbl_target_mount = QLabel("Device mount") + self.target_mount = QLineEdit( + target_mount if target_mount is not None else "", self + ) + + self.progress_bar = QProgressBar(self) + self.progress_bar.setValue(0) + self.progress_bar.setStyleSheet(PROGRESS_STYLE) + + pb_flash = QPushButton("FLASH") + pb_confirm = QPushButton("CONFIRM") + pb_exit = QPushButton("EXIT") + grid = QGridLayout() + + grid.addWidget(lbl_shv_url, 0, 0) + grid.addWidget(self.interface, 0, 1) + grid.addWidget(lbl_image, 1, 0) + grid.addWidget(self.image, 1, 1) + grid.addWidget(lbl_target_mount, 2, 0) + grid.addWidget(self.target_mount, 2, 1) + grid.addWidget(self.progress_bar, 3, 0, 1, 3) + grid.addWidget(pb_flash, 4, 0) + grid.addWidget(pb_confirm, 4, 1) + grid.addWidget(pb_exit, 4, 2) + + pb_flash.clicked.connect(self.do_flash) + pb_confirm.clicked.connect(self.do_confirm) + pb_exit.clicked.connect(self.reject) + + self.setLayout(grid) + + def set_interface(self, shv_server=None) -> None: + self.interface = QComboBox() + self.interface.setEditable(True) + # tcp://user@localhost:3755?password=pass + self.interface.addItems([shv_server if shv_server is not None else ""]) + + def do_flash(self) -> None: + rpc_url = str(self.interface.currentText()) + img_path = str(self.image.text()) + path_to_root = str(self.target_mount.text()) + workerFlash = flashThread( + self, rpc_url, img_path, path_to_root, self.progress_bar + ) + workerFlash.start() + + def do_confirm(self) -> None: + rpc_url = str(self.interface.currentText()) + path_to_root = str(self.target_mount.text()) + worker = confirmThread(self, rpc_url, path_to_root) + worker.start() + + +def main() -> None: + args = parse_args() + logging.basicConfig( + level=log_levels[sorted([1 - args.v + args.q, 0, len(log_levels) - 1])[1]], + format="[%(asctime)s] [%(levelname)s] - %(message)s", + ) + + _ = QApplication(sys.argv) + + dialog = ShvFlasherGui( + image=args.image, target_mount=args.target_mount, shv_server=args.shv_server + ) + dialog.exec() + + +if __name__ == "__main__": + main() diff --git a/examples/shv-nxboot-updater/update-script/requirements.txt b/examples/shv-nxboot-updater/update-script/requirements.txt new file mode 100644 index 00000000000..c3b0be328de --- /dev/null +++ b/examples/shv-nxboot-updater/update-script/requirements.txt @@ -0,0 +1,5 @@ +pyshv>=0.10.0 +PyQt6 +argparse +asyncio +logging diff --git a/examples/shv-nxboot-updater/update-script/shvconfirm.py b/examples/shv-nxboot-updater/update-script/shvconfirm.py new file mode 100644 index 00000000000..cc9bd06b74b --- /dev/null +++ b/examples/shv-nxboot-updater/update-script/shvconfirm.py @@ -0,0 +1,33 @@ +############################################################################ +# apps/examples/shv-nxboot-updater/update-script/shvconfirm.py +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ + +from shv.rpcapi.valueclient import SHVValueClient +from shv.rpcurl import RpcUrl + + +async def shv_confirm(connection: str, path_to_root: str) -> None: + url = RpcUrl.parse(connection) + client = await SHVValueClient.connect(url) + assert client is not None + + await client.call(f"{path_to_root}/fwStable", "confirm") + + print("FW confirmed.") + await client.disconnect() diff --git a/examples/shv-nxboot-updater/update-script/shvflasher.py b/examples/shv-nxboot-updater/update-script/shvflasher.py new file mode 100644 index 00000000000..ae4a5dc23e2 --- /dev/null +++ b/examples/shv-nxboot-updater/update-script/shvflasher.py @@ -0,0 +1,84 @@ +############################################################################ +# apps/examples/shv-nxboot-updater/update-script/shvflasher.py +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ + +import asyncio +import io +import zlib + +from shv import SHVBytes +from shv.rpcapi.valueclient import SHVValueClient +from shv.rpcurl import RpcUrl + + +async def shv_flasher( + connection: str, name: str, path_to_root: str, queue: asyncio.Queue | None +) -> None: + url = RpcUrl.parse(connection) + client = await SHVValueClient.connect(url) + assert client is not None + node_name = f"{path_to_root}/fwUpdate" + node_name_dotdevice = f"{path_to_root}/.device" + + res = await client.call(node_name, "stat") + + maxfilesize = res[1] + maxwrite = res[5] + print(f"Received maximum enabled write size {maxwrite}.") + print(f"Received maximum file size is {maxfilesize}") + print(f"Started uploading new firmware {name}... this may take some time.") + size = 0 + with open(name, mode="rb") as f: + # first, compute the CRC from the zlib library + # turns out, NuttX uses the same polynomial + + if queue: + f.seek(0, io.SEEK_END) + size = f.tell() + print(f"File's size is {size}") + f.seek(0, io.SEEK_SET) + transfers = size / maxwrite + + i = 0 + crc = 0 + while data := f.read(maxwrite): + print("Uploading chunk n.", i + 1) + crc = zlib.crc32(data, crc) + offset = i * maxwrite + res = await client.call(node_name, "write", [offset, SHVBytes(data)]) + i += 1 + if queue: + currProgress = (int)((i * 100) / transfers) + queue.put_nowait(currProgress) + + print("Flashing completed!") + + # now get the CRC from the device and reset the device, if OK + res = await client.call(node_name, "crc", [0, size]) + # just to be sure, make it unsigned + res = res & 0xFFFFFFFF + # the result of the CRC is signed, actually, so make reinterpret it as unsigned + print(f"Calculated CRC: {hex(crc)} and received: {hex(res)}") + + if res == crc: + print("Checksum match, perform reset!") + res = await client.call(node_name_dotdevice, "reset") + await client.disconnect() + else: + print("Checksum mismatch!") diff --git a/examples/shv-test/Kconfig b/examples/shv-test/Kconfig new file mode 100644 index 00000000000..26532c5dd51 --- /dev/null +++ b/examples/shv-test/Kconfig @@ -0,0 +1,30 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +menuconfig EXAMPLES_SHV_TEST + bool "Silicon Heaven Protocol showcase" + depends on NETUTILS_LIBSHVC + default n + ---help--- + Enable the shv-test application, to show off the capabilities of the SHV protocol. + +if EXAMPLES_SHV_TEST + +config EXAMPLES_SHV_TEST_PROGNAME + string "shv-test App Name" + default "shv_test" + ---help--- + This is the name of the program that will be used when the NSH ELF + program is installed. + +config EXAMPLES_SHV_TEST_PRIORITY + int "shv-test task priority" + default 100 + +config EXAMPLES_SHV_TEST_STACKSIZE + int "shv-test task stack size" + default 4096 + +endif # EXAMPLES_NX_SHV_FWUPDATER diff --git a/examples/shv-test/Make.defs b/examples/shv-test/Make.defs new file mode 100644 index 00000000000..2a76f7cd951 --- /dev/null +++ b/examples/shv-test/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/examples/shv-test/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ + +ifneq ($(CONFIG_EXAMPLES_SHV_TEST),) +CONFIGURED_APPS += $(APPDIR)/examples/shv-test +endif diff --git a/examples/shv-test/Makefile b/examples/shv-test/Makefile new file mode 100644 index 00000000000..68a515ad647 --- /dev/null +++ b/examples/shv-test/Makefile @@ -0,0 +1,31 @@ +############################################################################ +# apps/examples/shv-test/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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 $(APPDIR)/Make.defs + +MODULE = $(CONFIG_EXAMPLES_SHV_TEST_UPDATER) + +PROGNAME += $(CONFIG_EXAMPLES_SHV_TEST_PROGNAME) +PRIORITY += $(CONFIG_EXAMPLES_SHV_TEST_PRIORITY) +STACKSIZE += $(CONFIG_EXAMPLES_SHV_TEST_STACKSIZE) + +MAINSRC += shv_test.c + +include $(APPDIR)/Application.mk diff --git a/examples/shv-test/shv_test.c b/examples/shv-test/shv_test.c new file mode 100644 index 00000000000..6b3ec076cc4 --- /dev/null +++ b/examples/shv-test/shv_test.c @@ -0,0 +1,424 @@ +/**************************************************************************** + * apps/examples/shv-test/shv_test.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int shv_nuttxtesting_set(shv_con_ctx_t *shv_ctx, shv_node_t *item, + int rid); +static int shv_nuttxtesting_get(shv_con_ctx_t *shv_ctx, shv_node_t *item, + int rid); +static int shv_nuttxtesting_art(shv_con_ctx_t *shv_ctx, shv_node_t *item, + int rid); + +static void quit_handler(int signum); +static void print_help(char *name); + +static shv_node_t *shv_tree_create_dynamically(int mode); +static void attention_cb(shv_con_ctx_t *shv_ctx, + enum shv_attention_reason r); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* An execution barrier */ + +static sem_t running; + +/* Testing variable */ + +static int g_testing_val; + +/* ------------------------- ROOT METHODS --------------------------------- */ + +static const shv_method_des_t * const shv_dev_root_dmap_items[] = +{ + &shv_dmap_item_dir, + &shv_dmap_item_ls, +}; + +static const shv_dmap_t shv_dev_root_dmap = + SHV_CREATE_NODE_DMAP(root, shv_dev_root_dmap_items); + +/* ----------------------- nuttxtesting METHODS -------------------------- */ + +static const shv_method_des_t shv_dev_nuttxtesting_dmap_item_set = +{ + .name = "setTestingVal", + .method = shv_nuttxtesting_set +}; + +static const shv_method_des_t shv_dev_nuttxtesting_dmap_item_get = +{ + .name = "getTestingVal", + .method = shv_nuttxtesting_get +}; + +static const shv_method_des_t shv_dev_nuttxtesting_dmap_item_art = +{ + .name = "asciiArt", + .method = shv_nuttxtesting_art +}; + +static const shv_method_des_t *const shv_dev_nuttxtesting_dmap_items[] = +{ + &shv_dev_nuttxtesting_dmap_item_art, + &shv_dmap_item_dir, + &shv_dev_nuttxtesting_dmap_item_get, + &shv_dmap_item_ls, + &shv_dev_nuttxtesting_dmap_item_set +}; + +static const shv_dmap_t shv_dev_nuttxtesting_dmap = + SHV_CREATE_NODE_DMAP(nuttxtesting, shv_dev_nuttxtesting_dmap_items); + +/* ------------------- Static const tree root creation ------------------- */ + +/* First, define all static nodes */ + +static const shv_dotdevice_node_t shv_static_node_dotdevice = +{ + .shv_node = + { + .name = ".device", + .dir = UL_CAST_UNQ1(shv_dmap_t *, &shv_dotdevice_dmap), + .children = + { + .mode = (SHV_NLIST_MODE_GSA | SHV_NLIST_MODE_STATIC) + } + }, + .devops = + { + .reset = shv_dotdevice_node_posix_reset, + .uptime = shv_dotdevice_node_posix_uptime + }, + .name = "SHV Compatible Device", + .serial_number = "0xDEADBEEF", + .version = "0.1.0" +}; + +static const shv_dotapp_node_t shv_static_node_dotapp = +{ + .shv_node = + { + .name = ".app", + .dir = UL_CAST_UNQ1(shv_dmap_t *, &shv_dotapp_dmap), + .children = + { + .mode = (SHV_NLIST_MODE_GSA | SHV_NLIST_MODE_STATIC) + } + }, + .appops = + { + .date = NULL /* As of September 25, date parsing not implemented yet */ + }, + .name = "NuttX libshvc example", + .version = "1.0.0" +}; + +static const shv_node_t shv_static_node_nuttxtesting = +{ + .name = "nuttxTesting", + .dir = UL_CAST_UNQ1(shv_dmap_t *, &shv_dev_nuttxtesting_dmap), + .children = + { + .mode = (SHV_NLIST_MODE_GSA | SHV_NLIST_MODE_STATIC) + } +}; + +/* Now, define tree root's children */ + +const shv_node_t *const shv_static_tree_root_items[] = +{ + &shv_static_node_dotapp.shv_node, + &shv_static_node_dotdevice.shv_node, + &shv_static_node_nuttxtesting +}; + +/* Construct the root. Yes, it's a bit cumbersome, + * but an automated code generator should have no problem with this! + */ + +const shv_node_t shv_static_tree_root = +{ + .dir = UL_CAST_UNQ1(shv_dmap_t *, &shv_dev_root_dmap), + .children = + { + .mode = (SHV_NLIST_MODE_GSA | SHV_NLIST_MODE_STATIC), + .list = + { + .gsa = + { + .root = + { + .items = (void **)shv_static_tree_root_items, + .count = sizeof(shv_static_tree_root_items) / + sizeof(shv_static_tree_root_items[0]), + .alloc_count = 0, + } + } + } + } +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int shv_nuttxtesting_set(shv_con_ctx_t *shv_ctx, shv_node_t *item, + int rid) +{ + shv_unpack_data(&shv_ctx->unpack_ctx, &g_testing_val, 0); + printf("Testing val set to %d\n", g_testing_val); + shv_send_empty_response(shv_ctx, rid); + return 0; +} + +static int shv_nuttxtesting_get(shv_con_ctx_t *shv_ctx, shv_node_t *item, + int rid) +{ + shv_unpack_data(&shv_ctx->unpack_ctx, 0, 0); + shv_send_int(shv_ctx, rid, g_testing_val); + return 0; +} + +static int shv_nuttxtesting_art(shv_con_ctx_t *shv_ctx, shv_node_t *item, + int rid) +{ + /* Generated from https://budavariam.github.io/asciiart-text/ */ + + shv_unpack_data(&shv_ctx->unpack_ctx, 0, 0); + puts(" ____ _ ___ __"); + puts(" / __ || | | \\ \\ / /"); + puts(" \\___ \\| |_| |\\ \\ / / "); + puts(" ___) | _ | \\ V / "); + puts(" |____/|_| |_| \\_/ "); + shv_send_int(shv_ctx, rid, 0); + return 0; +} + +static shv_node_t *shv_tree_create_dynamically(int mode) +{ + shv_node_t *tree_root; + shv_dotapp_node_t *dotapp_node; + shv_node_t *nuttxtesting_node; + shv_dotdevice_node_t *dotdevice_node; + + tree_root = shv_tree_node_new("", &shv_dev_root_dmap, mode); + if (tree_root == NULL) + { + return NULL; + } + + dotapp_node = shv_tree_dotapp_node_new(&shv_dotapp_dmap, mode); + if (dotapp_node == NULL) + { + free(tree_root); + return NULL; + } + + dotapp_node->name = "NuttX libshvc example"; + dotapp_node->version = "1.0.0"; + + shv_tree_add_child(tree_root, &dotapp_node->shv_node); + + dotdevice_node = shv_tree_dotdevice_node_new(&shv_dotdevice_dmap, mode); + if (dotdevice_node == NULL) + { + free(tree_root); + free(dotapp_node); + return NULL; + } + + dotdevice_node->name = "SHV Compatible Device"; + dotdevice_node->serial_number = "0xDEADBEEF"; + dotdevice_node->version = "0.1.0"; + shv_tree_add_child(tree_root, &dotdevice_node->shv_node); + + nuttxtesting_node = shv_tree_node_new("nuttxTesting", + &shv_dev_nuttxtesting_dmap, mode); + if (nuttxtesting_node == NULL) + { + free(tree_root); + free(dotapp_node); + free(dotdevice_node); + return NULL; + } + + shv_tree_add_child(tree_root, nuttxtesting_node); + + return tree_root; +} + +static void quit_handler(int signum) +{ + puts("Stopping SHV FW Updater!"); + sem_post(&running); +} + +static void print_help(char *name) +{ + printf("%s: " + "\n", name); + puts("Silicon Heaven NuttX example"); + puts("Showcases the tree creation."); + puts(" The specifies the ULUT datastructure"); + puts(" to be used to construct the SHV tree, choose:"); + puts(" 0: dynamically allocate the tree and store it in an AVL tree."); + puts(" 1: dynamically allocate the tree and store it in a continuous"); + puts(" array with binary searching."); + puts(" 2: use static const preallocated nodes and store it in"); + puts(" a continuous array with binary searching."); +} + +static void attention_cb(shv_con_ctx_t *shv_ctx, enum shv_attention_reason r) +{ + if (r == SHV_ATTENTION_ERROR) + { + printf("Error occurred in SHV, the reason is: %s\n", + shv_errno_str(shv_ctx)); + sem_post(&running); + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: main + ****************************************************************************/ + +int main(int argc, char *argv[]) +{ + /* Define the SHV Communication parameters */ + + int ret; + struct shv_connection connection; + const shv_node_t *tree_root; + shv_con_ctx_t *ctx; + int alloc_mode = SHV_NLIST_MODE_GAVL; + const char *user; + const char *passwd; + const char *mount; + const char *ip; + const char *port_s; + int port; + + /* Initialize the communication. But only if parameters are passed. */ + + if (argc != 7) + { + print_help(argv[0]); + return 1; + } + else + { + alloc_mode = atoi(argv[6]); + if (alloc_mode < 0 || alloc_mode > 2) + { + print_help(argv[0]); + return 1; + } + } + + user = argv[1]; + passwd = argv[2]; + mount = argv[3]; + ip = argv[4]; + port_s = argv[5]; + port = atoi(port_s); + alloc_mode = atoi(argv[6]); + + shv_connection_init(&connection, SHV_TLAYER_TCPIP); + connection.broker_user = user; + connection.broker_password = passwd; + connection.broker_mount = mount; + connection.reconnect_period = 10; + connection.reconnect_retries = 0; + if (shv_connection_tcpip_init(&connection, ip, port) < 0) + { + fprintf(stderr, "Have you supplied valid params to shv_connection?\n"); + return 1; + } + + puts("SHV Connection Init OK"); + + if (alloc_mode != SHV_NLIST_MODE_STATIC) + { + tree_root = shv_tree_create_dynamically(alloc_mode); + if (tree_root == NULL) + { + fprintf(stderr, "Can't create the SHV tree."); + return 1; + } + } + else + { + tree_root = &shv_static_tree_root; + } + + puts("SHV Tree created!"); + ctx = shv_com_init((shv_node_t *)tree_root, &connection, attention_cb); + if (ctx == NULL) + { + fprintf(stderr, "Can't establish the comm with the broker.\n"); + return 1; + } + + ret = shv_create_process_thread(99, ctx); + if (ret < 0) + { + fprintf(stderr, "%s\n", shv_errno_str(ctx)); + free(ctx); + return 1; + } + + sem_init(&running, 0, 0); + signal(SIGTERM, quit_handler); + + sem_wait(&running); + + puts("Close the communication"); + shv_com_destroy(ctx); + + return 0; +} diff --git a/netutils/libshvc/.gitignore b/netutils/libshvc/.gitignore new file mode 100644 index 00000000000..8057c411ed6 --- /dev/null +++ b/netutils/libshvc/.gitignore @@ -0,0 +1 @@ +shv-libs4c \ No newline at end of file diff --git a/netutils/libshvc/Kconfig b/netutils/libshvc/Kconfig new file mode 100644 index 00000000000..c838b3a6c52 --- /dev/null +++ b/netutils/libshvc/Kconfig @@ -0,0 +1,10 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config NETUTILS_LIBSHVC + bool "Silicon Heaven Protocol (lightweight C implementation)" + default n + ---help--- + Enable the SHV API to be used in NuttX applications. diff --git a/netutils/libshvc/Make.defs b/netutils/libshvc/Make.defs new file mode 100644 index 00000000000..873f9e6979f --- /dev/null +++ b/netutils/libshvc/Make.defs @@ -0,0 +1,40 @@ +############################################################################# +# apps/netutils/libshvc/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################# + +ifeq ($(CONFIG_NETUTILS_LIBSHVC),y) +CONFIGURED_APPS += $(APPDIR)/netutils/libshvc + +SHVDIR = shv-libs4c +REL_PATH_TO_INC = "$(SHVDIR)/_compiled/include" +REL_PATH_TO_LNK = "$(SHVDIR)/_compiled/lib" + +# Make and visible + +CFLAGS += $(INCDIR_PREFIX)$(APPDIR)/netutils/libshvc/$(REL_PATH_TO_INC) +CXXFLAGS += $(INCDIR_PREFIX)$(APPDIR)/netutils/libshvc/$(REL_PATH_TO_INC) + +# Specify the paths for the exports. + +EXTRA_APPS_INCPATHS += $(APPDIR)/netutils/libshvc/$(REL_PATH_TO_INC)/shv \ + $(APPDIR)/netutils/libshvc/$(REL_PATH_TO_INC)/ulut + +endif diff --git a/netutils/libshvc/Makefile b/netutils/libshvc/Makefile new file mode 100644 index 00000000000..74a344e8983 --- /dev/null +++ b/netutils/libshvc/Makefile @@ -0,0 +1,57 @@ +############################################################################# +# apps/netutils/libshvc/Makefile +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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 $(APPDIR)/Make.defs + +SHVDIR = shv-libs4c + +# Where to look for the extra object files +EXTOBJS = $(wildcard ./$(SHVDIR)/_build/user/libs4c/libshvtree/*.o) +EXTOBJS += $(wildcard ./$(SHVDIR)/_build/user/libs4c/libshvchainpack/*.o) +EXTOBJS += $(wildcard ./$(SHVDIR)/_build/user/submodule/ulut/ulut/*.o) + +$(SHVDIR): + $(call CLONE, https://github.com/silicon-heaven/shv-libs4c, $(SHVDIR), ) + +# Compile the library. Also pass all the NuttX Makefile variables. +compile-libshv: $(SHVDIR) + $(Q) if [ ! -d "./$(SHVDIR)/_compiled" ]; then \ + ( cd $(SHVDIR); git submodule update --recursive --init ) > /dev/null 2>&1; \ + make -C $(SHVDIR) CONFIG_SHV_LIBS4C_PLATFORM="nuttx" \ + CC="$(CC)" \ + CXX="$(CXX)" \ + CFLAGS="$(CFLAGS)" \ + CXXFLAGS="$(CXXFLAGS)" \ + LD="$(LD)" \ + LDFLAGS="$(LDFLAGS)" \ + CONFIG_OC_ULUT_TESTS=n; \ + fi + +context:: compile-libshv + +depend:: compile-libshv + +distclean:: + make -C $(SHVDIR) distclean + $(call DELDIR, $(SHVDIR)) + +include $(APPDIR)/Application.mk