diff --git a/examples/smf/CMakeLists.txt b/examples/smf/CMakeLists.txt new file mode 100644 index 00000000000..bba48ca029a --- /dev/null +++ b/examples/smf/CMakeLists.txt @@ -0,0 +1,18 @@ +# ############################################################################## +# apps/examples/hsm_psicc2/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## + +if(CONFIG_EXAMPLES_SMF) + nuttx_add_application( + NAME + ${CONFIG_EXAMPLES_SMF_PROGNAME} + SRCS + smf_main.c + hsm_psicc2_thread.c + STACKSIZE + ${CONFIG_EXAMPLES_SMF_STACKSIZE} + PRIORITY + ${CONFIG_EXAMPLES_SMF_PRIORITY}) +endif() diff --git a/examples/smf/Kconfig b/examples/smf/Kconfig new file mode 100644 index 00000000000..8d8efc1f7a7 --- /dev/null +++ b/examples/smf/Kconfig @@ -0,0 +1,37 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config EXAMPLES_SMF + tristate "State Machine Framework PSICC2 demo (HSM)" + default n + depends on NSH_BUILTIN_APPS + depends on SYSTEM_SMF + depends on SYSTEM_SMF_ANCESTOR_SUPPORT + depends on SYSTEM_SMF_INITIAL_TRANSITION + +if EXAMPLES_SMF + +config EXAMPLES_SMF_PROGNAME + string "Program name" + default "hsm_psicc2" + +config EXAMPLES_SMF_PRIORITY + int "Priority" + default 100 + +config EXAMPLES_SMF_STACKSIZE + int "Stack size" + default 2048 + +config EXAMPLES_SMF_EVENT_QUEUE_SIZE + int "Event queue size" + default 10 + +config EXAMPLES_SMF_MQ_NAME + string "Message queue name" + default "/hsm_psicc2_mq" + +endif + diff --git a/examples/smf/Make.defs b/examples/smf/Make.defs new file mode 100644 index 00000000000..00be2807919 --- /dev/null +++ b/examples/smf/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/examples/hello/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. +# +############################################################################ + +ifneq ($(CONFIG_EXAMPLES_SMF),) +CONFIGURED_APPS += $(APPDIR)/examples/smf +endif diff --git a/examples/smf/Makefile b/examples/smf/Makefile new file mode 100644 index 00000000000..2fd9309c131 --- /dev/null +++ b/examples/smf/Makefile @@ -0,0 +1,37 @@ +############################################################################ +# apps/examples/hsm_psicc2/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 + +# HSM PSICC2 (SMF demo) built-in application info + +PROGNAME = $(CONFIG_EXAMPLES_SMF_PROGNAME) +PRIORITY = $(CONFIG_EXAMPLES_SMF_PRIORITY) +STACKSIZE = $(CONFIG_EXAMPLES_SMF_STACKSIZE) +MODULE = $(CONFIG_EXAMPLES_SMF) + +# HSM PSICC2 Example + +MAINSRC = smf_main.c +CSRCS += hsm_psicc2_thread.c + +include $(APPDIR)/Application.mk diff --git a/examples/smf/hsm_psicc2_thread.c b/examples/smf/hsm_psicc2_thread.c new file mode 100644 index 00000000000..e457969e138 --- /dev/null +++ b/examples/smf/hsm_psicc2_thread.c @@ -0,0 +1,626 @@ +/**************************************************************************** + * apps/examples/smf/hsm_psicc2_thread.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Glenn Andrews + * State Machine example copyright (c) Miro Samek + * + * Implementation of the statechart in Figure 2.11 of + * Practical UML Statecharts in C/C++, 2nd Edition by Miro Samek + * https://www.state-machine.com/psicc2 + * Used with permission of the author. + * + * 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 "hsm_psicc2_thread.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct s_object +{ + struct smf_ctx ctx; /* MUST be first (matches SMF_CTX(obj)) */ + struct hsm_psicc2_event event; + int foo; +}; + +enum demo_states +{ + STATE_INITIAL, + STATE_S, + STATE_S1, + STATE_S2, + STATE_S11, + STATE_S21, + STATE_S211 +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct s_object s_obj; +static bool g_started; +static const struct smf_state demo_states[]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * STATE_INITIAL + ****************************************************************************/ + +static void initial_entry(void *o) +{ + struct s_object *obj = (struct s_object *)o; + + printf("[psicc2] %s\n", __func__); + obj->foo = false; +} + +static enum smf_state_result initial_run(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); + return SMF_EVENT_PROPAGATE; +} + +static void initial_exit(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); +} + +/**************************************************************************** + * STATE_S + ****************************************************************************/ + +static void s_entry(void *o) +{ + (void)o; + + printf("[psicc2] %s", __func__); +} + +static enum smf_state_result s_run(void *o) +{ + struct s_object *obj = (struct s_object *)o; + + printf("[psicc2] %s\n", __func__); + + switch (obj->event.event_id) + { + case EVENT_E: + printf("[psicc2] %s received EVENT_E", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S11]); + break; + + case EVENT_I: + if (obj->foo) + { + printf("[psicc2] %s received EVENT_I and set foo false\n", + __func__); + obj->foo = false; + } + else + { + printf("[psicc2] %s received EVENT_I and did nothing\n", + __func__); + } + + return SMF_EVENT_HANDLED; + + case EVENT_TERMINATE: + printf("[psicc2] %s received EVENT_TERMINATE. Terminating\n", + __func__); + smf_set_terminate(SMF_CTX(obj), -1); + break; + + default: + break; + } + + return SMF_EVENT_PROPAGATE; +} + +static void s_exit(void *o) +{ + (void)o; + + printf("%s", __func__); +} + +/**************************************************************************** + * STATE_S1 + ****************************************************************************/ + +static void s1_entry(void *o) +{ + (void)o; + + printf("%s", __func__); +} + +static enum smf_state_result s1_run(void *o) +{ + struct s_object *obj = (struct s_object *)o; + + printf("[psicc2] %s\n", __func__); + + switch (obj->event.event_id) + { + case EVENT_A: + printf("[psicc2] %s received EVENT_A\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S1]); + break; + + case EVENT_B: + printf("[psicc2] %s received EVENT_B\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S11]); + break; + + case EVENT_C: + printf("[psicc2] %s received EVENT_C\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S2]); + break; + + case EVENT_D: + if (!obj->foo) + { + printf("[psicc2] %s received EVENT_D and acted on it\n", + __func__); + obj->foo = true; + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S]); + } + else + { + printf("[psicc2] %s received EVENT_D and ignored it\n", + __func__); + } + break; + + case EVENT_F: + printf("[psicc2] %s received EVENT_F\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S211]); + break; + + case EVENT_I: + printf("[psicc2] %s received EVENT_I\n", __func__); + return SMF_EVENT_HANDLED; + + default: + break; + } + + return SMF_EVENT_PROPAGATE; +} + +static void s1_exit(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); +} + +/**************************************************************************** + * STATE_S11 + ****************************************************************************/ + +static void s11_entry(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); +} + +static enum smf_state_result s11_run(void *o) +{ + struct s_object *obj = (struct s_object *)o; + + printf("[psicc2] %s\n", __func__); + + switch (obj->event.event_id) + { + case EVENT_D: + if (obj->foo) + { + printf("[psicc2] %s received EVENT_D and acted upon it\n", + __func__); + obj->foo = false; + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S1]); + } + else + { + printf("[psicc2] %s received EVENT_D and ignored it\n", + __func__); + } + break; + + case EVENT_G: + printf("[psicc2] %s received EVENT_G\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S21]); + break; + + case EVENT_H: + printf("[psicc2] %s received EVENT_H\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S]); + break; + + default: + break; + } + + return SMF_EVENT_PROPAGATE; +} + +static void s11_exit(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); +} + +/**************************************************************************** + * STATE_S2 + ****************************************************************************/ + +static void s2_entry(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); +} + +static enum smf_state_result s2_run(void *o) +{ + struct s_object *obj = (struct s_object *)o; + + printf("[psicc2] %s\n", __func__); + + switch (obj->event.event_id) + { + case EVENT_C: + printf("[psicc2] %s received EVENT_C\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S1]); + break; + + case EVENT_F: + printf("[psicc2] %s received EVENT_F\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S11]); + break; + + case EVENT_I: + if (!obj->foo) + { + printf("[psicc2] %s received EVENT_I and set foo true\n", + __func__); + obj->foo = true; + return SMF_EVENT_HANDLED; + } + else + { + printf("[psicc2] %s received EVENT_I and did nothing\n", + __func__); + } + break; + + default: + break; + } + + return SMF_EVENT_PROPAGATE; +} + +static void s2_exit(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); +} + +/**************************************************************************** + * STATE_S21 + ****************************************************************************/ + +static void s21_entry(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); +} + +static enum smf_state_result s21_run(void *o) +{ + struct s_object *obj = (struct s_object *)o; + + printf("[psicc2] %s\n", __func__); + + switch (obj->event.event_id) + { + case EVENT_A: + printf("[psicc2] %s received EVENT_A\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S21]); + break; + + case EVENT_B: + printf("[psicc2] %s received EVENT_B\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S211]); + break; + + case EVENT_G: + printf("[psicc2] %s received EVENT_G\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S1]); + break; + + default: + break; + } + + return SMF_EVENT_PROPAGATE; +} + +static void s21_exit(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); +} + +/**************************************************************************** + * STATE_S211 + ****************************************************************************/ + +static void s211_entry(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); +} + +static enum smf_state_result s211_run(void *o) +{ + struct s_object *obj = (struct s_object *)o; + + printf("[psicc2] %s\n", __func__); + + switch (obj->event.event_id) + { + case EVENT_D: + printf("[psicc2] %s received EVENT_D\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S21]); + break; + + case EVENT_H: + printf("[psicc2] %s received EVENT_H\n", __func__); + smf_set_state(SMF_CTX(obj), &demo_states[STATE_S]); + break; + + default: + break; + } + + return SMF_EVENT_PROPAGATE; +} + +static void s211_exit(void *o) +{ + (void)o; + + printf("[psicc2] %s\n", __func__); +} + +/**************************************************************************** + * State Table + ****************************************************************************/ + +static const struct smf_state demo_states[] = +{ + [STATE_INITIAL] = SMF_CREATE_STATE( + initial_entry, + initial_run, + initial_exit, + NULL, + &demo_states[STATE_S2] + ), + [STATE_S] = SMF_CREATE_STATE( + s_entry, + s_run, + s_exit, + &demo_states[STATE_INITIAL], + &demo_states[STATE_S11] + ), + [STATE_S1] = SMF_CREATE_STATE( + s1_entry, + s1_run, + s1_exit, + &demo_states[STATE_S], + &demo_states[STATE_S11] + ), + [STATE_S2] = SMF_CREATE_STATE( + s2_entry, + s2_run, + s2_exit, + &demo_states[STATE_S], + &demo_states[STATE_S211] + ), + [STATE_S11] = SMF_CREATE_STATE( + s11_entry, + s11_run, + s11_exit, + &demo_states[STATE_S1], + NULL + ), + [STATE_S21] = SMF_CREATE_STATE( + s21_entry, + s21_run, + s21_exit, + &demo_states[STATE_S2], + &demo_states[STATE_S211] + ), + [STATE_S211] = SMF_CREATE_STATE( + s211_entry, + s211_run, + s211_exit, + &demo_states[STATE_S21], + NULL + ) +}; + +/**************************************************************************** + * Name: hsm_psicc2_thread_main + ****************************************************************************/ + +static int hsm_psicc2_thread_main(int argc, char **argv) +{ + struct mq_attr attr; + mqd_t mq; + ssize_t n; + int rc; + + (void)argc; + (void)argv; + + memset(&attr, 0, sizeof(attr)); + attr.mq_maxmsg = CONFIG_EXAMPLES_SMF_EVENT_QUEUE_SIZE; + attr.mq_msgsize = sizeof(struct hsm_psicc2_event); + + mq = mq_open(CONFIG_EXAMPLES_SMF_MQ_NAME, O_CREAT | O_RDONLY, 0666, &attr); + if (mq == (mqd_t)-1) + { + printf("psicc2][ERR] mq_open(%s) failed: %d\n", + CONFIG_EXAMPLES_SMF_MQ_NAME, errno); + return -1; + } + + printf("[psicc2] State Machine thread started\n"); + + memset(&s_obj, 0, sizeof(s_obj)); + smf_set_initial(SMF_CTX(&s_obj), &demo_states[STATE_INITIAL]); + + while (true) + { + n = mq_receive(mq, (char *)&s_obj.event, sizeof(s_obj.event), NULL); + if (n < 0) + { + printf("psicc2][ERR] mq_receive failed: %d\n", errno); + continue; + } + + rc = smf_run_state(SMF_CTX(&s_obj)); + if (rc != 0) + { + printf("[psicc2] SMF terminated (rc=%d). Exiting thread\n", rc); + break; + } + } + + mq_close(mq); + mq_unlink(CONFIG_EXAMPLES_SMF_MQ_NAME); + + g_started = false; + memset(&s_obj, 0, sizeof(s_obj)); + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: hsm_psicc2_thread_start + ****************************************************************************/ + +int hsm_psicc2_thread_start(void) +{ + int pid; + + if (g_started) + { + return 0; + } + + pid = task_create("psicc2_thread", + CONFIG_EXAMPLES_SMF_PRIORITY, + CONFIG_EXAMPLES_SMF_STACKSIZE, + hsm_psicc2_thread_main, + NULL); + if (pid < 0) + { + printf("psicc2][ERR] task_create failed: %d", errno); + return -1; + } + + g_started = true; + return 0; +} + +/**************************************************************************** + * Name: hsm_psicc2_post_event + ****************************************************************************/ + +int hsm_psicc2_post_event(uint32_t event_id) +{ + struct hsm_psicc2_event ev; + mqd_t mq; + int rc; + + ev.event_id = event_id; + + mq = mq_open(CONFIG_EXAMPLES_SMF_MQ_NAME, O_WRONLY | O_NONBLOCK); + if (mq == (mqd_t)-1) + { + printf("psicc2][ERR] mq_open(O_WRONLY) failed: %d " + "(did you run 'hsm_psicc2 start'?)\n", + errno); + return -1; + } + + rc = mq_send(mq, (const char *)&ev, sizeof(ev), 0); + if (rc < 0) + { + printf("psicc2][ERR] mq_send failed: %d\n", errno); + mq_close(mq); + return -1; + } + + mq_close(mq); + return 0; +} diff --git a/examples/smf/hsm_psicc2_thread.h b/examples/smf/hsm_psicc2_thread.h new file mode 100644 index 00000000000..385a2970312 --- /dev/null +++ b/examples/smf/hsm_psicc2_thread.h @@ -0,0 +1,89 @@ +/**************************************************************************** + * apps/examples/smf/hsm_psicc2_thread.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Glenn Andrews + * State Machine example copyright (c) Miro Samek + * + * Implementation of the statechart in Figure 2.11 of + * Practical UML Statecharts in C/C++, 2nd Edition by Miro Samek + * https://www.state-machine.com/psicc2 + * Used with permission of the author. + * + * 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. + * + ****************************************************************************/ + +#ifndef __APPS_EXAMPLES_SMF_HSM_PSICC2_THREAD_H +#define __APPS_EXAMPLES_SMF_HSM_PSICC2_THREAD_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct hsm_psicc2_event +{ + uint32_t event_id; +}; + +enum demo_events +{ + EVENT_A, + EVENT_B, + EVENT_C, + EVENT_D, + EVENT_E, + EVENT_F, + EVENT_G, + EVENT_H, + EVENT_I, + EVENT_TERMINATE +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int hsm_psicc2_thread_start(void); +int hsm_psicc2_post_event(uint32_t event_id); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __APPS_EXAMPLES_SMF_HSM_PSICC2_THREAD_H */ diff --git a/examples/smf/smf_main.c b/examples/smf/smf_main.c new file mode 100644 index 00000000000..b6a9677a542 --- /dev/null +++ b/examples/smf/smf_main.c @@ -0,0 +1,127 @@ +/**************************************************************************** + * apps/examples/smf/smf_main.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Glenn Andrews + * State Machine example copyright (c) Miro Samek + * + * Implementation of the statechart in Figure 2.11 of + * Practical UML Statecharts in C/C++, 2nd Edition by Miro Samek + * https://www.state-machine.com/psicc2 + * Used with permission of the author. + * + * 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 "hsm_psicc2_thread.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usage + ****************************************************************************/ + +static void usage(void) +{ + printf("Usage:\n"); + printf(" hsm_psicc2 start\n"); + printf(" hsm_psicc2 event \n"); + printf(" hsm_psicc2 terminate\n"); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: main + ****************************************************************************/ + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + usage(); + return 1; + } + + if (strcmp(argv[1], "start") == 0) + { + printf("State Machine Framework Demo\n"); + printf("See PSiCC2 Fig 2.11 for the statechart\n"); + printf("https://www.state-machine.com/psicc2\n\n"); + return hsm_psicc2_thread_start(); + } + + if (strcmp(argv[1], "terminate") == 0) + { + return hsm_psicc2_post_event(EVENT_TERMINATE); + } + + if (strcmp(argv[1], "event") == 0) + { + int c; + + if (argc < 3 || argv[2] == NULL || argv[2][0] == '\0') + { + usage(); + return 1; + } + + c = toupper((unsigned char)argv[2][0]); + switch (c) + { + case 'A': + return hsm_psicc2_post_event(EVENT_A); + case 'B': + return hsm_psicc2_post_event(EVENT_B); + case 'C': + return hsm_psicc2_post_event(EVENT_C); + case 'D': + return hsm_psicc2_post_event(EVENT_D); + case 'E': + return hsm_psicc2_post_event(EVENT_E); + case 'F': + return hsm_psicc2_post_event(EVENT_F); + case 'G': + return hsm_psicc2_post_event(EVENT_G); + case 'H': + return hsm_psicc2_post_event(EVENT_H); + case 'I': + return hsm_psicc2_post_event(EVENT_I); + default: + printf("Invalid event '%c'\n", c); + return 1; + } + } + + usage(); + return 1; +} diff --git a/include/system/smf.h b/include/system/smf.h new file mode 100644 index 00000000000..edb1bf81be7 --- /dev/null +++ b/include/system/smf.h @@ -0,0 +1,267 @@ +/**************************************************************************** + * apps/include/system/smf.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __APPS_INCLUDE_SYSTEM_SMF_H +#define __APPS_INCLUDE_SYSTEM_SMF_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT +# ifdef CONFIG_SYSTEM_SMF_INITIAL_TRANSITION +# define SMF_CREATE_STATE(_entry, _run, _exit, _parent, _initial) \ + { \ + .entry = (_entry), \ + .run = (_run), \ + .exit = (_exit), \ + .parent = (_parent), \ + .initial = (_initial), \ + } +# else +# define SMF_CREATE_STATE(_entry, _run, _exit, _parent, _initial) \ + { \ + .entry = (_entry), \ + .run = (_run), \ + .exit = (_exit), \ + .parent = (_parent), \ + } +# endif +#else +# define SMF_CREATE_STATE(_entry, _run, _exit, _parent, _initial) \ + { \ + .entry = (_entry), \ + .run = (_run), \ + .exit = (_exit), \ + } +#endif + +#define SMF_CTX(o) ((struct smf_ctx *)(o)) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum smf_state_result +{ + SMF_EVENT_HANDLED, + SMF_EVENT_PROPAGATE +}; + +typedef void (*state_method)(void *obj); + +typedef enum smf_state_result (*state_execution)(void *obj); + +struct smf_state +{ + /* Optional method that will be run when this state is entered. */ + + state_method entry; + + /* Optional method that will be run repeatedly during the state machine + * loop. + */ + + state_execution run; + + /* Optional method that will be run when this state exits. */ + + state_method exit; + +#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT + /* Optional parent state that contains common entry/run/exit + * implementation among various child states. + * entry: Parent function executes BEFORE child function. + * run: Parent function executes AFTER child function. + * exit: Parent function executes AFTER child function. + * Note: When transitioning between two child states with a shared + * parent, that parent's exit and entry functions do not execute. + */ + + const struct smf_state *parent; + +# ifdef CONFIG_SYSTEM_SMF_INITIAL_TRANSITION + /* Optional initial transition state. NULL for leaf states. */ + + const struct smf_state *initial; +# endif /* CONFIG_SYSTEM_SMF_INITIAL_TRANSITION */ +#endif /* CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT */ +}; + +struct smf_ctx +{ + /* Current state the state machine is executing. */ + + const struct smf_state *current; + + /* Previous state the state machine executed. */ + + const struct smf_state *previous; + +#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT + /* Currently executing state (which may be a parent). */ + + const struct smf_state *executing; +#endif /* CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT */ + + /* This value is set by the set_terminate function and should + * terminate the state machine when its set to a value other than + * zero when it's returned by the run_state function. + */ + + int32_t terminate_val; + + /* The state machine casts this to a "struct internal_ctx" and it's + * used to track state machine context. + */ + + uint32_t internal; +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: smf_set_initial + * + * Description: + * Initialize the state machine and set its initial state. + * + * Input Parameters: + * ctx - State machine context + * init_state - Initial state the state machine starts in + * + ****************************************************************************/ + +void smf_set_initial(struct smf_ctx *ctx, + const struct smf_state *init_state); + +/**************************************************************************** + * Name: smf_set_state + * + * Description: + * Change the state machine state. This handles exiting the previous state + * and entering the target state. For HSMs the entry and exit actions of + * the Least Common Ancestor are not run. + * + * Input Parameters: + * ctx - State machine context + * new_state - State to transition to + * + ****************************************************************************/ + +void smf_set_state(struct smf_ctx *ctx, const struct smf_state *new_state); + +/**************************************************************************** + * Name: smf_set_terminate + * + * Description: + * Terminate a state machine. + * + * Input Parameters: + * ctx - State machine context + * val - Non-zero termination value returned by smf_run_state + * + ****************************************************************************/ + +void smf_set_terminate(struct smf_ctx *ctx, int32_t val); + +/**************************************************************************** + * Name: smf_run_state + * + * Description: + * Run one iteration of a state machine (including any parent states). + * + * Input Parameters: + * ctx - State machine context + * + * Returned Value: + * A non-zero value terminates the state machine. This non-zero value may + * represent a terminal state being reached or detection of an error. + * + ****************************************************************************/ + +int32_t smf_run_state(struct smf_ctx *ctx); + +/**************************************************************************** + * Name: smf_get_current_leaf_state + * + * Description: + * Get the current leaf state. This may be a parent state if the HSM is + * malformed (initial transitions are not set up correctly). + * + ****************************************************************************/ + +static inline const struct smf_state * +smf_get_current_leaf_state(const struct smf_ctx *ctx) +{ + return ctx->current; +} + +/**************************************************************************** + * Name: smf_get_current_executing_state + * + * Description: + * Get the state that is currently executing. This may be a parent state. + * + ****************************************************************************/ + +static inline const struct smf_state * +smf_get_current_executing_state(const struct smf_ctx *ctx) +{ +#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT + return ctx->executing; +#else + return ctx->current; +#endif /* CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT */ +} + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __APPS_INCLUDE_SYSTEM_SMF_H */ diff --git a/system/smf/Kconfig b/system/smf/Kconfig new file mode 100644 index 00000000000..62e52cf568a --- /dev/null +++ b/system/smf/Kconfig @@ -0,0 +1,28 @@ +# SMF (State Machine Framework) configuration options +# +# For syntax reference, see kconfig-language.txt in the NuttX tools repository. + +config SYSTEM_SMF + bool "(SMF) support" + default n + ---help--- + Enables the State Machine Framework (SMF) for implementing state machines + +if SYSTEM_SMF + +config SYSTEM_SMF_ANCESTOR_SUPPORT + bool "Enable ancestor/parent state support" + default n + ---help--- + Enables support for parent/ancestor relationships between SMF states. + Required for hierarchical state machines. + +config SYSTEM_SMF_INITIAL_TRANSITION + bool "Enable initial transition support" + default n + depends on SYSTEM_SMF_ANCESTOR_SUPPORT + ---help--- + Enables support for initial transitions in hierarchical states. + Depends on ancestor/parent state support. + +endif # SYSTEM_SMF diff --git a/system/smf/Make.defs b/system/smf/Make.defs new file mode 100644 index 00000000000..d628714f295 --- /dev/null +++ b/system/smf/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/smf/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_SYSTEM_SMF),y) +CONFIGURED_APPS += $(APPDIR)/system/smf +endif diff --git a/system/smf/Makefile b/system/smf/Makefile new file mode 100644 index 00000000000..d5e1b549c37 --- /dev/null +++ b/system/smf/Makefile @@ -0,0 +1,29 @@ +############################################################################ +# apps/smf/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 + +# State Machine Framework library + +CSRCS = smf.c + +include $(APPDIR)/Application.mk diff --git a/system/smf/smf.c b/system/smf/smf.c new file mode 100644 index 00000000000..473a64e234c --- /dev/null +++ b/system/smf/smf.c @@ -0,0 +1,605 @@ +/**************************************************************************** + * apps/system/smf/smf.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2021 The Chromium OS Authors + * + * 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 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct internal_ctx +{ + bool new_state : 1; + bool terminate : 1; + bool is_exit : 1; + bool handled : 1; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT + +/**************************************************************************** + * Name: is_descendant_of + ****************************************************************************/ + +static bool is_descendant_of(const struct smf_state *test_state, + const struct smf_state *target_state) +{ + const struct smf_state *state; + + for (state = test_state; state != NULL; state = state->parent) + { + if (target_state == state) + { + return true; + } + } + + return false; +} + +/**************************************************************************** + * Name: get_child_of + ****************************************************************************/ + +static const struct smf_state * +get_child_of(const struct smf_state *states, const struct smf_state *parent) +{ + const struct smf_state *state = states; + + while (state != NULL) + { + if (state->parent == parent) + { + return state; + } + + state = state->parent; + } + + return NULL; +} + +/**************************************************************************** + * Name: get_lca_of + * + * Description: + * Find the Least Common Ancestor (LCA) of two states that are not + * ancestors of one another. + * + ****************************************************************************/ + +static const struct smf_state * +get_lca_of(const struct smf_state *source, const struct smf_state *dest) +{ + const struct smf_state *ancestor; + + for (ancestor = source->parent; ancestor != NULL; + ancestor = ancestor->parent) + { + /* First common ancestor. */ + + if (is_descendant_of(dest, ancestor)) + { + return ancestor; + } + } + + return NULL; +} + +/**************************************************************************** + * Name: smf_execute_all_entry_actions + ****************************************************************************/ + +static bool smf_execute_all_entry_actions(struct smf_ctx *const ctx, + const struct smf_state *new_state, + const struct smf_state *topmost) +{ + struct internal_ctx *const internal = (void *)&ctx->internal; + const struct smf_state *to_execute; + + if (new_state == topmost) + { + /* There are no child states, so do nothing. */ + + return false; + } + + for (to_execute = get_child_of(new_state, topmost); + to_execute != NULL && to_execute != new_state; + to_execute = get_child_of(new_state, to_execute)) + { + /* Keep track of the executing entry action in case it calls + * smf_set_state(). + */ + + ctx->executing = to_execute; + + /* Execute every entry action EXCEPT that of the topmost state. */ + + if (to_execute->entry != NULL) + { + to_execute->entry(ctx); + + /* No need to continue if terminate was set. */ + + if (internal->terminate) + { + ctx->executing = ctx->current; + return true; + } + } + } + + /* Execute the new state entry action. */ + + ctx->executing = new_state; + if (new_state->entry != NULL) + { + new_state->entry(ctx); + + /* No need to continue if terminate was set. */ + + if (internal->terminate) + { + ctx->executing = ctx->current; + return true; + } + } + + ctx->executing = ctx->current; + + return false; +} + +/**************************************************************************** + * Name: smf_execute_ancestor_run_actions + ****************************************************************************/ + +static bool smf_execute_ancestor_run_actions(struct smf_ctx *const ctx) +{ + struct internal_ctx *const internal = (void *)&ctx->internal; + const struct smf_state *tmp_state; + + /* Execute all run actions in reverse order. */ + + if (internal->terminate) + { + /* Return if the current state terminated. */ + + return true; + } + + if (internal->new_state || internal->handled) + { + /* The child state transitioned or handled it. Stop propagating. */ + + return false; + } + + /* Try to run parent run actions. */ + + for (tmp_state = ctx->current->parent; tmp_state != NULL; + tmp_state = tmp_state->parent) + { + enum smf_state_result rc; + + /* Keep track of where we are in case an ancestor calls + * smf_set_state(). + */ + + ctx->executing = tmp_state; + + /* Execute parent run action. */ + + if (tmp_state->run != NULL) + { + rc = tmp_state->run(ctx); + + if (rc == SMF_EVENT_HANDLED) + { + internal->handled = true; + } + + /* No need to continue if terminate was set. */ + + if (internal->terminate) + { + ctx->executing = ctx->current; + return true; + } + + /* This state dealt with it. Stop propagating. */ + + if (internal->new_state || internal->handled) + { + break; + } + } + } + + /* All done executing the run actions. */ + + ctx->executing = ctx->current; + + return false; +} + +/**************************************************************************** + * Name: smf_execute_all_exit_actions + ****************************************************************************/ + +static bool smf_execute_all_exit_actions(struct smf_ctx *const ctx, + const struct smf_state *topmost) +{ + struct internal_ctx *const internal = (void *)&ctx->internal; + const struct smf_state *tmp_state; + const struct smf_state *to_execute; + + tmp_state = ctx->executing; + + for (to_execute = ctx->current; + to_execute != NULL && to_execute != topmost; + to_execute = to_execute->parent) + { + if (to_execute->exit != NULL) + { + ctx->executing = to_execute; + to_execute->exit(ctx); + + /* No need to continue if terminate was set in the exit action. */ + + if (internal->terminate) + { + ctx->executing = tmp_state; + return true; + } + } + } + + ctx->executing = tmp_state; + + return false; +} + +#endif /* CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT */ + +/**************************************************************************** + * Name: smf_clear_internal_state + ****************************************************************************/ + +static void smf_clear_internal_state(struct smf_ctx *const ctx) +{ + struct internal_ctx *const internal = (void *)&ctx->internal; + + internal->is_exit = false; + internal->terminate = false; + internal->handled = false; + internal->new_state = false; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: smf_set_initial + ****************************************************************************/ + +void smf_set_initial(struct smf_ctx *const ctx, + const struct smf_state *init_state) +{ +#ifdef CONFIG_SYSTEM_SMF_INITIAL_TRANSITION + /* The final target will be the deepest leaf state that the target + * contains. Set that as the real target. + */ + + while (init_state->initial != NULL) + { + init_state = init_state->initial; + } +#endif + + smf_clear_internal_state(ctx); + ctx->current = init_state; + ctx->previous = NULL; + ctx->terminate_val = 0; + +#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT + struct internal_ctx *const internal = (void *)&ctx->internal; + const struct smf_state *topmost; + + ctx->executing = init_state; + + /* topmost is the root ancestor of init_state, its parent == NULL. */ + + topmost = get_child_of(init_state, NULL); + + /* Execute topmost state entry action, + * since smf_execute_all_entry_actions doesn't. + */ + + if (topmost->entry != NULL) + { + ctx->executing = topmost; + topmost->entry(ctx); + ctx->executing = init_state; + + if (internal->terminate) + { + /* No need to continue if terminate was set. */ + + return; + } + } + + if (smf_execute_all_entry_actions(ctx, init_state, topmost)) + { + /* No need to continue if terminate was set. */ + + return; + } +#else + /* Execute entry action if it exists. */ + + if (init_state->entry != NULL) + { + init_state->entry(ctx); + } +#endif +} + +/**************************************************************************** + * Name: smf_set_state + ****************************************************************************/ + +void smf_set_state(struct smf_ctx *const ctx, + const struct smf_state *new_state) +{ + struct internal_ctx *const internal = (void *)&ctx->internal; + + if (new_state == NULL) + { + printf("SMF ERR: new_state cannot be NULL\n"); + return; + } + + /* It does not make sense to call smf_set_state in an exit phase of a + * state since we are already in a transition; we would always ignore + * the intended state to transition into. + */ + + if (internal->is_exit) + { + printf("SMF ERR: Calling %s from exit action\n", __func__); + return; + } + +#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT + const struct smf_state *topmost; + + if (ctx->executing != new_state && + ctx->executing->parent == new_state->parent) + { + /* Optimize sibling transitions (different states under same parent). */ + + topmost = ctx->executing->parent; + } + else if (is_descendant_of(ctx->executing, new_state)) + { + /* New state is a parent of where we are now. */ + + topmost = new_state; + } + else if (is_descendant_of(new_state, ctx->executing)) + { + /* We are a parent of the new state. */ + + topmost = ctx->executing; + } + else + { + /* Not directly related, find LCA. */ + + topmost = get_lca_of(ctx->executing, new_state); + } + + internal->is_exit = true; + internal->new_state = true; + + /* Call all exit actions up to (but not including) the topmost. */ + + if (smf_execute_all_exit_actions(ctx, topmost)) + { + /* No need to continue if terminate was set in the exit action. */ + + return; + } + + /* If self-transition, call the exit action. */ + + if (ctx->executing == new_state && new_state->exit != NULL) + { + new_state->exit(ctx); + + /* No need to continue if terminate was set in the exit action. */ + + if (internal->terminate) + { + return; + } + } + + internal->is_exit = false; + + /* If self transition, call the entry action. */ + + if (ctx->executing == new_state && new_state->entry != NULL) + { + new_state->entry(ctx); + + /* No need to continue if terminate was set in the entry action. */ + + if (internal->terminate) + { + return; + } + } + +#ifdef CONFIG_SYSTEM_SMF_INITIAL_TRANSITION + /* The final target will be the deepest leaf state that the target + * contains. Set that as the real target. + */ + + while (new_state->initial != NULL) + { + new_state = new_state->initial; + } +#endif + + /* Update the state variables. */ + + ctx->previous = ctx->current; + ctx->current = new_state; + ctx->executing = new_state; + + /* Call all entry actions (except those of topmost). */ + + if (smf_execute_all_entry_actions(ctx, new_state, topmost)) + { + /* No need to continue if terminate was set in the entry action. */ + + return; + } +#else + /* Flat state machines have a very simple transition. */ + + if (ctx->current->exit != NULL) + { + internal->is_exit = true; + ctx->current->exit(ctx); + + /* No need to continue if terminate was set in the exit action. */ + + if (internal->terminate) + { + return; + } + + internal->is_exit = false; + } + + /* Update the state variables. */ + + ctx->previous = ctx->current; + ctx->current = new_state; + + if (ctx->current->entry != NULL) + { + ctx->current->entry(ctx); + + /* No need to continue if terminate was set in the entry action. */ + + if (internal->terminate) + { + return; + } + } +#endif +} + +/**************************************************************************** + * Name: smf_set_terminate + ****************************************************************************/ + +void smf_set_terminate(struct smf_ctx *const ctx, int32_t val) +{ + struct internal_ctx *const internal = (void *)&ctx->internal; + + internal->terminate = true; + ctx->terminate_val = val; +} + +/**************************************************************************** + * Name: smf_run_state + ****************************************************************************/ + +int32_t smf_run_state(struct smf_ctx *const ctx) +{ + struct internal_ctx *const internal = (void *)&ctx->internal; + + /* No need to continue if terminate was set. */ + + if (internal->terminate) + { + return ctx->terminate_val; + } + + /* Executing a state's run function could cause a transition, so clear + * the internal state to ensure that the transition is handled correctly. + */ + + smf_clear_internal_state(ctx); + +#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT + ctx->executing = ctx->current; + + if (ctx->current->run != NULL) + { + enum smf_state_result rc; + + rc = ctx->current->run(ctx); + if (rc == SMF_EVENT_HANDLED) + { + internal->handled = true; + } + } + + if (smf_execute_ancestor_run_actions(ctx)) + { + return ctx->terminate_val; + } +#else + if (ctx->current->run != NULL) + { + ctx->current->run(ctx); + } +#endif + + return 0; +}