Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,279 changes: 1,217 additions & 62 deletions components/kaos/appmgr/src/appmgr_ao.c

Large diffs are not rendered by default.

49 changes: 43 additions & 6 deletions components/kaos/appmgr/src/appmgr_verbs.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "kaos.h"
#include "kapp_cat.h"
#include "kdrv_kfw.h"
#include "kode_log.h"
#include "kqp.h"

Expand All @@ -21,13 +22,15 @@
#define APPMGR_V_BYTES_PER_KB 1024U /**< `size_kb=`'s unit. */
#define APPMGR_V_ERASE_MARGIN_MS 5000U /**< What `app_erase` waits past AO_AppMgr's own bound on AO_Storage. */
#define APPMGR_V_ERASE_MS (APPMGR_WIPE_MS + APPMGR_V_ERASE_MARGIN_MS) /**< `app_erase`'s deadline. */
#define APPMGR_V_RUN_MS (KODE_APPMGR_OPEN_MS + APPMGR_V_ERASE_MARGIN_MS) /**< `app_run`'s: AO_AppMgr's bound on a medium's open, and the margin. */

/** @brief Which app verb holds the console. */
typedef enum {
APPMGR_V_NONE = 0, /**< None of them. */
APPMGR_V_LIST, /**< app_list: APP_ENTRY_SIG pages until the end. */
APPMGR_V_CAT, /**< app_cat set/clear: closed by APP_CAT_RSP_SIG. */
APPMGR_V_ERASE /**< app_erase: closed by APP_ERASE_DONE_SIG. */
APPMGR_V_ERASE, /**< app_erase: closed by APP_ERASE_DONE_SIG. */
APPMGR_V_RUN /**< app_run: closed by APP_REQ_RSP_SIG. */
} appmgr_verb_kind_t;

static appmgr_verb_kind_t s_appmgr_kind;
Expand Down Expand Up @@ -55,19 +58,36 @@ static void h_app_list(const kverb_t *c, const karg_val_t *v)
appmgr_list_request(AO_Console);
}

/** @brief app_run name=<name|file>: a .bin is flashed then booted. */
/**
* @brief app_run name=<name|file>: a .bin is flashed then booted.
*
* The line stays open until AO_AppMgr says whether it took the request: `>ok`
* when the app is on its way to boot, `missing` for a name nobody installed or
* a file that is not on a medium, `busy` when AO_AppMgr has work in hand.
*/
static void h_app_run(const kverb_t *c, const karg_val_t *v)
{
(void)c;
bool posted;

console_begin(c->verb);
console_extend(APPMGR_V_RUN_MS);
s_appmgr_kind = APPMGR_V_RUN;
if ( console_is_bin(v[0].s) )
{
appmgr_flash_request(v[0].s, true); /* flash, then boot it */
/* Flash, then boot it. */
posted = appmgr_image_ask((uint8_t)KFW_SRC_FS, v[0].s,
(uint8_t)KFW_TARGET_APP, true, AO_Console);
}
else
{
appmgr_run_request(v[0].s);
posted = appmgr_run_ask(v[0].s, AO_Console);
}
if ( !posted )
{
/* Nothing was posted, so nobody will answer. */
console_reply_err(KCLI_ERR_REJECT, c->verb);
console_end();
}
console_reply(">ok");
}

/**
Expand Down Expand Up @@ -363,6 +383,19 @@ static bool appmgr_erase_answer(QEvt const *e)
return true;
}

/** @brief app_run's answer: AO_AppMgr's verdict on the request. */
static bool appmgr_run_answer(QEvt const *e)
{
bool handled = false;

if ( (QSignal)APP_REQ_RSP_SIG == e->sig )
{
console_reply_app_verdict(Q_EVT_CAST(AppReqRspEvt)->verdict);
handled = true;
}
return handled;
}

