diff --git a/docs/guide/libbbfdm_DeviceInfo_FirmwareImage.md b/docs/guide/FirmwareImage.md similarity index 59% rename from docs/guide/libbbfdm_DeviceInfo_FirmwareImage.md rename to docs/guide/FirmwareImage.md index 4151da9b061c9fcf92b766b4c30a59cb8245e983..41c16f124e46cac9caef9e946b54b008b4fc6dc6 100644 --- a/docs/guide/libbbfdm_DeviceInfo_FirmwareImage.md +++ b/docs/guide/FirmwareImage.md @@ -1,12 +1,74 @@ -# Design For Firmware Activation +# Design For Dual FirmwareImage Download and Activation -According to the TR181 data model, the Activate() command is an operation to activate the firmware image immediately or schedule it in another time. +According to the TR181 data model, dual boot bank is represented by `Device.DeviceInfo.FirmwareImage.` multi-instance object + +``` +Device.DeviceInfo.FirmwareImage.{i}.Alias +Device.DeviceInfo.FirmwareImage.{i}.Available +Device.DeviceInfo.FirmwareImage.{i}.BootFailureLog +Device.DeviceInfo.FirmwareImage.{i}.Name +Device.DeviceInfo.FirmwareImage.{i}.Status +Device.DeviceInfo.FirmwareImage.{i}.Version +``` + +This object also provides two operate commands to manage the firmware image using USP(TR-369) + +1. Download() operate command is basically used for download a new firmware image and apply it +``` +Device.DeviceInfo.FirmwareImage.{i}.Download() +Device.DeviceInfo.FirmwareImage.{i}.Download() input:AutoActivate +Device.DeviceInfo.FirmwareImage.{i}.Download() input:CheckSum +Device.DeviceInfo.FirmwareImage.{i}.Download() input:CheckSumAlgorithm +Device.DeviceInfo.FirmwareImage.{i}.Download() input:CommandKey +Device.DeviceInfo.FirmwareImage.{i}.Download() input:FileSize +Device.DeviceInfo.FirmwareImage.{i}.Download() input:Password +Device.DeviceInfo.FirmwareImage.{i}.Download() input:URL +Device.DeviceInfo.FirmwareImage.{i}.Download() input:Username +Device.DeviceInfo.FirmwareImage.{i}.Download() input:X_IOWRT_EU_KeepConfig +``` + +> Note: `X_IOWRT_EU_KeepConfig` is a vendor extension and provide additional functionality, its not a mandatory parameter and only visible if vendor extension enabled. + + +2. Activate() operate command to activate the downloaded firmware or switch to other available bank + +``` +Device.DeviceInfo.FirmwareImage.{i}.Activate() +Device.DeviceInfo.FirmwareImage.{i}.Activate() input:TimeWindow.{i}.End +Device.DeviceInfo.FirmwareImage.{i}.Activate() input:TimeWindow.{i}.MaxRetries +Device.DeviceInfo.FirmwareImage.{i}.Activate() input:TimeWindow.{i}.Mode +Device.DeviceInfo.FirmwareImage.{i}.Activate() input:TimeWindow.{i}.Start +Device.DeviceInfo.FirmwareImage.{i}.Activate() input:TimeWindow.{i}.UserMessage +Device.DeviceInfo.FirmwareImage.{i}.Activate() input:X_IOWRT_EU_KeepConfig +``` + +> Note: `X_IOWRT_EU_KeepConfig` is a vendor extension and provide additional functionality, its not a mandatory parameter and only visible if vendor extension enabled. + +Apart from these it also has `Device.DeviceInfo.MaxNumberOfActivateTimeWindows` which defines the maximum available timewindows for `Activate()` operate command. + +Here, TR-181 definition for parameters and operate commands works well for most of the tree, except the Vendor extension functionality and TimeWindow management, which is cpe dependent. + +## Vendor extensions + +With Firmware management, one unaddressed topic is which is very critical for actual deployment is, "What to do with (user/operate) config changes done on the current firmware?" + +To give operate/user more flexibility to make that decision, we provide datamodel Vendor extensions to decide at the time of firmware Download/Activate, with below design -In fact, the Linux system already provides us a cron to schedule some jobs. And since Openwrt is one of the Linux systems, so we can use a cron job as solution to handle the firmware activation. -The Activate() command has as arguments the TimeWindow object which is used to activate the required firmware in a specified time. For that, foreach TimeWindow instance a cron job will be created. +1. In Download() operate command, + a. if AutoActivate=true, then only use the provided X_IOWRT_EU_KeepConfig value, in case of KeepConfig not provided use default keep_config=1 + b. if AutoActivate=false or not provided then ignore X_IOWRT_EU_KeepConfig value, in this case download will be done without keeping config +2. In Activate() operate command + a. Copy the current config to the new bank, if X_IOWRT_EU_KeepConfig is provided and true + b. If X_IOWRT_EU_KeepConfig not provided then use "1" as default keep config value +3. Bank switch using DeviceInfo.BootFirmwareImage + a. In case of boot back switch using BootFirmwareImage, config will not be copied -> Note: As per TR181 data model, max 5 instances of TimeWindow is supported. +## Activate command handling + +The Activate() command is an operation to activate the firmware image immediately or schedule it in another time using TimeWindow. + +In datamodel the TimeWindow is handled by using crontab. Below is an example of an 'Activate()' command call with three TimeWindow instances. As a result, three jobs are created according to the defined TimeWindow.{i}.Start: @@ -34,44 +96,19 @@ root@iopsys-44d43771aff0:~# ``` -For those cron jobs it is required to give the handler script to be executed which is in our case [bbf_activate_handler.sh](../../libbbfdm/scripts/bbf_activate_handler.sh). And, it is located under '/usr/share/bbfdm/' in the device. - - -## Cron job specification - -For each cron job related to the activated firmware, it is needed to define it as below: - -```bash -* * * * * command to execute * * * * * * -- - - - - - - - - - - - -| | | | | | | | | | | | -| | | | | | | | | | | --- Message that informs the user of a new activation request -| | | | | | | | | | ----- Maximum number of retries -| | | | | | | | | ------- Force firmware activation when it's not idle (0 - 1) -| | | | | | | | --------- End of the time window -| | | | | | | ----------- Firmware Bank id to be activated -| | | | | | ------------- Mode (AnyTime, Immediately, WhenIdle, ConfirmationNeeded) -| | | | | -------------------------------- Activate firmware script 'bbf_activate_handler.sh' -| | | | ---------------------------------- Day of week (0 - 6) (Sunday =0) -| | | ------------------------------------ Month (1 - 12) -| | -------------------------------------- Day (1 - 31) -| ---------------------------------------- Hour (0 - 23) ------------------------------------------- Minute (0 - 59) -``` - +For those cron jobs it is required to give the handler script to be executed which is in our case [bbf_activate_handler.sh](../../libbbfdm/scripts/bbf_activate_handler.sh). And, it is located under '/usr/share/bbfdm/scripts/' in the device. -## Activate Handler script -As described, we create a cron job for each TimeWindow in order to activate the required firmware within a specified time by running the [bbf_activate_handler.sh](../../libbbfdm/scripts/bbf_activate_handler.sh) handler script. +### Activate Handler for different Mode support -In fact, the aim of this script is to manage firmware images based on the **mode** and the other passed arguments. +The aim of `bbf_activate_handler.sh` script is to manage firmware images based on the **mode** and the other passed arguments. -### 1. Mode 'AnyTime' and 'Immediately': +#### 1. Mode 'AnyTime' and 'Immediately': For these modes and based on the firmware bank id, the required firmware image will be immediately activated at start time. The TimeWindow.{i}.End is ignored. -### 2. How to handle 'WhenIdle' mode: +#### 2. How to handle 'WhenIdle' mode: Definition of WhenIdle may vary for each deployment and customer, to make it customizable [bbf_check_idle.sh](../../libbbfdm/scripts/bbf_check_idle.sh) script is used. It is assumed that customer shall overwrite this file using customer-config to match with there requirement. @@ -93,7 +130,7 @@ If the exit code from the idle script is zero then firmware image can be activat > Note6: It is very likely that TimeWindow with 'WhenIdle' mode might not find any suitable Idle state, in that case firmware shall not be activated. If users/operators want to make sure that firmware gets activated at the end, then they can add a TimeWindow with 'AnyTime/Immediate' mode at the end, to activate the firmware. -## Good to know +#### Good to know * TimeWindow instance arguments are optional. @@ -109,33 +146,3 @@ If the exit code from the idle script is zero then firmware image can be activat * TimeWindow.{i}.Mode = 'ConfirmationNeeded' is not supported. - -## Vendor extension option to keep config while firmware download - -It deployments for some customers, its required to do a factory reset after doing a firmware upgrade to start the CPE from clean state and then provision it from ACS/Controller. - -As per standard datamodel, it's at-least 2 step time consuming process: -- Download the Firmware using 'Device.DeviceInfo.FirmwareImage.{i}.Download()' operate command with AutoActivate=1 -- Wait for the 'Device.Boot!' event -- Factory reset the CPE using 'Device.FactoryReset()' -- Wait for the Boot event and then start provisioning. - -We added an addition vendor specific input option which can be used by USP controller to factoryReset the CPE along with Firmware Upgrade, with this customer can save the cost of one additional reboot, which result into faster provisioning of the CPE. - -Below are the current input options defined for Download operate command -```bash -Device.DeviceInfo.FirmwareImage.{i}.Download() -Device.DeviceInfo.FirmwareImage.{i}.Download() input:AutoActivate -Device.DeviceInfo.FirmwareImage.{i}.Download() input:CheckSum -Device.DeviceInfo.FirmwareImage.{i}.Download() input:CheckSumAlgorithm -Device.DeviceInfo.FirmwareImage.{i}.Download() input:CommandKey -Device.DeviceInfo.FirmwareImage.{i}.Download() input:FileSize -Device.DeviceInfo.FirmwareImage.{i}.Download() input:Password -Device.DeviceInfo.FirmwareImage.{i}.Download() input:URL -Device.DeviceInfo.FirmwareImage.{i}.Download() input:Username -Device.DeviceInfo.FirmwareImage.{i}.Download() input:X_IOWRT_EU_KeepConfig -``` - -Customer can use X_IOWRT_EU_KeepConfig=0, to do factory reset(not copy the current config to next firmware) while doing the download. - -> Note: Default value of X_IOWRT_EU_KeepConfig is 1, so in case this option not used, it keeps the config(as the default behavior of the CPE). diff --git a/src/files/usr/share/bbfdm/scripts/bbf_activate_handler.sh b/src/files/usr/share/bbfdm/scripts/bbf_activate_handler.sh index 57c2caa917498e02df0e7fd13845d75ded136c06..4e66a73e971fa15c8f0c44633967822b2628fd55 100755 --- a/src/files/usr/share/bbfdm/scripts/bbf_activate_handler.sh +++ b/src/files/usr/share/bbfdm/scripts/bbf_activate_handler.sh @@ -11,7 +11,7 @@ ROOT="$(dirname "${0}")" CHECK_IDLE_FILE="${ROOT}/bbf_check_idle.sh" RETRY_TIME=300 START_TIME=$(date +%s) -MODE="${1}" +MODE="" log() { echo "${@}"|logger -t bbf.activate_firmware -p info @@ -19,7 +19,7 @@ log() { activate_and_reboot_device() { local bank_id="${1}" - local keep_config="${2}" + local keep_config="${2:-1}" local success success=$(ubus call fwbank set_bootbank "{'bank':${bank_id}}" | jsonfilter -e @.success) @@ -49,12 +49,12 @@ handle_whenidle_mode() { local diff=0 [ ! -x "${CHECK_IDLE_FILE}" ] && { - activate_and_reboot_device "${bank_id}" + activate_and_reboot_device "${bank_id}" "${keep_config}" } sh "${CHECK_IDLE_FILE}" if [ "$?" = "0" ]; then - activate_and_reboot_device "${bank_id}" + activate_and_reboot_device "${bank_id}" "${keep_config}" else [ "${end_time}" -gt "$((diff + RETRY_TIME))" ] && { sleep "${RETRY_TIME}" @@ -66,7 +66,7 @@ handle_whenidle_mode() { while [ "${end_time}" -gt "${diff}" ]; do sh "${CHECK_IDLE_FILE}" if [ "$?" = "0" ]; then - activate_and_reboot_device "${bank_id}" + activate_and_reboot_device "${bank_id}" "${keep_config}" else if [ "${end_time}" -gt "$((diff + RETRY_TIME))" ]; then @@ -92,12 +92,25 @@ handle_confirmation_needed_mode() { } ######################## main ######################## +if [ "$#" -lt "6" ]; then + log "Invalid inputs [$*]" + exit 1 +fi + +MODE="${1}"; shift +BANKID="${1}"; shift +ENDTIME="${1}"; shift +LASTWINDOW="${1}"; shift +MAXRETRIES="${1}"; shift +KEEPCONFIG="${1}"; shift +MSG="$*" + if [ "${MODE}" = "Immediately" ] || [ "${MODE}" = "AnyTime" ]; then - activate_and_reboot_device "${2}" "${7}" + activate_and_reboot_device "${BANKID}" "${KEEPCONFIG}" elif [ "${MODE}" = "WhenIdle" ]; then - handle_whenidle_mode "${2}" "${3}" "${4}" "${7}" + handle_whenidle_mode "${BANKID}" "${ENDTIME}" "${LASTWINDOW}" "${KEEPCONFIG}" elif [ "${MODE}" = "ConfirmationNeeded" ]; then - handle_confirmation_needed_mode "${2}" "${3}" "${4}" "${5}" "${6}" "${7}" + handle_confirmation_needed_mode "${BANKID}" "${ENDTIME}" "${LASTWINDOW}" "${MAXRETRIES}" "${KEEPCONFIG}" "${MSG}" else log "[${MODE}] mode is not supported" exit 1 diff --git a/src/fw_images.c b/src/fw_images.c index 38a50c4e32d22e70807a986b1f7dbf80218a5fe4..9444680321da7ba4086cc6233c2b027a2968482f 100644 --- a/src/fw_images.c +++ b/src/fw_images.c @@ -95,17 +95,6 @@ static char *get_fwbank_bank_id(const char *option_name) return bank_id ? bank_id : ""; } -static struct uci_section *is_varstate_section_exist(const char *package, const char *section) -{ - struct uci_section *s = NULL; - - uci_path_foreach_sections(varstate, package, section, s) { - return s; - } - - return NULL; -} - static void fwbank_copy_config(void) { char output[64] = {0}; @@ -120,12 +109,16 @@ static bool fwbank_set_bootbank(const char *bank_id) return !res ? true : false; } -static bool fwbank_upgrade(const char *path, const char *auto_activate, const char *bank_id, const char *keep_settings) +static bool fwbank_upgrade(const char *path, bool activate, const char *bank_id, const char *keep_settings) { json_object *json_obj = NULL; bool res = false; - dmubus_call_blocking("fwbank", "upgrade", UBUS_ARGS{{"path", path, String}, {"auto_activate", auto_activate, Boolean}, {"bank", bank_id, Integer}, {"keep_settings", keep_settings, Boolean}}, 4, &json_obj); + if (activate == false) { + dmubus_call_blocking("fwbank", "upgrade", UBUS_ARGS{{"path", path, String}, {"auto_activate", "0", Boolean}, {"bank", bank_id, Integer}, {"keep_settings", "0", Boolean}}, 4, &json_obj); + } else { + dmubus_call_blocking("fwbank", "upgrade", UBUS_ARGS{{"path", path, String}, {"auto_activate", "1", Boolean}, {"bank", bank_id, Integer}, {"keep_settings", keep_settings, Boolean}}, 4, &json_obj); + } if (json_obj) { char *result = dmjson_get_value(json_obj, 1, "result"); @@ -275,7 +268,7 @@ static int bbf_fw_image_download(struct ubus_context *ctx, const char *url, cons string_to_bool(auto_activate, &activate); // Apply Firmware Image - if (!fwbank_upgrade(fw_image_path, (activate) ? "1" : "0", bank_id, DM_STRLEN(keep) ? keep : "1")) { + if (!fwbank_upgrade(fw_image_path, activate, bank_id, DM_STRLEN(keep) ? keep : "1")) { res = 1; snprintf(fault_msg, sizeof(fault_msg), "Internal error occurred when applying the firmware"); goto end; @@ -582,14 +575,6 @@ static int operate_DeviceInfoFirmwareImage_Download(char *refparam, struct dmctx #ifdef SYSMNGR_VENDOR_EXTENSIONS keep_config = dmjson_get_value((json_object *)value, 1, CUSTOM_PREFIX"KeepConfig"); - - bool keep = DM_STRLEN(keep_config) ? dmuci_string_to_boolean(keep_config) : true; - - struct uci_section *s = is_varstate_section_exist("sysmngr", "globals"); - if (!s) dmuci_add_section_varstate("sysmngr", "globals", &s); - - dmuci_set_value_by_section_varstate(s, "keep_config", keep ? "1" : "0"); - dmuci_commit_package_varstate("sysmngr"); #endif char *bank_id = get_fwbank_option_value(data, "id"); @@ -609,6 +594,9 @@ static operation_args firmware_image_activate_args = { "TimeWindow.{i}.Mode", "TimeWindow.{i}.UserMessage", "TimeWindow.{i}.MaxRetries", +#ifdef SYSMNGR_VENDOR_EXTENSIONS + CUSTOM_PREFIX"KeepConfig", +#endif NULL } }; @@ -628,11 +616,17 @@ static int operate_DeviceInfoFirmwareImage_Activate(char *refparam, struct dmctx char *user_message[MAX_TIME_WINDOW] = {0}; char *max_retries[MAX_TIME_WINDOW] = {0}; char *keep_config = NULL; + bool bKeepConfig = false; int res = 0, last_idx = -1; - dmuci_get_option_value_string_varstate("sysmngr", "globals", "keep_config", &keep_config); +#ifdef SYSMNGR_VENDOR_EXTENSIONS + keep_config = dmjson_get_value((json_object *)value, 1, CUSTOM_PREFIX"KeepConfig"); +#endif + if (DM_STRLEN(keep_config) == 0) { - keep_config = dmstrdup("1"); + bKeepConfig = true; + } else { + string_to_bool(keep_config, &bKeepConfig); } for (int i = 0; i < MAX_TIME_WINDOW; i++) { @@ -697,20 +691,17 @@ static int operate_DeviceInfoFirmwareImage_Activate(char *refparam, struct dmctx long int start_t = (DM_STRTOL(start_time[i]) > 60) ? DM_STRTOL(start_time[i]) : 60; t_time += start_t; struct tm *tm_local = localtime(&t_time); + size_t len; - snprintf(buffer, sizeof(buffer), "%d %d %d %d * sh %s '%s' '%s' '%ld' '%d' '%s' '%s' '%s'\n", - tm_local->tm_min, - tm_local->tm_hour, - tm_local->tm_mday, - tm_local->tm_mon + 1, - ACTIVATE_HANDLER_FILE, - mode[i], - bank_id, - (DM_STRTOL(end_time[i]) - DM_STRTOL(start_time[i])), - (i == last_idx), - user_message[i], - max_retries[i], - keep_config); + snprintf(buffer, sizeof(buffer), "%d %d %d %d * sh %s", + tm_local->tm_min, tm_local->tm_hour, + tm_local->tm_mday, tm_local->tm_mon + 1, + ACTIVATE_HANDLER_FILE); + + len = strlen(buffer); + snprintf(buffer+len, sizeof(buffer)-len, " '%s' '%s' '%ld' '%d' '%s' '%d' '%s'\n", + mode[i], bank_id, (DM_STRTOL(end_time[i]) - DM_STRTOL(start_time[i])), + (i == last_idx), max_retries[i], bKeepConfig, user_message[i]); fprintf(file, "%s", buffer); } @@ -722,8 +713,9 @@ static int operate_DeviceInfoFirmwareImage_Activate(char *refparam, struct dmctx if (!fwbank_set_bootbank(bank_id)) return USP_FAULT_COMMAND_FAILURE; - if (DM_STRCMP(keep_config, "1") == 0) + if (bKeepConfig == true) { fwbank_copy_config(); + } bbfdm_task_fork(_exec_reboot, NULL, NULL, NULL); }