Single bank OTA

This commit is contained in:
cpq committed 2023-11-02 09:30:55 +00:00
1 parent d6d2d7575a
commit 376e5ccaae
26 files changed
+2061 -1804

No files matched your search

+10
View File
@@ -255,6 +255,14 @@ static void handle_device_reset(struct mg_connection *c) {
mg_timer_add(c->mgr, 500, 0, (void (*)(void *)) mg_device_reset, NULL); mg_timer_add(c->mgr, 500, 0, (void (*)(void *)) mg_device_reset, NULL);
} }
static void handle_device_eraselast(struct mg_connection *c) {
size_t ss = mg_flash_sector_size(), size = mg_flash_size();
char *base = (char *) mg_flash_start(), *last = base + size - ss;
if (mg_flash_bank() == 2) last -= size / 2;
mg_flash_erase(last);
mg_http_reply(c, 200, s_json_header, "true\n");
}
// HTTP request handler function // HTTP request handler function
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_ACCEPT) { if (ev == MG_EV_ACCEPT) {
@@ -294,6 +302,8 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
handle_firmware_status(c); handle_firmware_status(c);
} else if (mg_http_match_uri(hm, "/api/device/reset")) { } else if (mg_http_match_uri(hm, "/api/device/reset")) {
handle_device_reset(c); handle_device_reset(c);
} else if (mg_http_match_uri(hm, "/api/device/eraselast")) {
handle_device_eraselast(c);
} else { } else {
struct mg_http_serve_opts opts; struct mg_http_serve_opts opts;
memset(&opts, 0, sizeof(opts)); memset(&opts, 0, sizeof(opts));
File diff suppressed because it is too large. Load diff
@@ -42,6 +42,7 @@ export const Icons = {
chip: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 002.25-2.25V6.75a2.25 2.25 0 00-2.25-2.25H6.75A2.25 2.25 0 004.5 6.75v10.5a2.25 2.25 0 002.25 2.25zm.75-12h9v9h-9v-9z" /> </svg>`, chip: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 002.25-2.25V6.75a2.25 2.25 0 00-2.25-2.25H6.75A2.25 2.25 0 004.5 6.75v10.5a2.25 2.25 0 002.25 2.25zm.75-12h9v9h-9v-9z" /> </svg>`,
camera: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 015.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 00-1.134-.175 2.31 2.31 0 01-1.64-1.055l-.822-1.316a2.192 2.192 0 00-1.736-1.039 48.774 48.774 0 00-5.232 0 2.192 2.192 0 00-1.736 1.039l-.821 1.316z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zM18.75 10.5h.008v.008h-.008V10.5z" /> </svg>`, camera: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 015.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 00-1.134-.175 2.31 2.31 0 01-1.64-1.055l-.822-1.316a2.192 2.192 0 00-1.736-1.039 48.774 48.774 0 00-5.232 0 2.192 2.192 0 00-1.736 1.039l-.821 1.316z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zM18.75 10.5h.008v.008h-.008V10.5z" /> </svg>`,
arrows: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" /> </svg>`, arrows: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" /> </svg>`,
doc: props => html`<svg class=${props.class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>`,
}; };
export const tipColors = { export const tipColors = {
@@ -157,7 +158,7 @@ export function Colored({icon, text, colors}) {
export function Stat({title, text, tipText, tipIcon, tipColors, colors}) { export function Stat({title, text, tipText, tipIcon, tipColors, colors}) {
return html` return html`
<div class="flex flex-col bg-white border shadow-sm rounded-xl dark:bg-slate-900 dark:border-gray-800"> <div class="flex flex-col bg-white border shadow-sm rounded-xl dark:bg-slate-900 dark:border-gray-800">
<div class="overflow-auto rounded-lg bg-white px-4 py-2 shadow"> <div class="overflow-auto rounded-lg bg-white px-4 py-2 ">
<div class="flex items-center gap-x-2"> <div class="flex items-center gap-x-2">
<p class="text-sm truncate text-gray-500 font-medium"> ${title} </p> <p class="text-sm truncate text-gray-500 font-medium"> ${title} </p>
<//> <//>
@@ -173,27 +174,34 @@ export function Stat({title, text, tipText, tipIcon, tipColors, colors}) {
<//>`; <//>`;
}; };
export function TextValue({value, setfn, disabled, placeholder, type, addonRight, addonLeft, attr, min, max, step}) { export function TextValue({value, setfn, disabled, placeholder, type, addonRight, addonLeft, attr, min, max, step, mult}) {
const [bg, setBg] = useState('bg-white'); const [bg, setBg] = useState('bg-white');
useEffect(() => { checkval(value); }, []); useEffect(() => { if (type == 'number') checkval(+min, +max, +value); }, []);
const checkval = function(v) { step ||= '1', mult ||= 1;
const checkval = function(min, max, v) {
setBg('bg-white'); setBg('bg-white');
if (min && v < min) setBg('bg-red-100 border-red-200'); if (min && v < min) setBg('bg-red-100 border-red-200');
if (max && v > max) setBg('bg-red-100 border-red-200'); if (max && v > max) setBg('bg-red-100 border-red-200');
}; };
const oninput = ev => { const m = step.match(/^.+\.(.+)/);
const digits = m ? m[1].length : 0;
const onchange = ev => {
let v = ev.target.value; let v = ev.target.value;
if (type == 'number') v = parseInt(v); if (type == 'number') {
checkval(+min, +max, +v);
v = +(parseFloat(v) / mult).toFixed(digits);
}
setfn(v); setfn(v);
checkval(v);
}; };
if (type == 'number') value = +(value * mult).toFixed(digits);
return html` return html`
<div class="flex w-full items-center rounded border shadow-sm ${bg}"> <div class="flex w-full items-center rounded border shadow-sm ${bg}">
${ addonLeft && html`<span class="inline-flex font-normal truncate py-1 border-r bg-slate-100 items-center border-gray-300 px-2 text-gray-500 text-xs">${addonLeft}<//>` } ${addonLeft && html`<span class="inline-flex font-normal truncate py-1 border-r bg-slate-100 items-center border-gray-300 px-2 text-gray-500 text-xs">${addonLeft}<//>` }
<input type=${type || 'text'} disabled=${disabled} <input type=${type || 'text'} disabled=${disabled} value=${value}
oninput=${oninput} ...${attr} step=${step} min=${min} max=${max}
class="${bg} font-normal text-sm rounded w-full flex-1 py-0.5 px-2 text-gray-700 placeholder:text-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:bg-gray-100 disabled:text-gray-500" placeholder=${placeholder} value=${value} /> onchange=${onchange} ...${attr}
${ addonRight && html`<span class="inline-flex font-normal truncate py-1 border-l bg-slate-100 items-center border-gray-300 px-2 text-gray-500 text-xs overflow-scroll" style="min-width: 50%;">${addonRight}<//>` } class="${bg} font-normal text-sm rounded w-full flex-1 py-0.5 px-2 text-gray-700 placeholder:text-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:bg-gray-100 disabled:text-gray-500" placeholder=${placeholder} />
${addonRight && html`<span class="inline-flex font-normal truncate py-1 border-l bg-slate-100 items-center border-gray-300 px-2 text-gray-500 text-xs overflow-scroll" style="min-width: 50%;">${addonRight}<//>` }
<//>`; <//>`;
}; };
@@ -344,6 +352,6 @@ export function UploadFileButton(props) {
<div class="inline-flex flex-col ${props.class}"> <div class="inline-flex flex-col ${props.class}">
<input class="hidden" type="file" ref=${input} onchange=${onchange} accept=${props.accept} /> <input class="hidden" type="file" ref=${input} onchange=${onchange} accept=${props.accept} />
<${Button} title=${props.title} icon=${Icons.download} onclick=${onclick} ref=${btn} colors=${props.colors} /> <${Button} title=${props.title} icon=${Icons.download} onclick=${onclick} ref=${btn} colors=${props.colors} />
<div class="py-2 text-sm text-slate-400 ${status || 'hidden'}">${status}<//> <div class="pt-2 text-sm text-slate-400 ${status || 'hidden'}">${status}<//>
<//>`; <//>`;
}; };
+1 -1
View File
@@ -1 +1 @@
/*! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter var,Helvetica,sans-serif;font-feature-settings:"cv11","ss01";font-variation-settings:"opsz" 32}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.left-0{left:0}.right-4{right:1rem}.right-auto{right:auto}.top-0{top:0}.top-4{top:1rem}.isolate{isolation:isolate}.z-10{z-index:10}.z-\[48\]{z-index:48}.z-\[60\]{z-index:60}.col-span-2{grid-column:span 2/span 2}.m-4{margin:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-0{margin-top:0;margin-bottom:0}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-5{margin-top:1.25rem;margin-bottom:1.25rem}.mb-1{margin-bottom:.25rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mt-1{margin-top:.25rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-7{margin-top:1.75rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-0{width:0}.w-11{width:2.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-72{width:18rem}.w-96{width:24rem}.w-full{width:100%}.min-w-full{min-width:100%}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow-0{flex-grow:0}.basis-\[30px\]{flex-basis:30px}.border-separate{border-collapse:initialLine truncated /*! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter var,Helvetica,sans-serif;font-feature-settings:"cv11","ss01";font-variation-settings:"opsz" 32}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.left-0{left:0}.right-auto{right:auto}.top-0{top:0}.isolate{isolation:isolate}.z-10{z-index:10}.z-\[48\]{z-index:48}.z-\[60\]{z-index:60}.col-span-2{grid-column:span 2/span 2}.m-4{margin:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-0{margin-top:0;margin-bottom:0}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-5{margin-top:1.25rem;margin-bottom:1.25rem}.mb-1{margin-bottom:.25rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mt-1{margin-top:.25rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-7{margin-top:1.75rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-0{width:0}.w-11{width:2.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-72{width:18rem}.w-96{width:24rem}.w-full{width:100%}.min-w-full{min-width:100%}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-\[30px\]{flex-basis:30px}.border-separate{border-collapse:initial}.border-spacing-0Line truncated
+27 -14
View File
@@ -216,36 +216,49 @@ function FirmwareUpdate({}) {
.then(r => new Promise(r => setTimeout(ev => { refresh(); r(); }, 3000))); .then(r => new Promise(r => setTimeout(ev => { refresh(); r(); }, 3000)));
const onrollback = ev => fetch('api/firmware/rollback') const onrollback = ev => fetch('api/firmware/rollback')
.then(onreboot); .then(onreboot);
const onerase = ev => fetch('api/device/eraselast').then(refresh);
const onupload = function(ok, name, size) { const onupload = function(ok, name, size) {
if (!ok) return false; if (!ok) return false;
return new Promise(r => setTimeout(ev => { refresh(); r(); }, 3000)); return new Promise(r => setTimeout(ev => { refresh(); r(); }, 3000));
}; };
const clean = info[0].valid && info[0].golden == 0;
return html` return html`
<div class="m-4 gap-4 grid grid-cols-1 lg:grid-cols-2"> <div class="m-4 gap-4 grid grid-cols-1 lg:grid-cols-3">
<${FirmwareStatus} title="Current firmware image" info=${info[0]}> <${FirmwareStatus} title="Current firmware image" info=${info[0]}>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<${Button} title="Commit this firmware" <${Button} title="Commit this firmware" onclick=${oncommit}
onclick=${oncommit} icon=${Icons.thumbUp} disabled=${clean} /> icon=${Icons.thumbUp} disabled=${info[0].status == 3} cls="w-full" />
<${Button} title="Reboot device" onclick=${onreboot} icon=${Icons.refresh} clsx="absolute top-4 right-4" />
<${UploadFileButton}
title="Upload new firmware: choose .bin file:" onupload=${onupload}
url="api/firmware/upload" accept=".bin,.uf2" />
<//> <//>
<//> <//>
<${FirmwareStatus} title="Previous firmware image" info=${info[1]}> <${FirmwareStatus} title="Previous firmware image" info=${info[1]}>
<${Button} title="Rollback to this firmware" onclick=${onrollback} <${Button} title="Rollback to this firmware" onclick=${onrollback}
icon=${Icons.backward} disabled=${info[1].valid == false} /> icon=${Icons.backward} disabled=${info[1].status == 0} cls="w-full" />
<//> <//>
<div class="bg-white xm-4 divide-y border rounded flex flex-col">
<div class="font-light uppercase flex items-center text-gray-600 px-4 py-2">
Device control
<//>
<div class="px-4 py-3 flex flex-col gap-2 grow">
<${UploadFileButton}
title="Upload new firmware .bin file" onupload=${onupload}
url="api/firmware/upload" accept=".bin,.uf2" />
<div class="grow"><//>
<${Button} title="Reboot device" onclick=${onreboot} icon=${Icons.refresh} cls="w-full" />
<${Button} title="Erase last sector" onclick=${onerase} icon=${Icons.doc} cls="w-full hidden" />
<//>
<//>
<//>
<div class="m-4 gap-4 grid grid-cols-1 lg:grid-cols-2">
<div class="bg-white border shadow-lg"> <div class="bg-white border shadow-lg">
<${DeveloperNote}> <${DeveloperNote}>
<div class="my-2"> <div class="my-2">
When a new firmware gets flashed, its status is marked as, "first_boot". Firmware status and other information is stored in the last sector
That is an unreliable (uncommitted) firmware. A user may choose of flash
to revert back to the previous committed firmware on the subsequent <//>
boots. Clicking on the "commit" button calls "mg_ota_commit()" function <div class="my-2">
which commits the firmware. Firmware status can be FIRST_BOOT, UNCOMMITTED or COMMITTED. If no
information is available, it is UNAVAILABLE.
<//> <//>
<div class="my-2"> <div class="my-2">
This GUI loads a firmware file and sends it chunk by chunk to the This GUI loads a firmware file and sends it chunk by chunk to the
@@ -42,23 +42,21 @@ int main(void) {
mg_mgr_init(&mgr); // Mongoose event manager mg_mgr_init(&mgr); // Mongoose event manager
mg_log_set(MG_LL_DEBUG); // Set log level mg_log_set(MG_LL_DEBUG); // Set log level
#if MG_OTA == MG_OTA_FLASH mg_ota_boot(); // Call bootloader: continue to load, or boot another FW
// If we don't have any OTA info saved, e.g. we're pre-flashed, then
// call mg_ota_commit() to mark this firmware as reliable
if (mg_ota_status(MG_FIRMWARE_CURRENT) == MG_OTA_UNAVAILABLE) mg_ota_commit();
// Demonstrate the use of mg_flash_{load/save} functions for keeping device #if MG_OTA == MG_OTA_FLASH
// Demonstrate the use of mg_flash_{load/save} functions for storing device
// configuration data on flash. Increment boot count on every boot. // configuration data on flash. Increment boot count on every boot.
struct deviceconfig { struct deviceconfig {
uint32_t boot_count; uint32_t boot_count;
int some_other_data; char some_other_data[40];
}; };
uint32_t key = 0x12345678; // A unique key, one per data type uint32_t key = 0x12345678; // A unique key, one per data type
struct deviceconfig dc = {}; // Initialise to some default values struct deviceconfig dc = {}; // Initialise to some default values
mg_flash_load(NULL, key, &dc, sizeof(dc)); // Load from flash mg_flash_load(NULL, key, &dc, sizeof(dc)); // Load from flash
dc.boot_count++; // Increment boot count dc.boot_count++; // Increment boot count
mg_flash_save(NULL, key, &dc, sizeof(dc)); // And save back mg_flash_save(NULL, key, &dc, sizeof(dc)); // And save back
MG_INFO(("Boot count: %u", dc.boot_count)); MG_INFO(("Boot count: %u", dc.boot_count)); // Print boot count
#endif #endif
// Initialise Mongoose network stack // Initialise Mongoose network stack
@@ -24,6 +24,7 @@ firmware.bin: firmware.elf
firmware.elf: cmsis_core cmsis_h7 $(SOURCES) hal.h link.ld Makefile firmware.elf: cmsis_core cmsis_h7 $(SOURCES) hal.h link.ld Makefile
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@ arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
arm-none-eabi-size $@
flash: firmware.bin flash: firmware.bin
st-flash --reset write $< 0x8000000 st-flash --reset write $< 0x8000000
@@ -8,6 +8,16 @@
#pragma once #pragma once
#define BTN PIN('C', 13) // User push button
#define LED1 PIN('B', 0) // On-board LED pin (green)
#define LED2 PIN('E', 1) // On-board LED pin (yellow)
#define LED3 PIN('B', 14) // On-board LED pin (red)
#define LED LED2 // Use yellow LED for blinking
#ifndef UART_DEBUG
#define UART_DEBUG USART3
#endif
#include <stm32h723xx.h> #include <stm32h723xx.h>
#include <stdbool.h> #include <stdbool.h>
@@ -21,12 +31,6 @@
#define PINNO(pin) (pin & 255) #define PINNO(pin) (pin & 255)
#define PINBANK(pin) (pin >> 8) #define PINBANK(pin) (pin >> 8)
#define LED1 PIN('B', 0) // On-board LED pin (green)
#define LED2 PIN('E', 1) // On-board LED pin (yellow)
#define LED3 PIN('B', 14) // On-board LED pin (red)
#define LED LED2 // Use yellow LED for blinking
// System clock (2.1, Figure 1; 8.5, Figure 45; 8.5.5, Figure 47; 8.5.6, Figure // System clock (2.1, Figure 1; 8.5, Figure 45; 8.5.5, Figure 47; 8.5.6, Figure
// 49; 8.5.8 Table 56; datasheet) CPU_FREQUENCY <= 550 MHz; hclk = CPU_FREQUENCY // 49; 8.5.8 Table 56; datasheet) CPU_FREQUENCY <= 550 MHz; hclk = CPU_FREQUENCY
// / HPRE ; hclk <= 275 MHz; APB clocks <= 137.5 MHz. D1 domain bus matrix (and // / HPRE ; hclk <= 275 MHz; APB clocks <= 137.5 MHz. D1 domain bus matrix (and
@@ -96,10 +100,6 @@ static inline void gpio_output(uint16_t pin) {
GPIO_PULL_NONE, 0); GPIO_PULL_NONE, 0);
} }
#ifndef UART_DEBUG
#define UART_DEBUG USART3
#endif
// D2 Kernel clock (8.7.21) USART1 defaults to pclk2 (APB2), while USART2,3 // D2 Kernel clock (8.7.21) USART1 defaults to pclk2 (APB2), while USART2,3
// default to pclk1 (APB1). Even if using other kernel clocks, the APBx clocks // default to pclk1 (APB1). Even if using other kernel clocks, the APBx clocks
// must be enabled for CPU access, as the kernel clock drives the BRR, not the // must be enabled for CPU access, as the kernel clock drives the BRR, not the
@@ -16,19 +16,15 @@ SECTIONS {
.rodata : { *(.rodata*) } > flash .rodata : { *(.rodata*) } > flash
.data : { .data : {
_sdata = .; /* for init_ram() */ _sdata = .;
*(.first_data) *(.first_data)
*(.data SORT(.data.*)) *(.data SORT(.data.*))
_edata = .; /* for init_ram() */ *(.iram .iram* .iram.*)
_edata = .;
} > sram AT > flash } > sram AT > flash
_sidata = LOADADDR(.data); _sidata = LOADADDR(.data);
.bss : { .bss : { _sbss = .; *(.bss SORT(.bss.*) COMMON) _ebss = .; } > sram
_sbss = .; /* for init_ram() */
*(.bss SORT(.bss.*) COMMON)
_ebss = .; /* for init_ram() */
} > sram
. = ALIGN(8); . = ALIGN(8);
_end = .; /* for cmsis_gcc.h and init_ram() */ _end = .;
} }
@@ -43,9 +43,22 @@ int main(void) {
mg_mgr_init(&mgr); // Mongoose event manager mg_mgr_init(&mgr); // Mongoose event manager
mg_log_set(MG_LL_DEBUG); // Set log level mg_log_set(MG_LL_DEBUG); // Set log level
// If we don't have any OTA info saved, e.g. we're pre-flashed, then mg_ota_boot(); // Call bootloader: continue to load, or boot another FW
// call mg_ota_commit() to mark this firmware as reliable
if (mg_ota_status(MG_FIRMWARE_CURRENT) == MG_OTA_UNAVAILABLE) mg_ota_commit(); #if MG_OTA == MG_OTA_FLASH
// Demonstrate the use of mg_flash_{load/save} functions for keeping device
// configuration data on flash. Increment boot count on every boot.
struct deviceconfig {
uint32_t boot_count;
char some_other_data[40];
};
uint32_t key = 0x12345678; // A unique key, one per data type
struct deviceconfig dc = {}; // Initialise to some default values
mg_flash_load(NULL, key, &dc, sizeof(dc)); // Load from flash
dc.boot_count++; // Increment boot count
mg_flash_save(NULL, key, &dc, sizeof(dc)); // And save back
MG_INFO(("Boot count: %u", dc.boot_count));
#endif
// Initialise Mongoose network stack // Initialise Mongoose network stack
struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4}; struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4};
@@ -1,6 +1,8 @@
#pragma once #pragma once
#define MG_ARCH MG_ARCH_NEWLIB #define MG_ARCH MG_ARCH_NEWLIB
#define MG_OTA MG_OTA_FLASH
#define MG_DEVICE MG_DEVICE_STM32H7
#define MG_ENABLE_TCPIP 1 #define MG_ENABLE_TCPIP 1
#define MG_ENABLE_CUSTOM_MILLIS 1 #define MG_ENABLE_CUSTOM_MILLIS 1
@@ -45,9 +45,22 @@ int main(void) {
mg_mgr_init(&mgr); // Mongoose event manager mg_mgr_init(&mgr); // Mongoose event manager
mg_log_set(MG_LL_DEBUG); // Set log level mg_log_set(MG_LL_DEBUG); // Set log level
// If we don't have any OTA info saved, e.g. we're pre-flashed, then mg_ota_boot(); // Call bootloader: continue to load, or boot another FW
// call mg_ota_commit() to mark this firmware as reliable
if (mg_ota_status(MG_FIRMWARE_CURRENT) == MG_OTA_UNAVAILABLE) mg_ota_commit(); #if MG_OTA == MG_OTA_FLASH
// Demonstrate the use of mg_flash_{load/save} functions for keeping device
// configuration data on flash. Increment boot count on every boot.
struct deviceconfig {
uint32_t boot_count;
char some_other_data[40];
};
uint32_t key = 0x12345678; // A unique key, one per data type
struct deviceconfig dc = {}; // Initialise to some default values
mg_flash_load(NULL, key, &dc, sizeof(dc)); // Load from flash
dc.boot_count++; // Increment boot count
mg_flash_save(NULL, key, &dc, sizeof(dc)); // And save back
MG_INFO(("Boot count: %u", dc.boot_count));
#endif
// Initialise Mongoose network stack // Initialise Mongoose network stack
struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4}; struct mg_tcpip_driver_stm32h_data driver_data = {.mdc_cr = 4};
+168 -90
View File
@@ -211,26 +211,6 @@ bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) {
return ok; return ok;
} }
static bool mg_flash_writev(char *location, struct mg_str *strings, size_t n) {
size_t align = mg_flash_write_align(), i, j, k = 0, nwritten = 0;
char buf[align];
bool ok = true;
for (i = 0; ok && i < n; i++) {
for (j = 0; ok && j < strings[i].len; j++) {
buf[k++] = strings[i].ptr[j];
if (k >= sizeof(buf)) {
ok = mg_flash_write(location + nwritten, buf, sizeof(buf));
k = 0, nwritten += sizeof(buf);
}
}
}
if (k > 0) {
while (k < sizeof(buf)) buf[k++] = 0xff;
ok = mg_flash_write(location + nwritten, buf, sizeof(buf));
}
return ok;
}
// For all saved objects in the sector, delete old versions of objects // For all saved objects in the sector, delete old versions of objects
static void mg_flash_sector_cleanup(char *sector) { static void mg_flash_sector_cleanup(char *sector) {
// Buffer all saved objects into an IO buffer (backed by RAM) // Buffer all saved objects into an IO buffer (backed by RAM)
@@ -277,8 +257,10 @@ bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) {
} else if (((s - base) % ss) != 0) { } else if (((s - base) % ss) != 0) {
MG_ERROR(("%p is not a sector boundary", sector)); MG_ERROR(("%p is not a sector boundary", sector));
} else { } else {
size_t needed = sizeof(uint32_t) * 2 + len; char ab[mg_flash_write_align()]; // Aligned write block
size_t needed_aligned = MG_ROUND_UP(needed, mg_flash_write_align()); uint32_t hdr[2] = {(uint32_t) len, key};
size_t needed = sizeof(hdr) + len;
size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab));
while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n;
// If there is not enough space left, cleanup sector and re-eval ofs // If there is not enough space left, cleanup sector and re-eval ofs
@@ -290,11 +272,39 @@ bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) {
if (ofs + needed_aligned <= ss) { if (ofs + needed_aligned <= ss) {
// Enough space to save this object // Enough space to save this object
uint32_t hdr[2] = {(uint32_t) len, key}; if (sizeof(ab) < sizeof(hdr)) {
struct mg_str data[] = {mg_str_n((char *) hdr, sizeof(hdr)), // Flash write granularity is 32 bit or less, write with no buffering
mg_str_n(buf, len)}; ok = mg_flash_write(s + ofs, hdr, sizeof(hdr));
ok = mg_flash_writev(s + ofs, data, 2); if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len);
MG_DEBUG(("Saving %lu bytes @ %p, key %x: %d", len, s + ofs, key, ok)); } else {
// Flash granularity is sizeof(hdr) or more. We need to save in
// 3 chunks: initial block, bulk, rest. This is because we have
// two memory chunks to write: hdr and buf, on aligned boundaries.
n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write
if (n > len) n = len; // is
memset(ab, 0xff, sizeof(ab)); // initialized to all-one
memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size)
memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf
MG_INFO(("saving initial block of %lu", sizeof(ab)));
ok = mg_flash_write(s + ofs, ab, sizeof(ab));
if (ok && len > n) {
size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab));
if (n2 > 0) {
MG_INFO(("saving bulk, %lu", n2));
ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2);
}
if (ok && len > n) {
size_t n3 = len - n - n2;
if (n3 > sizeof(ab)) n3 = sizeof(ab);
memset(ab, 0xff, sizeof(ab));
memcpy(ab, (char *) buf + n + n2, n3);
MG_INFO(("saving rest, %lu", n3));
ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab));
}
}
}
MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned,
s + ofs, key, ok));
MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned)); MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned));
} else { } else {
MG_ERROR(("Sector is full")); MG_ERROR(("Sector is full"));
@@ -391,6 +401,7 @@ bool mg_flash_erase(void *location) {
} else { } else {
uintptr_t diff = (char *) location - (char *) mg_flash_start(); uintptr_t diff = (char *) location - (char *) mg_flash_start();
uint32_t sector = diff / mg_flash_sector_size(); uint32_t sector = diff / mg_flash_sector_size();
uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value
flash_unlock(); flash_unlock();
flash_clear_err(); flash_clear_err();
MG_REG(FLASH_NSCR) = 0; MG_REG(FLASH_NSCR) = 0;
@@ -406,6 +417,7 @@ bool mg_flash_erase(void *location) {
MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location,
ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR)));
// mg_hexdump(location, 32); // mg_hexdump(location, 32);
MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR
} }
return ok; return ok;
} }
@@ -435,21 +447,18 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) {
flash_clear_err(); flash_clear_err();
MG_ARM_DISABLE_IRQ(); MG_ARM_DISABLE_IRQ();
// MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr));
MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag
while (ok && src < end) { while (ok && src < end) {
if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag
*(volatile uint32_t *) dst++ = *src++; *(volatile uint32_t *) dst++ = *src++;
flash_wait(); flash_wait();
if (flash_is_err()) ok = false; if (flash_is_err()) ok = false;
} }
MG_ARM_ENABLE_IRQ();
MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR), flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR),
MG_REG(FLASH_NSSR))); MG_REG(FLASH_NSSR)));
if (flash_is_err()) ok = false;
// mg_hexdump(addr, len > 32 ? 32 : len);
// MG_REG(FLASH_NSCR) &= ~MG_BIT(1); // Set programming flag
MG_REG(FLASH_NSCR) = 0; // Clear flags MG_REG(FLASH_NSCR) = 0; // Clear flags
MG_ARM_ENABLE_IRQ();
return ok; return ok;
} }
@@ -477,66 +486,71 @@ void mg_device_reset(void) {
#define FLASH_CCR 0x14 #define FLASH_CCR 0x14
#define FLASH_OPTSR_CUR 0x1c #define FLASH_OPTSR_CUR 0x1c
#define FLASH_OPTSR_PRG 0x20 #define FLASH_OPTSR_PRG 0x20
#define FLASH_SIZE_REG 0x1ff1e880
void *mg_flash_start(void) { MG_IRAM void *mg_flash_start(void) {
return (void *) 0x08000000; return (void *) 0x08000000;
} }
size_t mg_flash_size(void) { MG_IRAM size_t mg_flash_size(void) {
return 2 * 1024 * 1024; // 2Mb return MG_REG(FLASH_SIZE_REG) * 1024;
} }
size_t mg_flash_sector_size(void) { MG_IRAM size_t mg_flash_sector_size(void) {
return 128 * 1024; // 128k return 128 * 1024; // 128k
} }
size_t mg_flash_write_align(void) { MG_IRAM size_t mg_flash_write_align(void) {
return 32; // 256 bit return 32; // 256 bit
} }
int mg_flash_bank(void) { MG_IRAM int mg_flash_bank(void) {
if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual bank support
return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
} }
static void flash_unlock(void) { MG_IRAM static void flash_unlock(void) {
static bool unlocked = false; static bool unlocked = false;
if (unlocked == false) { if (unlocked == false) {
MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123;
MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab;
MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; if (mg_flash_bank() > 0) {
MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123;
MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab;
}
MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared" MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared"
MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once
unlocked = true; unlocked = true;
} }
} }
static bool flash_page_start(volatile uint32_t *dst) { MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
volatile char *p = (char *) dst; volatile char *p = (char *) dst;
return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0;
} }
static bool flash_is_err(uint32_t bank) { MG_IRAM static bool flash_is_err(uint32_t bank) {
return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5 return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5
} }
static void flash_wait(uint32_t bank) { MG_IRAM static void flash_wait(uint32_t bank) {
while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0;
} }
static void flash_clear_err(uint32_t bank) { MG_IRAM static void flash_clear_err(uint32_t bank) {
flash_wait(bank); // Wait until ready flash_wait(bank); // Wait until ready
MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors
} }
static bool flash_bank_is_swapped(uint32_t bank) { MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) {
return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7
} }
// Figure out flash bank based on the address // Figure out flash bank based on the address
static uint32_t flash_bank(void *addr) { MG_IRAM static uint32_t flash_bank(void *addr) {
size_t ofs = (char *) addr - (char *) mg_flash_start(); size_t ofs = (char *) addr - (char *) mg_flash_start();
if (mg_flash_bank() == 0) return FLASH_BASE1;
return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2; return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2;
} }
bool mg_flash_erase(void *addr) { MG_IRAM bool mg_flash_erase(void *addr) {
bool ok = false; bool ok = false;
if (flash_page_start(addr) == false) { if (flash_page_start(addr) == false) {
MG_ERROR(("%p is not on a sector boundary", addr)); MG_ERROR(("%p is not on a sector boundary", addr));
@@ -544,12 +558,13 @@ bool mg_flash_erase(void *addr) {
uintptr_t diff = (char *) addr - (char *) mg_flash_start(); uintptr_t diff = (char *) addr - (char *) mg_flash_start();
uint32_t sector = diff / mg_flash_sector_size(); uint32_t sector = diff / mg_flash_sector_size();
uint32_t bank = flash_bank(addr); uint32_t bank = flash_bank(addr);
uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value
flash_unlock(); flash_unlock();
if (sector > 7) sector -= 8; if (sector > 7) sector -= 8;
// MG_INFO(("Erasing @ %p, sector %lu, bank %#x", addr, sector, bank));
flash_clear_err(bank); flash_clear_err(bank);
MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism
MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase
MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit
MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing
@@ -557,12 +572,13 @@ bool mg_flash_erase(void *addr) {
MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr,
ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
MG_REG(bank + FLASH_SR))); MG_REG(bank + FLASH_SR)));
// mg_hexdump(addr, 32); MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR
} }
return ok; return ok;
} }
bool mg_flash_swap_bank() { MG_IRAM bool mg_flash_swap_bank() {
if (mg_flash_bank() == 0) return true;
uint32_t bank = FLASH_BASE1; uint32_t bank = FLASH_BASE1;
uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31);
flash_unlock(); flash_unlock();
@@ -575,7 +591,7 @@ bool mg_flash_swap_bank() {
return true; return true;
} }
bool mg_flash_write(void *addr, const void *buf, size_t len) { MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) {
if ((len % mg_flash_write_align()) != 0) { if ((len % mg_flash_write_align()) != 0) {
MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
return false; return false;
@@ -587,25 +603,25 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) {
bool ok = true; bool ok = true;
flash_unlock(); flash_unlock();
flash_clear_err(bank); flash_clear_err(bank);
MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag
MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism
MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len));
MG_ARM_DISABLE_IRQ(); MG_ARM_DISABLE_IRQ();
MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag
// MG_INFO(("Writing flash @ %p, %lu bytes", addr, len));
while (ok && src < end) { while (ok && src < end) {
if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
*(volatile uint32_t *) dst++ = *src++; *(volatile uint32_t *) dst++ = *src++;
flash_wait(bank); flash_wait(bank);
if (flash_is_err(bank)) ok = false; if (flash_is_err(bank)) ok = false;
} }
MG_ARM_ENABLE_IRQ();
MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
MG_REG(bank + FLASH_SR))); MG_REG(bank + FLASH_SR)));
// mg_hexdump(addr, len > 32 ? 32 : len);
MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag
MG_ARM_ENABLE_IRQ();
return ok; return ok;
} }
void mg_device_reset(void) { MG_IRAM void mg_device_reset(void) {
// SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
} }
@@ -3243,7 +3259,7 @@ long mg_json_get_long(struct mg_str json, const char *path, long dflt) {
static int s_level = MG_LL_INFO; int mg_log_level = MG_LL_INFO;
static mg_pfn_t s_log_func = mg_pfn_stdout; static mg_pfn_t s_log_func = mg_pfn_stdout;
static void *s_log_func_param = NULL; static void *s_log_func_param = NULL;
@@ -3261,29 +3277,19 @@ static void logs(const char *buf, size_t len) {
for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]); for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
} }
void mg_log_set(int log_level) {
MG_DEBUG(("Setting log level to %d", log_level));
s_level = log_level;
}
#if MG_ENABLE_CUSTOM_LOG #if MG_ENABLE_CUSTOM_LOG
// Let user define their own mg_log_prefix() and mg_log() // Let user define their own mg_log_prefix() and mg_log()
#else #else
bool mg_log_prefix(int level, const char *file, int line, const char *fname) { void mg_log_prefix(int level, const char *file, int line, const char *fname) {
if (level <= s_level) { const char *p = strrchr(file, '/');
const char *p = strrchr(file, '/'); char buf[41];
char buf[41]; size_t n;
size_t n; if (p == NULL) p = strrchr(file, '\\');
if (p == NULL) p = strrchr(file, '\\'); n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level,
n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level, p == NULL ? file : p + 1, line, fname);
p == NULL ? file : p + 1, line, fname); if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
if (n > sizeof(buf) - 2) n = sizeof(buf) - 2; while (n < sizeof(buf)) buf[n++] = ' ';
while (n < sizeof(buf)) buf[n++] = ' '; logs(buf, n - 1);
logs(buf, n - 1);
return true;
} else {
return false;
}
} }
void mg_log(const char *fmt, ...) { void mg_log(const char *fmt, ...) {
@@ -5492,6 +5498,8 @@ size_t mg_ota_size(int fw) {
(void) fw; (void) fw;
return 0; return 0;
} }
MG_IRAM void mg_ota_boot(void) {
}
#endif #endif
#ifdef MG_ENABLE_LINES #ifdef MG_ENABLE_LINES
@@ -5526,7 +5534,7 @@ bool mg_ota_begin(size_t new_firmware_size) {
size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size(); size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size();
s_crc32 = 0; s_crc32 = 0;
s_addr = (char *) mg_flash_start() + half; s_addr = (char *) mg_flash_start() + half;
MG_DEBUG(("Firmware %lu bytes, max %lu", s_size, max)); MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max));
if (new_firmware_size < max) { if (new_firmware_size < max) {
ok = true; ok = true;
s_size = new_firmware_size; s_size = new_firmware_size;
@@ -5560,6 +5568,14 @@ bool mg_ota_write(const void *buf, size_t len) {
return ok; return ok;
} }
MG_IRAM static uint32_t mg_fwkey(int fw) {
uint32_t key = MG_OTADATA_KEY + fw;
int bank = mg_flash_bank();
if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--;
if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++;
return key;
}
bool mg_ota_end(void) { bool mg_ota_end(void) {
char *base = (char *) mg_flash_start() + mg_flash_size() / 2; char *base = (char *) mg_flash_start() + mg_flash_size() / 2;
bool ok = false; bool ok = false;
@@ -5569,7 +5585,7 @@ bool mg_ota_end(void) {
if (size == s_size && crc32 == s_crc32) { if (size == s_size && crc32 == s_crc32) {
uint32_t now = (uint32_t) (mg_now() / 1000); uint32_t now = (uint32_t) (mg_now() / 1000);
struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT}; struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT};
uint32_t key = MG_OTADATA_KEY + (mg_flash_bank() == 2 ? 1 : 2); uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS);
ok = mg_flash_save(NULL, key, &od, sizeof(od)); ok = mg_flash_save(NULL, key, &od, sizeof(od));
} }
MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
@@ -5581,12 +5597,10 @@ bool mg_ota_end(void) {
return ok; return ok;
} }
static struct mg_otadata mg_otadata(int fw) { MG_IRAM static struct mg_otadata mg_otadata(int fw) {
uint32_t key = mg_fwkey(fw);
struct mg_otadata od = {}; struct mg_otadata od = {};
int bank = mg_flash_bank(); MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : "prev"));
uint32_t key = MG_OTADATA_KEY + 1;
if ((fw == MG_FIRMWARE_CURRENT && bank == 2)) key++;
if ((fw == MG_FIRMWARE_PREVIOUS && bank == 1)) key++;
mg_flash_load(NULL, key, &od, sizeof(od)); mg_flash_load(NULL, key, &od, sizeof(od));
// MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key)); // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key));
// mg_hexdump(&od, sizeof(od)); // mg_hexdump(&od, sizeof(od));
@@ -5610,16 +5624,80 @@ size_t mg_ota_size(int fw) {
return od.size; return od.size;
} }
bool mg_ota_commit(void) { MG_IRAM bool mg_ota_commit(void) {
bool ok = true;
struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT); struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT);
od.status = MG_OTA_COMMITTED; if (od.status != MG_OTA_COMMITTED) {
uint32_t key = MG_OTADATA_KEY + mg_flash_bank(); od.status = MG_OTA_COMMITTED;
return mg_flash_save(NULL, key, &od, sizeof(od)); MG_INFO(("Committing current firmware, OD size %lu", sizeof(od)));
ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, sizeof(od));
}
return ok;
} }
bool mg_ota_rollback(void) { bool mg_ota_rollback(void) {
MG_DEBUG(("Rolling firmware back")); MG_DEBUG(("Rolling firmware back"));
return mg_flash_swap_bank(); if (mg_flash_bank() == 0) {
// No dual bank support. Mark previous firmware as FIRST_BOOT
struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
prev.status = MG_OTA_FIRST_BOOT;
return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &prev,
sizeof(prev));
} else {
return mg_flash_swap_bank();
}
}
MG_IRAM void mg_ota_boot(void) {
MG_INFO(("Booting. Flash bank: %d", mg_flash_bank()));
struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT);
struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
if (curr.status == MG_OTA_FIRST_BOOT) {
curr.status = MG_OTA_UNCOMMITTED;
MG_INFO(("First boot, setting status to UNCOMMITTED"));
mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &curr,
sizeof(curr));
} else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) {
// Swap paritions. Pray power does not disappear
size_t fs = mg_flash_size(), ss = mg_flash_sector_size();
char *partition1 = mg_flash_start();
char *partition2 = mg_flash_start() + fs / 2;
size_t ofs, max = fs / 2 - ss; // Set swap size to the whole partition
if (curr.status != MG_OTA_UNAVAILABLE &&
prev.status != MG_OTA_UNAVAILABLE) {
// We know exact sizes of both firmwares.
// Shrink swap size to the MAX(firmware1, firmware2)
size_t sz = curr.size > prev.size ? curr.size : prev.size;
if (sz > 0 && sz < max) max = sz;
}
// MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED
prev.status = MG_OTA_UNCOMMITTED;
mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev,
sizeof(prev));
mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr,
sizeof(curr));
MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss));
MG_INFO(("Do NOT power off..."));
mg_log_level = MG_LL_NONE;
// We use the last sector of partition2 for OTA data/config storage
// Therefore we can use last sector of partition1 for swapping
char *tmpsector = partition1 + fs / 2 - ss; // Last sector of partition1
(void) tmpsector;
for (ofs = 0; ofs < max; ofs += ss) {
// mg_flash_erase(tmpsector);
mg_flash_write(tmpsector, partition1 + ofs, ss);
// mg_flash_erase(partition1 + ofs);
mg_flash_write(partition1 + ofs, partition2 + ofs, ss);
// mg_flash_erase(partition2 + ofs);
mg_flash_write(partition2 + ofs, tmpsector, ss);
}
mg_device_reset();
}
} }
#endif #endif
+40 -25
View File
@@ -27,21 +27,21 @@ extern "C" {
#endif #endif
#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_custom.h #define MG_ARCH_CUSTOM 0 // User creates its own mongoose_custom.h
#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ... #define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
#define MG_ARCH_WIN32 2 // Windows #define MG_ARCH_WIN32 2 // Windows
#define MG_ARCH_ESP32 3 // ESP32 #define MG_ARCH_ESP32 3 // ESP32
#define MG_ARCH_ESP8266 4 // ESP8266 #define MG_ARCH_ESP8266 4 // ESP8266
#define MG_ARCH_FREERTOS 5 // FreeRTOS #define MG_ARCH_FREERTOS 5 // FreeRTOS
#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS #define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS #define MG_ARCH_ZEPHYR 7 // Zephyr RTOS
#define MG_ARCH_NEWLIB 8 // Bare metal ARM #define MG_ARCH_NEWLIB 8 // Bare metal ARM
#define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX) #define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX)
#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS #define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS
#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040 #define MG_ARCH_RP2040 11 // Raspberry Pi RP2040
#define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard #define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard
#define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5, FreeRTOS) #define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5, FreeRTOS)
#define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS #define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS
#if !defined(MG_ARCH) #if !defined(MG_ARCH)
#if defined(__unix__) || defined(__APPLE__) #if defined(__unix__) || defined(__APPLE__)
@@ -497,6 +497,7 @@ typedef int socklen_t;
#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX) #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
#define sleep(x) Sleep((x) *1000) #define sleep(x) Sleep((x) *1000)
#define mkdir(a, b) _mkdir(a) #define mkdir(a, b) _mkdir(a)
#define timegm(x) _mkgmtime(x)
#ifndef S_ISDIR #ifndef S_ISDIR
#define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR) #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
@@ -919,16 +920,23 @@ void mg_pfn_stdout(char c, void *param); // param: ignored
enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE }; enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE };
extern int mg_log_level; // Current log level, one of MG_LL_*
void mg_log(const char *fmt, ...); void mg_log(const char *fmt, ...);
bool mg_log_prefix(int ll, const char *file, int line, const char *fname); void mg_log_prefix(int ll, const char *file, int line, const char *fname);
void mg_log_set(int log_level); // bool mg_log2(int ll, const char *file, int line, const char *fmt, ...);
void mg_hexdump(const void *buf, size_t len); void mg_hexdump(const void *buf, size_t len);
void mg_log_set_fn(mg_pfn_t fn, void *param); void mg_log_set_fn(mg_pfn_t fn, void *param);
#define mg_log_set(level_) mg_log_level = (level_)
#if MG_ENABLE_LOG #if MG_ENABLE_LOG
#define MG_LOG(level, args) \ #define MG_LOG(level, args) \
do { \ do { \
if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) mg_log args; \ if ((level) <= mg_log_level) { \
mg_log_prefix((level), __FILE__, __LINE__, __func__); \
mg_log args; \
} \
} while (0) } while (0)
#else #else
#define MG_LOG(level, args) \ #define MG_LOG(level, args) \
@@ -1053,8 +1061,8 @@ uint64_t mg_now(void); // Return milliseconds since Epoch
#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a))) #define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a)))
#ifdef __GNUC__ #ifdef __GNUC__
#define MG_ARM_DISABLE_IRQ() asm volatile ("cpsid i" : : : "memory") #define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory")
#define MG_ARM_ENABLE_IRQ() asm volatile ("cpsie i" : : : "memory") #define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory")
#else #else
#define MG_ARM_DISABLE_IRQ() #define MG_ARM_DISABLE_IRQ()
#define MG_ARM_ENABLE_IRQ() #define MG_ARM_ENABLE_IRQ()
@@ -1678,6 +1686,12 @@ void mg_rpc_list(struct mg_rpc_req *r);
#define MG_OTA MG_OTA_NONE #define MG_OTA MG_OTA_NONE
#endif #endif
#if defined(__GNUC__) && !defined(__APPLE__)
#define MG_IRAM __attribute__((section(".iram")))
#else
#define MG_IRAM
#endif
// Firmware update API // Firmware update API
bool mg_ota_begin(size_t new_firmware_size); // Start writing bool mg_ota_begin(size_t new_firmware_size); // Start writing
bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k
@@ -1696,8 +1710,9 @@ uint32_t mg_ota_crc32(int firmware); // Return firmware checksum
uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch
size_t mg_ota_size(int firmware); // Firmware size size_t mg_ota_size(int firmware); // Firmware size
bool mg_ota_commit(void); // Commit current firmware bool mg_ota_commit(void); // Commit current firmware
bool mg_ota_rollback(void); // Rollback to the previous firmware bool mg_ota_rollback(void); // Rollback to the previous firmware
MG_IRAM void mg_ota_boot(void); // Bootloader function
// Copyright (c) 2023 Cesanta Software Limited // Copyright (c) 2023 Cesanta Software Limited
// All rights reserved // All rights reserved
@@ -1723,7 +1738,7 @@ int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2
// Write, erase, swap bank // Write, erase, swap bank
bool mg_flash_write(void *addr, const void *buf, size_t len); bool mg_flash_write(void *addr, const void *buf, size_t len);
bool mg_flash_erase(void *addr); // Must be at sector boundary bool mg_flash_erase(void *sector);
bool mg_flash_swap_bank(void); bool mg_flash_swap_bank(void);
// Convenience functions to store data on a flash sector with wear levelling // Convenience functions to store data on a flash sector with wear levelling
+15 -15
View File
@@ -1,20 +1,20 @@
#pragma once #pragma once
#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_custom.h #define MG_ARCH_CUSTOM 0 // User creates its own mongoose_custom.h
#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ... #define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
#define MG_ARCH_WIN32 2 // Windows #define MG_ARCH_WIN32 2 // Windows
#define MG_ARCH_ESP32 3 // ESP32 #define MG_ARCH_ESP32 3 // ESP32
#define MG_ARCH_ESP8266 4 // ESP8266 #define MG_ARCH_ESP8266 4 // ESP8266
#define MG_ARCH_FREERTOS 5 // FreeRTOS #define MG_ARCH_FREERTOS 5 // FreeRTOS
#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS #define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS #define MG_ARCH_ZEPHYR 7 // Zephyr RTOS
#define MG_ARCH_NEWLIB 8 // Bare metal ARM #define MG_ARCH_NEWLIB 8 // Bare metal ARM
#define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX) #define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX)
#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS #define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS
#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040 #define MG_ARCH_RP2040 11 // Raspberry Pi RP2040
#define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard #define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard
#define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5, FreeRTOS) #define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5, FreeRTOS)
#define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS #define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS
#if !defined(MG_ARCH) #if !defined(MG_ARCH)
#if defined(__unix__) || defined(__APPLE__) #if defined(__unix__) || defined(__APPLE__)
+1
View File
@@ -96,6 +96,7 @@ typedef int socklen_t;
#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX) #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
#define sleep(x) Sleep((x) *1000) #define sleep(x) Sleep((x) *1000)
#define mkdir(a, b) _mkdir(a) #define mkdir(a, b) _mkdir(a)
#define timegm(x) _mkgmtime(x)
#ifndef S_ISDIR #ifndef S_ISDIR
#define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR) #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
+1 -1
View File
@@ -23,7 +23,7 @@ int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2
// Write, erase, swap bank // Write, erase, swap bank
bool mg_flash_write(void *addr, const void *buf, size_t len); bool mg_flash_write(void *addr, const void *buf, size_t len);
bool mg_flash_erase(void *addr); // Must be at sector boundary bool mg_flash_erase(void *sector);
bool mg_flash_swap_bank(void); bool mg_flash_swap_bank(void);
// Convenience functions to store data on a flash sector with wear levelling // Convenience functions to store data on a flash sector with wear levelling
+37 -27
View File
@@ -58,26 +58,6 @@ bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) {
return ok; return ok;
} }
static bool mg_flash_writev(char *location, struct mg_str *strings, size_t n) {
size_t align = mg_flash_write_align(), i, j, k = 0, nwritten = 0;
char buf[align];
bool ok = true;
for (i = 0; ok && i < n; i++) {
for (j = 0; ok && j < strings[i].len; j++) {
buf[k++] = strings[i].ptr[j];
if (k >= sizeof(buf)) {
ok = mg_flash_write(location + nwritten, buf, sizeof(buf));
k = 0, nwritten += sizeof(buf);
}
}
}
if (k > 0) {
while (k < sizeof(buf)) buf[k++] = 0xff;
ok = mg_flash_write(location + nwritten, buf, sizeof(buf));
}
return ok;
}
// For all saved objects in the sector, delete old versions of objects // For all saved objects in the sector, delete old versions of objects
static void mg_flash_sector_cleanup(char *sector) { static void mg_flash_sector_cleanup(char *sector) {
// Buffer all saved objects into an IO buffer (backed by RAM) // Buffer all saved objects into an IO buffer (backed by RAM)
@@ -124,8 +104,10 @@ bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) {
} else if (((s - base) % ss) != 0) { } else if (((s - base) % ss) != 0) {
MG_ERROR(("%p is not a sector boundary", sector)); MG_ERROR(("%p is not a sector boundary", sector));
} else { } else {
size_t needed = sizeof(uint32_t) * 2 + len; char ab[mg_flash_write_align()]; // Aligned write block
size_t needed_aligned = MG_ROUND_UP(needed, mg_flash_write_align()); uint32_t hdr[2] = {(uint32_t) len, key};
size_t needed = sizeof(hdr) + len;
size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab));
while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n;
// If there is not enough space left, cleanup sector and re-eval ofs // If there is not enough space left, cleanup sector and re-eval ofs
@@ -137,11 +119,39 @@ bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) {
if (ofs + needed_aligned <= ss) { if (ofs + needed_aligned <= ss) {
// Enough space to save this object // Enough space to save this object
uint32_t hdr[2] = {(uint32_t) len, key}; if (sizeof(ab) < sizeof(hdr)) {
struct mg_str data[] = {mg_str_n((char *) hdr, sizeof(hdr)), // Flash write granularity is 32 bit or less, write with no buffering
mg_str_n(buf, len)}; ok = mg_flash_write(s + ofs, hdr, sizeof(hdr));
ok = mg_flash_writev(s + ofs, data, 2); if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len);
MG_DEBUG(("Saving %lu bytes @ %p, key %x: %d", len, s + ofs, key, ok)); } else {
// Flash granularity is sizeof(hdr) or more. We need to save in
// 3 chunks: initial block, bulk, rest. This is because we have
// two memory chunks to write: hdr and buf, on aligned boundaries.
n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write
if (n > len) n = len; // is
memset(ab, 0xff, sizeof(ab)); // initialized to all-one
memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size)
memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf
MG_INFO(("saving initial block of %lu", sizeof(ab)));
ok = mg_flash_write(s + ofs, ab, sizeof(ab));
if (ok && len > n) {
size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab));
if (n2 > 0) {
MG_INFO(("saving bulk, %lu", n2));
ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2);
}
if (ok && len > n) {
size_t n3 = len - n - n2;
if (n3 > sizeof(ab)) n3 = sizeof(ab);
memset(ab, 0xff, sizeof(ab));
memcpy(ab, (char *) buf + n + n2, n3);
MG_INFO(("saving rest, %lu", n3));
ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab));
}
}
}
MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned,
s + ofs, key, ok));
MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned)); MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned));
} else { } else {
MG_ERROR(("Sector is full")); MG_ERROR(("Sector is full"));
+5 -6
View File
@@ -1,5 +1,5 @@
#include "log.h"
#include "device.h" #include "device.h"
#include "log.h"
#if MG_DEVICE == MG_DEVICE_STM32H5 #if MG_DEVICE == MG_DEVICE_STM32H5
@@ -73,6 +73,7 @@ bool mg_flash_erase(void *location) {
} else { } else {
uintptr_t diff = (char *) location - (char *) mg_flash_start(); uintptr_t diff = (char *) location - (char *) mg_flash_start();
uint32_t sector = diff / mg_flash_sector_size(); uint32_t sector = diff / mg_flash_sector_size();
uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value
flash_unlock(); flash_unlock();
flash_clear_err(); flash_clear_err();
MG_REG(FLASH_NSCR) = 0; MG_REG(FLASH_NSCR) = 0;
@@ -88,6 +89,7 @@ bool mg_flash_erase(void *location) {
MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location,
ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR)));
// mg_hexdump(location, 32); // mg_hexdump(location, 32);
MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR
} }
return ok; return ok;
} }
@@ -117,21 +119,18 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) {
flash_clear_err(); flash_clear_err();
MG_ARM_DISABLE_IRQ(); MG_ARM_DISABLE_IRQ();
// MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr));
MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag
while (ok && src < end) { while (ok && src < end) {
if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag
*(volatile uint32_t *) dst++ = *src++; *(volatile uint32_t *) dst++ = *src++;
flash_wait(); flash_wait();
if (flash_is_err()) ok = false; if (flash_is_err()) ok = false;
} }
MG_ARM_ENABLE_IRQ();
MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR), flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR),
MG_REG(FLASH_NSSR))); MG_REG(FLASH_NSSR)));
if (flash_is_err()) ok = false;
// mg_hexdump(addr, len > 32 ? 32 : len);
// MG_REG(FLASH_NSCR) &= ~MG_BIT(1); // Set programming flag
MG_REG(FLASH_NSCR) = 0; // Clear flags MG_REG(FLASH_NSCR) = 0; // Clear flags
MG_ARM_ENABLE_IRQ();
return ok; return ok;
} }
+33 -26
View File
@@ -1,5 +1,5 @@
#include "log.h"
#include "device.h" #include "device.h"
#include "log.h"
#if MG_DEVICE == MG_DEVICE_STM32H7 #if MG_DEVICE == MG_DEVICE_STM32H7
@@ -13,66 +13,71 @@
#define FLASH_CCR 0x14 #define FLASH_CCR 0x14
#define FLASH_OPTSR_CUR 0x1c #define FLASH_OPTSR_CUR 0x1c
#define FLASH_OPTSR_PRG 0x20 #define FLASH_OPTSR_PRG 0x20
#define FLASH_SIZE_REG 0x1ff1e880
void *mg_flash_start(void) { MG_IRAM void *mg_flash_start(void) {
return (void *) 0x08000000; return (void *) 0x08000000;
} }
size_t mg_flash_size(void) { MG_IRAM size_t mg_flash_size(void) {
return 2 * 1024 * 1024; // 2Mb return MG_REG(FLASH_SIZE_REG) * 1024;
} }
size_t mg_flash_sector_size(void) { MG_IRAM size_t mg_flash_sector_size(void) {
return 128 * 1024; // 128k return 128 * 1024; // 128k
} }
size_t mg_flash_write_align(void) { MG_IRAM size_t mg_flash_write_align(void) {
return 32; // 256 bit return 32; // 256 bit
} }
int mg_flash_bank(void) { MG_IRAM int mg_flash_bank(void) {
if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual bank support
return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
} }
static void flash_unlock(void) { MG_IRAM static void flash_unlock(void) {
static bool unlocked = false; static bool unlocked = false;
if (unlocked == false) { if (unlocked == false) {
MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123;
MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab;
MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; if (mg_flash_bank() > 0) {
MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123;
MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab;
}
MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared" MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared"
MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once
unlocked = true; unlocked = true;
} }
} }
static bool flash_page_start(volatile uint32_t *dst) { MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
volatile char *p = (char *) dst; volatile char *p = (char *) dst;
return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0;
} }
static bool flash_is_err(uint32_t bank) { MG_IRAM static bool flash_is_err(uint32_t bank) {
return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5 return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5
} }
static void flash_wait(uint32_t bank) { MG_IRAM static void flash_wait(uint32_t bank) {
while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0;
} }
static void flash_clear_err(uint32_t bank) { MG_IRAM static void flash_clear_err(uint32_t bank) {
flash_wait(bank); // Wait until ready flash_wait(bank); // Wait until ready
MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors
} }
static bool flash_bank_is_swapped(uint32_t bank) { MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) {
return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7
} }
// Figure out flash bank based on the address // Figure out flash bank based on the address
static uint32_t flash_bank(void *addr) { MG_IRAM static uint32_t flash_bank(void *addr) {
size_t ofs = (char *) addr - (char *) mg_flash_start(); size_t ofs = (char *) addr - (char *) mg_flash_start();
if (mg_flash_bank() == 0) return FLASH_BASE1;
return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2; return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2;
} }
bool mg_flash_erase(void *addr) { MG_IRAM bool mg_flash_erase(void *addr) {
bool ok = false; bool ok = false;
if (flash_page_start(addr) == false) { if (flash_page_start(addr) == false) {
MG_ERROR(("%p is not on a sector boundary", addr)); MG_ERROR(("%p is not on a sector boundary", addr));
@@ -80,12 +85,13 @@ bool mg_flash_erase(void *addr) {
uintptr_t diff = (char *) addr - (char *) mg_flash_start(); uintptr_t diff = (char *) addr - (char *) mg_flash_start();
uint32_t sector = diff / mg_flash_sector_size(); uint32_t sector = diff / mg_flash_sector_size();
uint32_t bank = flash_bank(addr); uint32_t bank = flash_bank(addr);
uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value
flash_unlock(); flash_unlock();
if (sector > 7) sector -= 8; if (sector > 7) sector -= 8;
// MG_INFO(("Erasing @ %p, sector %lu, bank %#x", addr, sector, bank));
flash_clear_err(bank); flash_clear_err(bank);
MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism
MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase
MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit
MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing
@@ -93,12 +99,13 @@ bool mg_flash_erase(void *addr) {
MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr,
ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
MG_REG(bank + FLASH_SR))); MG_REG(bank + FLASH_SR)));
// mg_hexdump(addr, 32); MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR
} }
return ok; return ok;
} }
bool mg_flash_swap_bank() { MG_IRAM bool mg_flash_swap_bank() {
if (mg_flash_bank() == 0) return true;
uint32_t bank = FLASH_BASE1; uint32_t bank = FLASH_BASE1;
uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31);
flash_unlock(); flash_unlock();
@@ -111,7 +118,7 @@ bool mg_flash_swap_bank() {
return true; return true;
} }
bool mg_flash_write(void *addr, const void *buf, size_t len) { MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) {
if ((len % mg_flash_write_align()) != 0) { if ((len % mg_flash_write_align()) != 0) {
MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
return false; return false;
@@ -123,25 +130,25 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) {
bool ok = true; bool ok = true;
flash_unlock(); flash_unlock();
flash_clear_err(bank); flash_clear_err(bank);
MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag
MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism
MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len));
MG_ARM_DISABLE_IRQ(); MG_ARM_DISABLE_IRQ();
MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag
// MG_INFO(("Writing flash @ %p, %lu bytes", addr, len));
while (ok && src < end) { while (ok && src < end) {
if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
*(volatile uint32_t *) dst++ = *src++; *(volatile uint32_t *) dst++ = *src++;
flash_wait(bank); flash_wait(bank);
if (flash_is_err(bank)) ok = false; if (flash_is_err(bank)) ok = false;
} }
MG_ARM_ENABLE_IRQ();
MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
MG_REG(bank + FLASH_SR))); MG_REG(bank + FLASH_SR)));
// mg_hexdump(addr, len > 32 ? 32 : len);
MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag
MG_ARM_ENABLE_IRQ();
return ok; return ok;
} }
void mg_device_reset(void) { MG_IRAM void mg_device_reset(void) {
// SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
} }
+11 -21
View File
@@ -3,7 +3,7 @@
#include "str.h" #include "str.h"
#include "util.h" #include "util.h"
static int s_level = MG_LL_INFO; int mg_log_level = MG_LL_INFO;
static mg_pfn_t s_log_func = mg_pfn_stdout; static mg_pfn_t s_log_func = mg_pfn_stdout;
static void *s_log_func_param = NULL; static void *s_log_func_param = NULL;
@@ -21,29 +21,19 @@ static void logs(const char *buf, size_t len) {
for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]); for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
} }
void mg_log_set(int log_level) {
MG_DEBUG(("Setting log level to %d", log_level));
s_level = log_level;
}
#if MG_ENABLE_CUSTOM_LOG #if MG_ENABLE_CUSTOM_LOG
// Let user define their own mg_log_prefix() and mg_log() // Let user define their own mg_log_prefix() and mg_log()
#else #else
bool mg_log_prefix(int level, const char *file, int line, const char *fname) { void mg_log_prefix(int level, const char *file, int line, const char *fname) {
if (level <= s_level) { const char *p = strrchr(file, '/');
const char *p = strrchr(file, '/'); char buf[41];
char buf[41]; size_t n;
size_t n; if (p == NULL) p = strrchr(file, '\\');
if (p == NULL) p = strrchr(file, '\\'); n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level,
n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level, p == NULL ? file : p + 1, line, fname);
p == NULL ? file : p + 1, line, fname); if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
if (n > sizeof(buf) - 2) n = sizeof(buf) - 2; while (n < sizeof(buf)) buf[n++] = ' ';
while (n < sizeof(buf)) buf[n++] = ' '; logs(buf, n - 1);
logs(buf, n - 1);
return true;
} else {
return false;
}
} }
void mg_log(const char *fmt, ...) { void mg_log(const char *fmt, ...) {
+12 -5
View File
@@ -5,16 +5,23 @@
#include "fmt.h" #include "fmt.h"
enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE }; enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE };
extern int mg_log_level; // Current log level, one of MG_LL_*
void mg_log(const char *fmt, ...); void mg_log(const char *fmt, ...);
bool mg_log_prefix(int ll, const char *file, int line, const char *fname); void mg_log_prefix(int ll, const char *file, int line, const char *fname);
void mg_log_set(int log_level); // bool mg_log2(int ll, const char *file, int line, const char *fmt, ...);
void mg_hexdump(const void *buf, size_t len); void mg_hexdump(const void *buf, size_t len);
void mg_log_set_fn(mg_pfn_t fn, void *param); void mg_log_set_fn(mg_pfn_t fn, void *param);
#define mg_log_set(level_) mg_log_level = (level_)
#if MG_ENABLE_LOG #if MG_ENABLE_LOG
#define MG_LOG(level, args) \ #define MG_LOG(level, args) \
do { \ do { \
if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) mg_log args; \ if ((level) <= mg_log_level) { \
mg_log_prefix((level), __FILE__, __LINE__, __func__); \
mg_log args; \
} \
} while (0) } while (0)
#else #else
#define MG_LOG(level, args) \ #define MG_LOG(level, args) \
+9 -2
View File
@@ -13,6 +13,12 @@
#define MG_OTA MG_OTA_NONE #define MG_OTA MG_OTA_NONE
#endif #endif
#if defined(__GNUC__) && !defined(__APPLE__)
#define MG_IRAM __attribute__((section(".iram")))
#else
#define MG_IRAM
#endif
// Firmware update API // Firmware update API
bool mg_ota_begin(size_t new_firmware_size); // Start writing bool mg_ota_begin(size_t new_firmware_size); // Start writing
bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k
@@ -31,5 +37,6 @@ uint32_t mg_ota_crc32(int firmware); // Return firmware checksum
uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch
size_t mg_ota_size(int firmware); // Firmware size size_t mg_ota_size(int firmware); // Firmware size
bool mg_ota_commit(void); // Commit current firmware bool mg_ota_commit(void); // Commit current firmware
bool mg_ota_rollback(void); // Rollback to the previous firmware bool mg_ota_rollback(void); // Rollback to the previous firmware
MG_IRAM void mg_ota_boot(void); // Bootloader function
+2
View File
@@ -35,4 +35,6 @@ size_t mg_ota_size(int fw) {
(void) fw; (void) fw;
return 0; return 0;
} }
MG_IRAM void mg_ota_boot(void) {
}
#endif #endif
+83 -13
View File
@@ -1,7 +1,7 @@
#include "arch.h" #include "arch.h"
#include "device.h"
#include "log.h" #include "log.h"
#include "ota.h" #include "ota.h"
#include "device.h"
// This OTA implementation uses the internal flash API outlined in device.h // This OTA implementation uses the internal flash API outlined in device.h
// It splits flash into 2 equal partitions, and stores OTA status in the // It splits flash into 2 equal partitions, and stores OTA status in the
@@ -27,7 +27,7 @@ bool mg_ota_begin(size_t new_firmware_size) {
size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size(); size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size();
s_crc32 = 0; s_crc32 = 0;
s_addr = (char *) mg_flash_start() + half; s_addr = (char *) mg_flash_start() + half;
MG_DEBUG(("Firmware %lu bytes, max %lu", s_size, max)); MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max));
if (new_firmware_size < max) { if (new_firmware_size < max) {
ok = true; ok = true;
s_size = new_firmware_size; s_size = new_firmware_size;
@@ -61,6 +61,14 @@ bool mg_ota_write(const void *buf, size_t len) {
return ok; return ok;
} }
MG_IRAM static uint32_t mg_fwkey(int fw) {
uint32_t key = MG_OTADATA_KEY + fw;
int bank = mg_flash_bank();
if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--;
if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++;
return key;
}
bool mg_ota_end(void) { bool mg_ota_end(void) {
char *base = (char *) mg_flash_start() + mg_flash_size() / 2; char *base = (char *) mg_flash_start() + mg_flash_size() / 2;
bool ok = false; bool ok = false;
@@ -70,7 +78,7 @@ bool mg_ota_end(void) {
if (size == s_size && crc32 == s_crc32) { if (size == s_size && crc32 == s_crc32) {
uint32_t now = (uint32_t) (mg_now() / 1000); uint32_t now = (uint32_t) (mg_now() / 1000);
struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT}; struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT};
uint32_t key = MG_OTADATA_KEY + (mg_flash_bank() == 2 ? 1 : 2); uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS);
ok = mg_flash_save(NULL, key, &od, sizeof(od)); ok = mg_flash_save(NULL, key, &od, sizeof(od));
} }
MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
@@ -82,12 +90,10 @@ bool mg_ota_end(void) {
return ok; return ok;
} }
static struct mg_otadata mg_otadata(int fw) { MG_IRAM static struct mg_otadata mg_otadata(int fw) {
uint32_t key = mg_fwkey(fw);
struct mg_otadata od = {}; struct mg_otadata od = {};
int bank = mg_flash_bank(); MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : "prev"));
uint32_t key = MG_OTADATA_KEY + 1;
if ((fw == MG_FIRMWARE_CURRENT && bank == 2)) key++;
if ((fw == MG_FIRMWARE_PREVIOUS && bank == 1)) key++;
mg_flash_load(NULL, key, &od, sizeof(od)); mg_flash_load(NULL, key, &od, sizeof(od));
// MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key)); // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key));
// mg_hexdump(&od, sizeof(od)); // mg_hexdump(&od, sizeof(od));
@@ -111,15 +117,79 @@ size_t mg_ota_size(int fw) {
return od.size; return od.size;
} }
bool mg_ota_commit(void) { MG_IRAM bool mg_ota_commit(void) {
bool ok = true;
struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT); struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT);
od.status = MG_OTA_COMMITTED; if (od.status != MG_OTA_COMMITTED) {
uint32_t key = MG_OTADATA_KEY + mg_flash_bank(); od.status = MG_OTA_COMMITTED;
return mg_flash_save(NULL, key, &od, sizeof(od)); MG_INFO(("Committing current firmware, OD size %lu", sizeof(od)));
ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, sizeof(od));
}
return ok;
} }
bool mg_ota_rollback(void) { bool mg_ota_rollback(void) {
MG_DEBUG(("Rolling firmware back")); MG_DEBUG(("Rolling firmware back"));
return mg_flash_swap_bank(); if (mg_flash_bank() == 0) {
// No dual bank support. Mark previous firmware as FIRST_BOOT
struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
prev.status = MG_OTA_FIRST_BOOT;
return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &prev,
sizeof(prev));
} else {
return mg_flash_swap_bank();
}
}
MG_IRAM void mg_ota_boot(void) {
MG_INFO(("Booting. Flash bank: %d", mg_flash_bank()));
struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT);
struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
if (curr.status == MG_OTA_FIRST_BOOT) {
curr.status = MG_OTA_UNCOMMITTED;
MG_INFO(("First boot, setting status to UNCOMMITTED"));
mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &curr,
sizeof(curr));
} else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) {
// Swap paritions. Pray power does not disappear
size_t fs = mg_flash_size(), ss = mg_flash_sector_size();
char *partition1 = mg_flash_start();
char *partition2 = mg_flash_start() + fs / 2;
size_t ofs, max = fs / 2 - ss; // Set swap size to the whole partition
if (curr.status != MG_OTA_UNAVAILABLE &&
prev.status != MG_OTA_UNAVAILABLE) {
// We know exact sizes of both firmwares.
// Shrink swap size to the MAX(firmware1, firmware2)
size_t sz = curr.size > prev.size ? curr.size : prev.size;
if (sz > 0 && sz < max) max = sz;
}
// MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED
prev.status = MG_OTA_UNCOMMITTED;
mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev,
sizeof(prev));
mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr,
sizeof(curr));
MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss));
MG_INFO(("Do NOT power off..."));
mg_log_level = MG_LL_NONE;
// We use the last sector of partition2 for OTA data/config storage
// Therefore we can use last sector of partition1 for swapping
char *tmpsector = partition1 + fs / 2 - ss; // Last sector of partition1
(void) tmpsector;
for (ofs = 0; ofs < max; ofs += ss) {
// mg_flash_erase(tmpsector);
mg_flash_write(tmpsector, partition1 + ofs, ss);
// mg_flash_erase(partition1 + ofs);
mg_flash_write(partition1 + ofs, partition2 + ofs, ss);
// mg_flash_erase(partition2 + ofs);
mg_flash_write(partition2 + ofs, tmpsector, ss);
}
mg_device_reset();
}
} }
#endif #endif
+2 -2
View File
@@ -40,8 +40,8 @@ uint64_t mg_now(void); // Return milliseconds since Epoch
#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a))) #define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a)))
#ifdef __GNUC__ #ifdef __GNUC__
#define MG_ARM_DISABLE_IRQ() asm volatile ("cpsid i" : : : "memory") #define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory")
#define MG_ARM_ENABLE_IRQ() asm volatile ("cpsie i" : : : "memory") #define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory")
#else #else
#define MG_ARM_DISABLE_IRQ() #define MG_ARM_DISABLE_IRQ()
#define MG_ARM_ENABLE_IRQ() #define MG_ARM_ENABLE_IRQ()