/** @brief AO_AppMgr's answers to the verb in flight. */
static bool appmgr_answer(QEvt const *e)
{
Expand All @@ -380,6 +413,10 @@ static bool appmgr_answer(QEvt const *e)
{
handled = appmgr_erase_answer(e);
}
else if ( APPMGR_V_RUN == s_appmgr_kind )
{
handled = appmgr_run_answer(e);
}
else
{
/* None of them. */
Expand Down
73 changes: 68 additions & 5 deletions components/kaos/console/src/console_ao.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ KAOS_WEAK void storage_path_request(QActive *who, uint8_t op, const char *path);
KAOS_WEAK void storage_read_request(void);
KAOS_WEAK void storage_write_all_request(const char *name, const void *data,
uint32_t len, QActive *who);
KAOS_WEAK void appmgr_image_request(uint8_t src_kind, const char *src,
uint8_t dst_kind, bool launch);
KAOS_WEAK bool appmgr_image_ask(uint8_t src_kind, const char *src,
uint8_t dst_kind, bool launch, QActive *who);
KAOS_WEAK void appmgr_list_page(uint16_t skip, QActive *who);
KAOS_WEAK void appmgr_list_request(QActive *who);
KAOS_WEAK bool appmgr_recv_request(uint8_t dst, uint32_t size, uint32_t crc32,
Expand Down Expand Up @@ -286,6 +286,8 @@ typedef enum {
* APPDATA_END_SIG. */
KCLI_APPDATA_RM, /**< appdata_rm: closed by APPDATA_RM_RSP_SIG. */
KCLI_APPDATA_GET, /**< appdata_get: closed by APPDATA_GET_RSP_SIG. */
KCLI_INSTALL, /**< install off a medium or the mailbox: closed by
* AO_AppMgr's verdict, APP_REQ_RSP_SIG. */
KCLI_OWNER, /**< A registered verb: its owner's answer hook sees
* the events, and console_end() closes it. */
} kcli_inflight_t;
Expand Down Expand Up @@ -1778,14 +1780,59 @@ static void install_from_wire(Console *me, const kcmd_t *c, uint8_t dst,
}
}

/** How long `install` waits for AO_AppMgr's verdict: the deadline AO_AppMgr gives a
* medium to open a file, then the console's own. AO_AppMgr answers every
* request it takes, so this only bounds a lost answer. */
#define KCLI_INSTALL_MS (KODE_APPMGR_OPEN_MS + KCLI_CMD_TIMEOUT_MS)

/**
* @brief Close the command in flight with AO_AppMgr's verdict on its request.
*
* `>ok` for a request taken; code 4 `busy` when AO_AppMgr had work in hand;
* code 5 `missing` for a file that is not on the medium or an app that is not
* installed, `empty` for a mailbox with nothing in it, `refused` for anything
* else AO_AppMgr turned down.
*
* @param verdict app_req_verdict_t.
*/
void console_reply_app_verdict(uint8_t verdict)
{
switch ( (app_req_verdict_t)verdict )
{
case APP_REQ_TAKEN: {
console_reply(">ok");
break;
}
case APP_REQ_BUSY: {
console_reply_err(KCLI_ERR_BUSY, "busy");
break;
}
case APP_REQ_MISSING: {
console_reply_err(KCLI_ERR_REJECT, "missing");
break;
}
case APP_REQ_EMPTY: {
console_reply_err(KCLI_ERR_REJECT, "empty");
break;
}
default: {
console_reply_err(KCLI_ERR_REJECT, "refused");
break;
}
}
console_end();
}

/**
* @brief install src=<where from> dst=<where to> [run=on|off]
* [size=<n> crc32=<n>].
*
* `dst=` is `os`, `c5`, `pending` (the mailbox) or `app`, which boots once
* only on `run=on`; `run` is refused for any other `dst`. `src=` is
* `fs:name`, `sd:name`, `http(s)://...`, `"pending"` or `"terminal"`. Only
* `"terminal"` takes `size` and `crc32`, and only there is `>ok` a result.
* `"terminal"` takes `size` and `crc32`, and only there is `>ok` a result: off
* a medium or the mailbox it says AO_AppMgr took the install, and the
* verdict decides (console_reply_app_verdict()).
*/
static void h_install(Console *me, const kcmd_t *c, const karg_val_t *v)
{
Expand Down Expand Up @@ -1904,8 +1951,14 @@ static void h_install(Console *me, const kcmd_t *c, const karg_val_t *v)
console_reply(">ok");
return;
}
appmgr_image_request(kind, spec, dst, run);
console_reply(">ok");
cmd_begin(me, KCLI_INSTALL, c->verb);
console_extend(KCLI_INSTALL_MS);
if ( !appmgr_image_ask(kind, spec, dst, run, &me->super) )
{
/* Nothing was posted, so nobody will answer. */
console_reply_err(KCLI_ERR_REJECT, c->verb);
console_end();
}
}

/* ---- Handlers: the app data folder ---------------------------------------
Expand Down Expand Up @@ -7925,6 +7978,16 @@ static QState console_own(Console * const me, QEvt const * const e)
status = Q_HANDLED();
break;
}
case APP_REQ_RSP_SIG: {
/* Only the install in flight hears it: a verdict that arrives
* after the watchdog closed the command belongs to nobody. */
if ( (uint8_t)KCLI_INSTALL == me->inflight )
{
console_reply_app_verdict(Q_EVT_CAST(AppReqRspEvt)->verdict);
}
status = Q_HANDLED();
break;
}
case RECV_FAIL_SIG: {
if ( (uint8_t)KCLI_RECV == me->inflight )
{
Expand Down
7 changes: 7 additions & 0 deletions components/kaos/console/src/console_verbs.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ void console_reply(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
*/
void console_reply_err(kcli_err_t code, const char *msg);

/**
* @brief Close the command in flight with AO_AppMgr's verdict on its request.
* @param verdict app_req_verdict_t: `>ok` for taken; `busy` is code 4; `missing`,
* `empty` and `refused` are code 5.
*/
void console_reply_app_verdict(uint8_t verdict);

/**
* @brief `>ok` when the request reached its owner, else code 5 naming the verb.
* @param verb The verb.
Expand Down
78 changes: 76 additions & 2 deletions components/kaos/core/include/kaos.h
Original file line number Diff line number Diff line change
Expand Up @@ -3528,6 +3528,39 @@ kode_status_t appdata_put_request(const char *project, uint8_t *buf,
* is still making would be cut. */
#define APPMGR_TIDY_LATE_MS (5000U)

/* ---- What an install evicted, kept in NVS until the install ends --------------
* An install takes its victims out of the directory before the first byte
* arrives. AO_AppMgr records them first, in its own namespace, so a reset in
* the middle of the install does not cost them: the next boot puts back the
* ones whose bytes are as they were, which each record says by a CRC-32 of the
* image's extent taken when the victim was evicted. One blob, a header and one
* record per victim; absent when no install is evicting. A boot takes the table
* out of NVS before it checks anything.
*/
#define APPMGR_UNDO_NS "kappmgr" /**< The namespace AO_AppMgr keeps its records in. */
#define APPMGR_UNDO_KEY "undo" /**< The table of the victims of the install in hand. */
#define APPMGR_UNDO_VERSION 2U /**< The layout below; any other is ignored and erased. */
#define APPMGR_UNDO_MAX 64U /**< Records in a table: every slot of the directory. */
#define APPMGR_UNDO_HDR_LEN 4U /**< Bytes of appmgr_undo_hdr_t. */
#define APPMGR_UNDO_REC_LEN 52U /**< Bytes of appmgr_undo_rec_t. */

/** @brief The head of the blob. */
typedef struct {
uint8_t version; /**< APPMGR_UNDO_VERSION. */
uint8_t count; /**< Records that follow: 1..APPMGR_UNDO_MAX. */
uint8_t reserved[2]; /**< Zero. */
} appmgr_undo_hdr_t;

/** @brief One victim: its directory entry as it stood, field for field, its name and the CRC of its extent. */
typedef struct {
uint32_t offset; /**< Region-relative, a multiple of 64 KB. */
uint32_t size; /**< The image's exact length in bytes. */
uint32_t seq; /**< Its install number. */
uint32_t flags; /**< The entry's flags. */
char project[KODE_APP_PROJECT_MAX]; /**< Its name, read before the install could overwrite it. */
uint32_t crc; /**< khal_crc32() over [offset, offset + size), taken before the eviction. */
} appmgr_undo_rec_t;

/**
* @brief The folder's path on @p m, or false when the name cannot be a folder.
*
Expand Down Expand Up @@ -3592,8 +3625,10 @@ const uint8_t *net_stream_buf(uint8_t idx);

/* ---- AO_AppMgr: owner of the apps region --------------------------------
* Copies a .bin from a medium into the apps region (evicting the oldest install
* when short of room), lists, launches and removes apps by name. Which firmware
* boots is AO_Boot's; the two write different flash regions.
* when short of room), lists, launches and removes apps by name. An install that
* fails gives back every app it evicted whose flash its write never reached, and
* so does the next boot after a reset in the middle of one (the table below).
* Which firmware boots is AO_Boot's; the two write different flash regions.
*/
extern QActive * const AO_AppMgr; /**< The apps-region AO. */
void AppMgr_ctor(void); /**< Construct it; kaos_appmgr_start() calls it. */
Expand Down Expand Up @@ -3700,6 +3735,41 @@ bool appmgr_recv_request(uint8_t dst, uint32_t size, uint32_t crc32,
void appmgr_image_request(uint8_t src_kind, const char *src,
uint8_t dst_kind, bool launch);

/**
* @brief appmgr_image_request(), asking AO_AppMgr for its verdict.
*
* AO_AppMgr posts @p who one APP_REQ_RSP_SIG (AppReqRspEvt) once it has
* decided: taken (an `https` source as soon as it starts opening, a file on a
* medium once the medium has opened it, the mailbox at once), busy, missing (no
* such file on the medium), empty (nothing in the mailbox) or refused. What
* goes wrong after that, a download that fails, is published as
* APP_FLASH_FAIL_SIG and says nothing here.
*
* @param src_kind kfw_src_t as a plain integer.
* @param src File name for fs/sd, URL for http, ignored otherwise.
* @param dst_kind kfw_target_t as a plain integer.
* @param launch Boot the image once it is written and verified.
* @param who Where the verdict is posted.
* @return false when the pool was dry and nothing was posted: no verdict
* will come.
*/
bool appmgr_image_ask(uint8_t src_kind, const char *src, uint8_t dst_kind,
bool launch, QActive *who);

/**
* @brief appmgr_run_request(), asking AO_AppMgr for its verdict.
*
* AO_AppMgr posts @p who one APP_REQ_RSP_SIG: taken when an installed app has
* that name (the boot request is on its way to AO_Boot), missing when none
* does, busy when it has work in hand.
*
* @param name The installed app's project name.
* @param who Where the verdict is posted.
* @return false when the pool was dry and nothing was posted: no verdict
* will come.
*/
bool appmgr_run_ask(const char *name, QActive *who);

/**
* @brief The event appmgr_image_request() posts, built for a caller that posts it itself.
* @param src_kind kfw_src_t as a plain integer.
Expand Down Expand Up @@ -3828,6 +3898,10 @@ typedef struct {
* to @p who, refusals included: AO_AppMgr does one thing at a time and answers
* APPDATA_GET_BUSY while it writes an image. Any task or AO.
*
* A fetch that began shows like a write: APP_FLASH_PROGRESS_SIG at 0 % at once
* and again every second while it waits on the network, ended by an unnamed
* APP_FLASH_DONE_SIG or by APP_FLASH_FAIL_SIG.
*
* @param project The app's project name, as its image declares it.
* @param url The base URL, ending where the file names begin; shorter than
* KODE_APPDATA_URL_MAX.
Expand Down
37 changes: 37 additions & 0 deletions components/kdrv/apps/include/kdrv_apps.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@ kode_status_t kdrv_apps_write_end(void);
/** @brief Abandon the session. Whatever was written stays, unverified. */
void kdrv_apps_write_abort(void);

/**
* @brief Has the write erased any part of an extent?
*
* Answers for the write session in hand, or the last one that ran, and starts
* over with every committed directory: a commit says which extents hold apps,
* so an erase made before it speaks for nothing that comes after. An erase that
* failed counts as having reached its block, because flash a failed erase
* touched is not flash that can be trusted. Only blocks the write got to count:
* the region erases 64 KB at a time, as the data arrives.
*
* @param offset Region-relative start of the extent.
* @param size Its length in bytes.
* @return true when some of its bytes may no longer be what they were; false
* when every one of them is, or when @p size is 0.
*/
bool kdrv_apps_write_reached(uint32_t offset, uint32_t size);

/**
* @brief Read @p len bytes of the region at region-relative @p offset.
*
Expand All @@ -130,6 +147,26 @@ void kdrv_apps_write_abort(void);
*/
kode_status_t kdrv_apps_read(uint32_t offset, void *buf, size_t len);

/**
* @brief Fold the bytes of an extent of the region into a running CRC-32.
*
* Reads exactly @p size bytes from @p offset, in pieces, and never one more:
* what lies beyond the extent is not this call's to touch. The CRC is the
* one khal_crc32() gives, chainable, so an extent may be taken in parts: pass
* KHAL_CRC32_INIT in @p crc for the first and the result for the next. It
* does not parse the data. An image a reset half overwrote is another
* image's header and segments, and what a parser makes of them is not bounded
* by @p size; a digest is.
*
* @param offset Region-relative start, at or past KAPPS_DATA_OFF.
* @param size How many bytes; 0 reads nothing and changes nothing.
* @param crc The running CRC on entry and, on KODE_OK, the updated one;
* left alone on any other answer.
* @return KODE_OK; KODE_DENIED with no region or a NULL @p crc; KODE_ERROR for
* an extent that leaves the data area or one that cannot be mapped.
*/
kode_status_t kdrv_apps_extent_crc(uint32_t offset, uint32_t size, uint32_t *crc);

/** @brief What kdrv_apps_sink() needs: where kdrv_apps_plan() said to write. */
typedef struct {
uint32_t offset; /**< Region-relative, 64 KB-aligned. */
Expand Down
Loading
Loading