From 7885a5d5505767b622e1881b166457b7ce45daa1 Mon Sep 17 00:00:00 2001 From: Michael Kuyper Date: Mon, 4 Jun 2018 17:38:23 -0400 Subject: [PATCH 01/10] illustrations for documentation --- doc/img/fw-done.svg | 157 ++++++++++ doc/img/fw-init.svg | 160 +++++++++++ doc/img/fw-lz4-1.svg | 196 +++++++++++++ doc/img/fw-lz4-2.svg | 227 +++++++++++++++ doc/img/fw-lz4-3.svg | 194 +++++++++++++ doc/img/fw-lz4-dict-1.svg | 198 +++++++++++++ doc/img/fw-lz4-dict-2.svg | 308 ++++++++++++++++++++ doc/img/fw-lz4-dict-3.svg | 226 +++++++++++++++ doc/img/fw-lz4-dict-4.svg | 258 +++++++++++++++++ doc/img/fw-lz4-dict-5.svg | 225 +++++++++++++++ doc/img/fw-lz4-dict-inplace-1.svg | 198 +++++++++++++ doc/img/fw-lz4-dict-inplace-2.svg | 456 ++++++++++++++++++++++++++++++ doc/img/fw-lz4-dict-inplace-3.svg | 227 +++++++++++++++ doc/img/fw-plain-1.svg | 194 +++++++++++++ doc/img/fw-plain-2.svg | 226 +++++++++++++++ doc/img/fw-plain-3.svg | 193 +++++++++++++ doc/img/illustrations.odg | Bin 0 -> 22710 bytes 17 files changed, 3643 insertions(+) create mode 100644 doc/img/fw-done.svg create mode 100644 doc/img/fw-init.svg create mode 100644 doc/img/fw-lz4-1.svg create mode 100644 doc/img/fw-lz4-2.svg create mode 100644 doc/img/fw-lz4-3.svg create mode 100644 doc/img/fw-lz4-dict-1.svg create mode 100644 doc/img/fw-lz4-dict-2.svg create mode 100644 doc/img/fw-lz4-dict-3.svg create mode 100644 doc/img/fw-lz4-dict-4.svg create mode 100644 doc/img/fw-lz4-dict-5.svg create mode 100644 doc/img/fw-lz4-dict-inplace-1.svg create mode 100644 doc/img/fw-lz4-dict-inplace-2.svg create mode 100644 doc/img/fw-lz4-dict-inplace-3.svg create mode 100644 doc/img/fw-plain-1.svg create mode 100644 doc/img/fw-plain-2.svg create mode 100644 doc/img/fw-plain-3.svg create mode 100644 doc/img/illustrations.odg diff --git a/doc/img/fw-done.svg b/doc/img/fw-done.svg new file mode 100644 index 0000000..8a80c2b --- /dev/null +++ b/doc/img/fw-done.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + New firmware + + + + New firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-init.svg b/doc/img/fw-init.svg new file mode 100644 index 0000000..8c725c1 --- /dev/null +++ b/doc/img/fw-init.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + Current firmware + + + + Current firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-1.svg b/doc/img/fw-lz4-1.svg new file mode 100644 index 0000000..9fea3fc --- /dev/null +++ b/doc/img/fw-lz4-1.svg @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + Current firmware + + + + Current firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4) + + + + Update (lz4) + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-2.svg b/doc/img/fw-lz4-2.svg new file mode 100644 index 0000000..51a2346 --- /dev/null +++ b/doc/img/fw-lz4-2.svg @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + New firmware + + + + New firmware + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4) + + + + Update (lz4) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-3.svg b/doc/img/fw-lz4-3.svg new file mode 100644 index 0000000..5841567 --- /dev/null +++ b/doc/img/fw-lz4-3.svg @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + New firmware + + + + New firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4) + + + + Update (lz4) + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-dict-1.svg b/doc/img/fw-lz4-dict-1.svg new file mode 100644 index 0000000..5e9d491 --- /dev/null +++ b/doc/img/fw-lz4-dict-1.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + Current firmware + + + + Current firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4-dict) + + + + Update (lz4-dict) + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-dict-2.svg b/doc/img/fw-lz4-dict-2.svg new file mode 100644 index 0000000..77d9d61 --- /dev/null +++ b/doc/img/fw-lz4-dict-2.svg @@ -0,0 +1,308 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + Current firmware + + + + Current firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4-dict) + + + + Update (lz4-dict) + + + + + + + + + + + + + + + + + + + + + + + + Update (plain) + + + + Update (plain) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-dict-3.svg b/doc/img/fw-lz4-dict-3.svg new file mode 100644 index 0000000..d439bf9 --- /dev/null +++ b/doc/img/fw-lz4-dict-3.svg @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + Current firmware + + + + Current firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (plain) + + + + Update (plain) + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4-dict) + + + + Update (lz4-dict) + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-dict-4.svg b/doc/img/fw-lz4-dict-4.svg new file mode 100644 index 0000000..0e459c1 --- /dev/null +++ b/doc/img/fw-lz4-dict-4.svg @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (plain) + + + + Update (plain) + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4-dict) + + + + Update (lz4-dict) + + + + + + + + + + + + + + + + + + + + + + + + New firmware + + + + New firmware + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-dict-5.svg b/doc/img/fw-lz4-dict-5.svg new file mode 100644 index 0000000..2342e04 --- /dev/null +++ b/doc/img/fw-lz4-dict-5.svg @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + New firmware + + + + New firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (plain) + + + + Update (plain) + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4-dict) + + + + Update (lz4-dict) + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-dict-inplace-1.svg b/doc/img/fw-lz4-dict-inplace-1.svg new file mode 100644 index 0000000..ff4baed --- /dev/null +++ b/doc/img/fw-lz4-dict-inplace-1.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + Current firmware + + + + Current firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4-dict-inplace) + + + + Update (lz4-dict-inplace) + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-dict-inplace-2.svg b/doc/img/fw-lz4-dict-inplace-2.svg new file mode 100644 index 0000000..1297b14 --- /dev/null +++ b/doc/img/fw-lz4-dict-inplace-2.svg @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Scratch + + + + Scratch + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4-dict-inplace) + + + + Update (lz4-dict-inplace) + + + + + + + + + + + + + + + + + + + + + + + + New firmware + + + + New firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-lz4-dict-inplace-3.svg b/doc/img/fw-lz4-dict-inplace-3.svg new file mode 100644 index 0000000..28352f3 --- /dev/null +++ b/doc/img/fw-lz4-dict-inplace-3.svg @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + New firmware + + + + New firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Scratch + + + + Scratch + + + + + + + + + + + + + + + + + + + + + + + + Update (lz4-dict-inplace) + + + + Update (lz4-dict-inplace) + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-plain-1.svg b/doc/img/fw-plain-1.svg new file mode 100644 index 0000000..88ef0f7 --- /dev/null +++ b/doc/img/fw-plain-1.svg @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + Current firmware + + + + Current firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (plain) + + + + Update (plain) + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-plain-2.svg b/doc/img/fw-plain-2.svg new file mode 100644 index 0000000..2ad6cb6 --- /dev/null +++ b/doc/img/fw-plain-2.svg @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (plain) + + + + Update (plain) + + + + + + + + + + + + + + + + + + + + + + + + New firmware + + + + New firmware + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/img/fw-plain-3.svg b/doc/img/fw-plain-3.svg new file mode 100644 index 0000000..1b654cb --- /dev/null +++ b/doc/img/fw-plain-3.svg @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BL + + + + BL + + + + + + + + + + + + + + + + + + + + + + + + New firmware + + + + New firmware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Update (plain) + + + + Update (plain) + + + + + + + + \ No newline at end of file diff --git a/doc/img/illustrations.odg b/doc/img/illustrations.odg new file mode 100644 index 0000000000000000000000000000000000000000..ea49e54c545beead251561c7284562bd54ca9fb7 GIT binary patch literal 22710 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 Date: Mon, 4 Jun 2018 22:40:59 -0400 Subject: [PATCH 02/10] Overview of update types --- doc/img/fw-lz4-dict-3.svg | 24 +++--- doc/img/fw-lz4-dict-inplace-2.svg | 82 +++--------------- doc/img/illustrations.odg | Bin 22710 -> 16450 bytes doc/uptypes.md | 134 ++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 81 deletions(-) create mode 100644 doc/uptypes.md diff --git a/doc/img/fw-lz4-dict-3.svg b/doc/img/fw-lz4-dict-3.svg index d439bf9..c7c51c8 100644 --- a/doc/img/fw-lz4-dict-3.svg +++ b/doc/img/fw-lz4-dict-3.svg @@ -11,7 +11,7 @@ - + @@ -43,34 +43,34 @@ - + - + - + - + - + - + - + - + - + - + @@ -215,7 +215,7 @@ - Update (lz4-dict) + Update (lz4-dict) diff --git a/doc/img/fw-lz4-dict-inplace-2.svg b/doc/img/fw-lz4-dict-inplace-2.svg index 1297b14..8321646 100644 --- a/doc/img/fw-lz4-dict-inplace-2.svg +++ b/doc/img/fw-lz4-dict-inplace-2.svg @@ -11,7 +11,7 @@ - + @@ -44,34 +44,34 @@ - + - + - + - + - + - + - + - + - + - + @@ -264,66 +264,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/doc/img/illustrations.odg b/doc/img/illustrations.odg index ea49e54c545beead251561c7284562bd54ca9fb7..13ffa163e08e8022e09df1ff82e364abd7b7264e 100644 GIT binary patch delta 15083 zcmZv@1yClWS@cGuT`L>V&26X8XHTaYD zSQ2$v(zUpOMy$!k9O*{Im&Ioi@db+uF(vs@9E3=63gYsFMY@f>HV zTsw(E7r8=L#S$0Aav#-VU-fc-%~C&|N-sc&zgs%Mq0-+8Q0^39;2&z}7U=>=u=P## zrVHp}3G5XJYUK>-r4AZk4jp8R9Oe$|=ZzZXPx>hrQYRVOq#jnS6jrYi*R2y>ua(s6 z8yM;r65|&U;~$-A8e3}=*I=JiXP4Y;o7(P>+3g)t@048UoYE)~Gb|K8E*Uo>mM|uo zG$EZZDg&GV$YxBdCygm5Pbg6Qn3 zdgk`vP7N zfhpNBL8Z|FRk4v(aiL8S=_R2#jfp{ZsUhvTIkg#p)cV-cpwyDcjM9{>rl^$0gs}XU zpn{H=f~Ks3;==6I!tmbwyt<^6KXD_R^ZB*7}-?md4t~u9Bv{imu+0#-`@_=Jxuo=9Z4`&i0m$&hG5! zft*CCv4E1H?9PRv*#45V;f9>C()_`S!nxAk`NqP@*2<;&rvBE}@wU#vj_$$k=7p-! zjK-<#+L6kpsfy0g?(XHPp82kxfsyW?gFpgsO3*=|C^!@B#_rnI-t6Ab<$WLkoC+{! z9P17Of+{8@Dx~VUaj6IAil*T$uVOnqB%jtU@1%tv!C_U8P%VNwPsyp(T|ddM&Aui0 zwELjwAh*zoOr`}B3h_N)e-u`0F$l%3>+XnbiU@;>9qr1p z!qqwW-Fq|iN5cdCux@tqcB2Zi?)XyE@p|s%p?bp)pk#JT&Y+u}oPDFHY-}t)GZbwx zV#(RDg}3=9?^^eZyGq%pg-rYR?5RQn#u)3B z11=C8Dp;mYkKb!S1235R{JY7KX~D03+pY!<3RK#Lk&{az!J^w+tIRM~3JMxBHa9PG zYi()>fCTPS*Ew$QdrWGfQf8y*0&!YhvgFe^1!{+BY77)9Ld%;?)M!C+vP6ZGCo2ZL zkbQU$i30N_BYI!9+(Am_^zHU= z?AkMTtnAh^KJ1DZfZ0cj3=0XC52mR?#~l(x&5G;L2*!)oM zsOj&HkCP&@Cd>UC9i`0pqby=lBrBgJgaJw&yF+;GDk1LdY~Xnk&czjZZXl`sh2%AX z=cNR5ql*bc&&E!LhezH>a|cx-NxJKTizAc@kiDOPNy|!X7}~$mP;+ua10pMQUdlYjLH;i zY|Y8Z8Zt8ambi6ZB$t5@9jX$8NctQgd%RsdSl0Mdo`?X;(-g7^jeWa-C8s&mTAA_Z zo;$NA10M@Pxr{Fx%gMX|)J=svrdSwgocqvFnf5-z6OpaHp;dll;N+it06&_+I1(n7 z5aN&SSJZw=WK(fM-7XjqP0|5er#c+X2ML~jh&1dF*rm&Dkl(~GNqU&3z z^m6rGKbtgFc`ciPjlKD$A^3#~YZdDbVb?#uDy1?f3t!T5#{r1%4RMQS7HMM*Jd$6I z6(CKRsycs0z7D{pG-(8MIy(+9Gq+xJr>7ivMa7EtONYiAS2~ZM zKel%=-t%$k`tMJ_PgR~|$4*49udjcL2BHDkZDqA*qu(rb1@3&0ddX5&X#{NRY$c2W zxp6m;s|t%`ijYnLzdGW=TAK9L6b?S*1hyYc^0U}KAXZGT%d)^y`c`p#^YP{&>t1)28B+wqSMv&Ja+AuRQ(<-+n|iEi9kIJ6XTky)~Cdxm|xiNHHdO4;Yzx0KKHnmeh| zedaj-JnT4*?|%HHb6)EqF^_YLw_8w4frJTQ|IUrI?B-~#y0wKRwuRCR=%oCb&@AAfFt@YK8mq?!===*Q{SUbWrz2hLx++Z!iX)bscD)9 zz!^$>kJ@sCnAl?xsuGCADaSH1V&?ws0Gs_7Vq$Cx){Lkrr#V(Wb69ML+Rx7g&l5M{ z)NWS7O$G!uO-3n%$b8NC2^Cp;s?TG8yMKOQ;lw?thNg?_J;4I(0iI|5(gf0ylgBkn zLVE0APL&aM7*8Mlm-^@JMKg07LXh$RX()Gl``Y|1f+<>#G?}ng95Ln#s-85JEO&96 ze@2ceDJ;Gp+3IG)HGxuExX8e0lA`}ODugQ0r2-CwXNU?v{hdmALU2y~l3sb2qRPDZ zFJ29eMU@rz+R-5U5hBmXl-yehp8K}zDTR$*ny*JaJUIWJ1e(~=s-H9ncQSGSgjfSA zvs@eNK6o}&>SfUAL@GFV*QEY8WE{EPDZXYvACl)FUgvKv9) z_pw*AV9+2yLjy)!v)b)C1MiRk9+K0--xt+h?Y&pdyd8gus9MbAf;hijoU(wF8N40B zU*z%oLH)FNbRFiYp9?qAyNZerE{u-bg2A#NWdww+qbXyz?O?G|J~Cwpchw!JwpOgu zBSPR7ZRpsq4<6k99u5pzqIc%J@VKz0+mnFe+rui)cwm(c7{d_R4Yl|LfEa3HX)5{2 z8#Ak@&kASoZ+sTKsaC5B-{Lttb985>_4)hg6I!~scZ5gK1T9pcBahWr%w5-C!4tx#Ie zSB2%GbOI{^oxv~90Fi|OkO_rKx4)_CkUdwNz(q=9i>8)^mWMhxhr03x3Y*~b3yTSe zv|J`i==+9@Mqg1Te41KXTCR!g05PTZf};*MM1Sa|pci1=WJTe9e0*?No|e6VNP-gL z>F)7LQtQRmdj|)md?s#Idt<{cL-JL2@&&2)J0L=70tkHWk4={Wo|#aK?RA#2^$TNP z@|X}3Un6i|muLmATSw#hebYgKsu3u0?jpFhbRz~cNEyiWqR2Qf0Cn(n94`GCT0;y| z{#IHt8Zwi06Pg7=xS65VKNByck+S4zQX%!BTGpHtjE8u1=0WI&(?SSzT+lS1zgckI zYb__J8$@ZJo2cIa-yrhNq*WGE7;48E^z1sFTN1MrC?Hs4#Cw#!Fm8k&#MerEEB#q< zugr{Yyqq)!?RTcOPDwPxL@Y!h2y_A^$qRi^<%>XbP&`b-GhFx2R3P;*K15H{<|z(+ z&ZOgrchk4gIL}h+uMxt38)G0dtGO-c774Sq&r(lM4R_+5 zRyD-%uHrB>9OEWWPWCJLm|}7g9iS21sYxn)GE6xoiKrrJ&F1Ph9{i0WO1EGlOIlH# zg^7LHl2T^@c;)K9dZ@Kwvr(MXSDr@{u=rD2&Q57-&m99PgD~3 z*oxNS@mOx8+Fw+!RE0|zQxY`0@#~SD6eyaPC=cL@nNPPD7Dy{ZJF!an_me_Vw4DYm zaUpuqF^-vn*zqmM2l07#3sJ)^i{U05vou1&hl(=*QMm$O-j*W^7?XmaY>?(Wu06IR z^{#Yzed_hCFv%@8(S~U%FdlZ{Upn|d`c0O_p}lLg1+Y)@>5By|eE~?H6Ps&)jBwM% z*H}x#!or}(CGYy@h(5#NJ~^V!3&O*K``L%0=?23m94y1dzvITnS7}xiPIzU6cp=Tw zBJ`91+Ez`kAZH}%wttzOl6X&tjKFs%>F=tL@NW)FQ=oqhJ_Y>=%7I+x`D`Hutk&Y_ zVAQldWK;Why$rayl|{uT=TOtrcim(`BURIUmRHXh3;8ZwxM-M{(cL;BQBd5VqM~}< zW721*rMELWmC{p3mmKIiC@3J!FGAuA&oSWv7+fD7-227OmY=733d(q?CMP3uUuC*CG6b)YJh+uMVbOU!`OS$7Qkc%~;?T;}B;iAd!{V50YnQbI?#`>K zASCe;-&AfWtorTn5`N&V_X8v1UR~PN#efL9=m>8QnSVds%%(X36yybNaMx&!Df9&( zXpB{!QJN6tmTnxl)FBFLt@4Ks<+y zz-{%U!PJ0fa-+adg=?(ek!MDk9(Dy-D$M8um!&SC*sqR>nHrp`j={jmU`&~NHWnu{ zTR*Brd(8XNSaw?8CBCWHu=bwGsUx#raa~3(+n7Pov*WON(BpVCm!T_EILc%tQ~4%E zjx)#yra@gW)`!M|`=hJR`{3ag`k9CqPNL{p(CIetI4V%Vt#*GYu#Lt(mG29H;#lMF zGGr`{j{r~_Ql%~knr%}PO&qLT3vd;`n_L+X}l>? zx$z_0X~O8Lc8m7;&lbLNHnd@4-6j!aap1fqYsUCGrL`U_X{^F*WybutcFOI^&zNR4 znmp|eYIOr3qE$OQCnVpEaLi!{QT8i1hLFQhig646JuYFCA9aD@bJ+C;0b(5YhaIYQ ze)xjkxGWNQ*xr2N{9eQN1m;SmiZdzDN=mhYzJlw8XO#to7DFuB5AR}CVZ?LW;90WA zzh?x{2?HJIh=K9n7c}L;_V5>YrV!m6Va9|Y$X}5XlNYTOHVpa~$q54k^AE`h11q-scIJwbQA#=B?(8#xIX0QFRdZg)`+EJYReUZ%0I-rR#)F`Oz zK4yi$p-v6Ifgh;nz}9xDrC>AAWwRKw=27cWX|*9E(vWLn+ohki0obeW6SA-Y+&Hzo zR%{&L(qb|QFL#?Aa8?c?g#K{c_N9SQLrpdTzeV zEBywHHrTGhynl?ZyCBp&+j(9JbtoUtnI1lrKeqR5IR5VN=^zNb2tyc5rXqvUfaJ$R z>3z^C{{&T(friQ8S@&K81p!fo00H@51h?!zyW+0^sYqzR|Je(*#3Vp`bws%TAi;se zBqTr^RoMTh&VvN?zx#0fS1Cx$_TME5a8m{Tf3=Sx;r<`nyXwDe|K5L97z6+%m(6w* z|LeNlo8NCxD#?dy$_KxiCrnH2vfXDqE)NhygHTZub_4RwjuajmzUumlyFjD@QGckk z+8$?54U@QIt$nyR^wXcV32c=Nc9~V~GI!k#H6>m*&?Yg>rfPiF_4$&geJWgk-l$W4 zi(lhSRmy0T1qwSr(C{M9M0Eft^j}MU{!YT|PPbq+8_|_;$!My^%U|U*Cm`Fnj~4uwd^Yq%`?zcr|~I$ z?n?-9{`h9g#|5;;329oVWn2>9E8m-xp@7%Ghm>X1t%ezFVl4MkDLn-!ek81_{h(Qd zR0%F9$h`7m)2If)WW+jfUK6dArRlX#3obf$Uk7(-F0^*c#`*1DF1x>1z#OT!o_wYj z4`2wkAS)Tlu#8#*k`^3LwErUf^fogjU+f+$ai4dvfVsEe4BnTJW2FmP{jz^vDx#=*Q8Ie z2ayWBvHNB1f9h>(VrD7M^0pOovepyNZO{gw*j#6E9X+qq;f{%`xSUVZCi+MfAaF{@zfkQ zIwzW2sio{$wzzZ>W{5?-kSV`Mv5r<16=mn7U*8{m;@I6t7_L6;u@0D+50A%+JW0-5 zG+ZXsCQ0<`?G4*4c%LVnlr9L$6;f%z>q&N~G8QgWD+iJ%h_@1M zxsu=ed*=Wt;Y==`Q01z&u+s};Cl5+YD@6$+-qHyCQZNeF;9Vs+dO@;%lkV7gka@Gp zG6x9PA3L8{Tiwt8=99{b%T;E1{S(ayZ_HwB_LHF=4oqZ#AwO|Hv!5(qZBQRtKRQ}B z4wUY&ygZKLyAwGGy?}`X;n|)9gXzR1h_$0d4(;_B#dFgBp%hjP&K4izEKi%B51Y`~ z77Y$kObpIK9Hx`|EhlPpg_^}S-zuV!nG4?~AtqBjY2Yld1nezVSckaB;o13Pw1j1p zQe@fUQhZVYQNL*jU)dZUWt}BYWvvh?FmVlVu%$@$)IWwy-+pJySDB&Fi;8q$&nCg3 z%90-2q(_ba+RkH69mM0YQyTE<)f2U#29%43L^BI_oFSChVbB5GR!emhnI@J zVL;mS2fc#?P~U@r*Y8w^5nOIGwbEh-4$`LD7-QBYELED)mAol)5!;`$P{21#!HYV6pM%x?JfGg~hNyL5-JBlLA`;}dT%(S*4cIn7 zW2g@TY;84J4=s1ti|E~MrQj_X2)yfjH9rYoVtbfqraO_1sE&NzZjMO7Fvg?vK5fx8 z|7Nn^R7!q(zM{=46Vxa_!X0R0in>}P3&U#Q7S(}A{s}#txIq!~-r0~3D8qvdV-Kwn zS69AuK)nvH*Lp?O{VJhlRbZ!ta=|q*2=C7Y2&oO;a#0^3V=CLwW_4NyQEXt>%P9JG zI8&{F(%@z-#Emi8gIFZ=Cn_?^U2Rx4M`p+DD zz~tn?7u>EtsF6PWHiePXr{Kpb8#+$;eL4vwVf8EV@^%*v(YMG&l=vQe^5;3FoWNPF z0n#sYbzl^%8ew?`6`@s4R83el*bQv zo1wHcE%aB0>JN-ft>&P{wO++Z(@`jrOIiwbhWlaKs|`gAH&tKLHP?B}N}EmqF5=-?)e*o2=ItfP`f^?aItLw{d-g!v?wLrkn1vcy?w0_nWC)-Irka z<-TM1wEfjK)aLN-ca!<%Z>K-@s+qUx7}~#`em{-m>v!fXs&9H>E#1MwYn`-m|7p1V zWu!XB!BfuvIYs^%?XssZXU!AwUej9eaWy7?u!pZt$(Q^ji2ijA%eObv2>7-?z-cJ| z>Sx)JaQmBrjnDVBs+P`SmzZmV+P3a{_G52_C;#~y{q~ca{<~TKy!poD!?VpH%CcA& zXSqympra$5J}VRax5LwiazSz6U|M~}^YY3#q1mjjE#i*Y6Dji&!NI{6lmA^)dk0-? z>-B{;zhn|s=5#Lu5&Zh0J0N9?a!_k+QQQ)S;L}g@`vA9}^O8}cF{}S6%^2|s4Qtr} zKj+w_3+LmakS(hFKh5&TLz3A}Ergx#Tmd@7;m-yag519DN0Q8S)l;Mh`eGrKhXvPlQ zTEkUDIB7Fl**mRDT+EQVI5&RE0aCe?k;)z=eu=*j$dkH_r~qJKc&$5JV?z&8+( zbB=$5zW)YX{|0^kh5X?D-c1|;_O533uJoREwwHQaNk#1#y|XIqYvd`q#ALTS8M!qV z_LpJ{O(`20a>^a+g+i_C0}&us_SZ6p%-3n(BZu=|nV|4CL`qKw2&-M6F=vU)zWq!3W z18bIA2;XX&5B-rWlYa=%PfuvDX{7twdwZJ__u>WpN^2rA{6n zTinosqt>R)u}+&QYr6v0=nsSRPAHV)dWH$jIigdM`_1UPMjDpe0J5#WC;)AK!$uC4 z)oa$4Y%oruVkZa8#!oiox@CiPWo{1cd^I|TtsLrWSr(CwDb$UNdDM+6wOuXya?$>= zN6^~oe_n1Jch%Z2eHbWp0)HZI%BiQvNe}l@x5sm_ zPMWA~3Q?P>7FZF$OQyMUd|l;lC<07Bv4SH&QCd_5C!5=mK{2EJ#f^F8^bzv zTOkb#x~>G=GfDuZ4FWHzX&g4nePDE6A1d|sDJ#=7E)$x{jv56D^?MKyBfr^{Ih3nF zm5;O5QSW61WBpaWG8|p-59;omnOJI?YFAaW;!3Iny(RPgSP3Sz{Lc}6`;I6H?hSa7 zefC1pQt(s=kCF|wJS=;IH?}dTA5Q9zm^M(8aim=Qqu~Gr0eY>Bab12l>-8Mz0z}m9 zV<$Q5u|hK^Be4TKC$n`;*ZNDb>z$I%iuP_vfhPYQxUaSLhWO`bq%|iG9U~z>NqQ-X z-=F#~Js1fm3Th1xne|j``0WjH2p>R<&TmbrWe)GV%0yX~ZnyJyMJw+shjG6+Q?ORo z%3f69@R9&ENAb&S8;6w|JL$V@+|LA`BX93ltmYIMgnCg=N-=FBZx4PEPBP)rVs-1m z(88x3FNgwKy)C6~8*1uk-LU)u3F+h;n!fL8ef0c)i7%8ok6et_r^cE=%O1T}t*DF6 zmgsLTWv%JWD(MM_7DwyZAebDJxg=S%5rHlALW_XvPDDJl)dCD#U7kLCO_HoS3Zmg= zR5(_A8rP8St?DRjMVjcLwGpKtYxM9#7R)@o@X$Qe1k>f2Y8`b`wD>uyFB&}#_(gQg z>P=Q{1c8Hu?L_hZ@Sl=m)y`T$qYz`08nwq5c0A1Qc`1xK@qrQ^=1_Kq&vVwyHdJOMpNH-`v-1e$D(3OqRF7tf+9Gx zGA9Dg@!{W^B-Tr(%oRn@%E1~VVG*+{R$YoON+}U4(4j+KSorr9i<2z}THY9I*q2xz#OE@cI=^#D^N z`5~)CWNDYhR5yctr)1IFVEcXZ*jL4L%`*X&w7>9qnDT zDN{~~5S&EZGyYYcl_@XN*h2sN`DNuUYj^L0Lxu7pP0dhnW+*w_WlOGLi#+!n-{g0Q z3d-x$QUBDsL%r;p3R%7lz&DTKXisO|D3Rib9BJ|hI29>2<~a`RzgxN>;i1ilI2Kn6 zgnnU{JyKg+T#vF8f3sP~dza+)b{tqe1b z)K=foNVnnQFziC^C=&HLuzEfG{;(wZf5!O_#`Vk1(_|K|U&G3BCM!{q`)UL*pE<;a z(o6Hu6sQR`?n?tQ%pasON}Z3$v$+mhW8Gr!>=w!THf+}#2DQ zShzdAIryA9C)-*X)i}O(k4V`nF&C-FEcN_sg|ttMgF4Y1{A`1*!1&v+@~;XMNxkcd z-#DAT)*uwvp3I4LtqEEj| zef9uf9}0ou6^W>nQUtAIM_TdC#cO$s+?Ytz*VHePu_t36Pl=vfAYBo5Xi=2KMaR=AgwHHtNb;vp zN$h~c61V_36Y_|qHnVb>5v}T+zO%|#>8k48M9%kpa2@@8m6GS{BsZuhs4CYHlGy2W z3(Z5}t7zS>Lkdl;1)X7YU0KB`gSFO1cfzb(a0lK%!nYN`nq_8i_Eg??lvl7N+)ZbhQ82+=>ilLw)k81!=31dQ^&nhgrLxib7Q~ zSIPqtZa$&^bZd6(J*~suu5GM$8c7Z+yPGE}Y0=NPFjs{F7cNqub)KB8gYDSQl696V zTq{)|7dj4H+qq9UKishzmo7E%s({J~3zftzKHWn3<9fKvf|+grU6ILk5HGW#6cm7U zn2j=ZhkG)k?ss14^h3iUpz=8MW-;%FV+!C`W|;=Gr#B>3ly_-aot0%mSzhekf_6&& zqoY*md<6s*xRlBmA(a4R9LB*25!kHrhk$e2QlXw-KQc>TX`%e3v>W5A-GAvsd6CVY%HQR?tU1JPq=Vs1<2nO-w*;*@2JYu&21|a}X zcKC--a_zKJ9VzGCPA@9ON@1( zbg!7JE)U_>fUJy^hC3=OV_EwL*N+_CO)2=*iD|CLr`TM=tn~imnVjw=H&!Bs+qZKd zs$wEH_s%QW(HwDpM6-AtSm&}$s}JB6XT6-p#~u6x>iJ^v?qbNz*(J9}Pt30rAj!Ac zwCc)AG>f>&($jeiU=HwZvsn!&s4BuM=yX8V%k%cayU*B7_o_!t@LcgqliyIGI@$GJ z_x7lDVQ7~(YOy97lXI)FvK3FQ^g~^+dtf8Zv$?m3G4&A!%VJ5xhO3GRd{j^G?r*cefmQGT(0fUEIZQIa5tF<4d8yALp~1`v*2xS4Quf$6=Z3Lhj`p-#svp z){WA-LNH9cOa1qOaczzoH)TST)(TIDwa*QP^@zqpWC^_#CxLyyi?_Xl$Vx5~c^X@t z8tYC^G6OiIn|;nIC3(?_Un5}0-{__BIa9Q(#yP-h$R;TVHK$D$-{WXMVI{PbazZez zpcW5S`El{#)!VHdD;fR`s8_KNp+qHdw8KN=9+}sg5!Ctfy^VCw{aEXH(4izWAxq9% zV{9@#<&zGI+*yk((ohzxKp9tg!Q@u#S7&e6Rzq85bFvPDl^3kiz%3wbe_P(rtaP26 zx4zRe)bx*7LZ5E;r zdCO-q%F`0JdA!TM-hn7Npm*ehBo$C4ZZDNIfpx%eMoj~bv@pj@+K0-p-)Jkj^1=+U z!Doz~IaAF{Q!92QWkrA|ClDGGrplAL&GUz-h^&gDU^c40Cuy?ys4^@J10LQ5kbMa@ zIorMR+GL!LwJHFPajW9fFZD&5ncDZP4J>~J<93|AcWboI++oK@>Nd={C%rs2YVc-- z*8A7yDM_t7DgrjG5PFny~<8UNTK)XnxECg$f z3+6KCS+zS9&rTsPLE)1oPcLns&5PCxY`n<#PE}CQr5ELqX89CCp&u&p)mh2b;l5j{ zP^G@T8dWxFT`h}|aE`@)%EY|~VTNS)UcUh!tk;#{o~>2xtcOwtd$bjn5xvHuAq<}b zY`j}C-8(e87EA#(4K|Fv>9^|}Asz_gYS*&v2ocwdJc&2ubhbMdVRQ!3(sdpDm_@TM zulmRo0f_I@3^$H5Qk58*Nc0AZ&e)Vm(T{6<)RU%L&4ZkNan3(gyuB_j*Y@+z$0lr+ zSst>3JztIG(zj*{PJ~4`iXl}`g{0;5^ z%F2Gd25)$KjC|$hz@MaJbE&f;I;VlH^6%{ds6AFu>>;JL%>&c0(-ZqOplZvLo;F9{ zR+QS|B26r-#u%5q4J+sUX<0X%5i4jXE)#wxF>6U;5D&l>FQZ|_sk9?=K~?$_hQ<;) z(lp1zmCpmRNiqW0Ku z_nX1!kb!Bl=S1Y=>kdH3pt@|6@R)TGP1m=Y25;#|@(V+Z$W zh%-xC^D+fYQ6+9wXNH<$yWP?Ys%3DdaLrucrf^%94~D^3`5{oiGWsTLjNwMes6rXg zpH%^u2`bR&`De#{ef70^y^6ow>`gr0&delO5Q)rxPUlUnnIL-ay+#*VTDTuGl&*r= zOn*{VrPv(sxKBK;hQq}Vuct*t_G~V!w@8{Krhop89sh@h$jd>~7C{C9X#@fNZ{O4Z z9}NKy0s;VWF>`gbvbXpfz-?!{rLE(z&x!4~W1v)^Wu;Cd9pG**s3N|nZW-t-)!xGu z2}g*})0hMGcD{-@XZJ&qlB8<>PEp!8&&3vS>+xscnMel9fLCY~g@U8vq9nC5V7L^K zv?(juYH4vtSwI@#TNWQb9wrvP(F+$T=|UA^I|yJf^%#sk-{km_mq-eUe@hBVe<)^z zRRKNGs&nghZY*LN=7GYWBGV$bx%60U4%e4KBbeYr z9Rsj_w_mbR8*9PRZ+5^uSBp4QF0tda9m;?3W8pe)V?~`)V{&fjsAkv;XGjYr5);L+TO7IBg$yb)!o#2z?8UH znwqw8-h4lmq-=uYz5(1pDT;)+BFT_H2He0R`9l5{?uD$Mm(&}G1akvlsX0^U7rhP1 zvIb=v2BQ!s`e8=`hNPTrlu1~fH;3s)$v`=?P;!SOZO*87aSug|7^XW0s_BQ`(*PJB zT(4wi#2f#PK%}30f}%JyE(QU!R^o{vuOp+6+*!EUM`xwIG~azJZ5+vdJ`YBd@#KCB z8g5YgQyD_1L^=a#uw!3nb^4P)IbcUjE_ws*#Fv)ho=G+QOxXTQ9Tje^03+(uC-OG9 z=h|c(b&A8ft~brflS66d2QXfK_!wXg((r3Ozo>r}_Sc~iRdWTfUzOD><;^*X274V2 zoL+2N2pZ^!pP7))3~dVa?&>GYDKHH}27O0w>W6z_5L^|pS(lx{2ZwCmf|B<5b=3)X zdwkS>{)xI}t4?KzPpFzV?H5b+X>dcfVN$SK^1(yNOL(WWJYSG<3?~-Pa|bw?3dpAw zimT+c3Wxd)GKs@U`8B{vIT2GdA+lI*X)WrN8ysGUy)9VqeOIn5xf97$A&f8FJZFtr zLQukNApH9{ArpeOBh-Z&L?a)T0ns7S*7g$A6UVM%wmyWm!HrhB6`%yS*oRO*1HX|P zdH2tc&X^GV@&d5gi#Eh$YJPxU>X_xNM%XN9Fb6oX~KF zqD-BYZ6Se?SP!pCQA3SU?PEGYdB%EIILr45WpjR56*MnTFB(k=&KH6oQ+Y-Zzg{rW z#caAIU#0AryNj^)*Yc01gag4lRGHgzPdY2$2j6kTG?)-78?xUB00gj%FUo2gKyp*- z+$O}g7o#1)jDe*qG6(boXoPV;1A; zXAJjsvd*ACt&rmMGnxL-5cNA1xKHA+&@0>6e3GXvYQ`?`M|jwY8#yYoHC~W-`#E)- zDD@RdxbQW(+BYN|_z-|m>CW$1)sM>wU%26K?c+AG<5GdWy@(8%E~>cxUt8^0s5G*=6EJE zc$LF1Gkd#0j7OyOm?Ykck`kY?HI1Pdv5sS_oyKGw443hRSakq*o!>Hbk)|j()gQgt z_opnV6`Maf^H=KXy$WRqrPxbg^ZqOMm)aK9Utx9n?rSW0JyDmKtd2aoEmK}yX3fDf zE*;$P+pGkq-Z}=;`urS^Z%fJ7a8+gJ;B5hKZEkkVb7>gL`hoK;lw$@biCy21-Q(<% z?Qk(JJM>zD^;ZDBZFe5qs;wC@@3|>m)ah-ZDU0n{!A{ryNi-0KtiD&cY>xtMGg{nVhqEL5&Yz(@LE-7h9N@oHZa@19PnnPh@(EE-$;%V#EHU zbBpGgk_yMeW=jCa8-DvgsQR(&~EJ2ex$Cx(2awYXqsb`L<*}n%f*=DAFio$;E z=i}{=f`etpVB1|BQG+db?ylnpO)|9dK=SuQPN>!?u!*Hs;gf@s4$J=1W637+%XAyT z)gg(PMI*XNr}|=`*RxFCRKZSA%D*Bfa@G(c-Jw80IwVnieOcgO9A|l~V|X`$%G}cL zh(c+GZW!xCT~BI!lFXEe>>+GGMLhD2kNQZ)H=|cb5Ws6KSn?+PmJWx}45#)ITmig4 zUObGegGe}Wn?@U$5C9GjKKwO1vkr9$5j;v1bx0>0!Z$Lr!(mDonYeXu2sf87Y`DND z*~)Bv(0Ftb6@57A_3i{LtI0;56~%2iIIB76$}4&&N5Jv85S>L*_C~F#uTekX*Vfn{ z?^RFAbMXl%vK37m8?|XJSiTeD&rR;IqTW7a)U8k$5^@}9?NSJTY-TMo{Bz0+=zG^d z{EaYi2LETBK*7*K{wIkN_@d16zq2TToGM&g|KwHvXI#qPjLZKJL`)D6J2O|Kf1tqs zJsz(r9vD70kV%d5zd}kb)$qWSv4ODa)c>XaV~xQEYN`Iq`j0w@4fObr#xK~wJk@_S zu2#VVW5xlZs8an`-+xqV9N>h?ztsG{BNyTTd(`Ou$GU?9JXIwp|2KsBPag~Hf4n{j z2o^3dQ3YM^-wpp01^wf4`Tr!2>>mfr|M*}2J=uSE{Lc;e7bN|Mz_J2&HE@alS;zle e{l96d{~?{+z(`FZL}U|t+e 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 + +It is the application's responsility to place an available update +somewhere in Flash memory, preferrably at the very end to maximize +the available space for updating. Updating overwrites the current +firmware, leaving only the new firmware in Flash memory. + + + +As the update process is destructive, extreme care is taken to make +this step robust. It is implemented in such a way that in case of +interruption, the update can be restarted or resumed. This ensures +that even in the case of power-loss or other transient failure, the +bootloader can continue updating on the next pass, leaving the device +with a functioning firmware. + +The bootloader supports a number of different update types: + +- Plain Updates +- LZ4 Compressed Updates +- LZ4 Delta Updates +- LZ4 In-Place Delta Updates + +### Plain Updates (`plain`) + +Plain updates are the most simple way to perform an update. The full +new firmware image is contained as-is in the update blob. + + + +The bootloader then simply copies the firmware from the update blob +to its intended location. + + + +After the process is complete, the new firmware is in place and the +update can be invalidated. + + + +### LZ4 Compressed Updates (`lz4`) + +LZ4 compressed updates differ from plain updates only by using [LZ4 +compression](https://en.wikipedia.org/wiki/LZ4_(compression_algorithm)) +to reduce the size of the update. LZ4 was chosen because of the +simplicity and robustness of the decompressor. + + + +The bootloader then decompresses the firmware from the update blob +to its intended location. + + + +After the process is complete, the new firmware is in place and the +update can be invalidated. + + + +### LZ4 Delta Updates (`lz4-dict`) + +LZ4 delta updates make use of _dictionary mode_, whereby data can be +preloaded into the backreferences space. For delta updates, the +previous firmware image is used as the dictionary. This can dramatically +reduce the size of an update, but since decompression requires access +to the dictionary, the update is now specific to the source firmware +it was created for. Further, the entire dictionary must stay available +during the decompression process, which means the original firmware +cannot be decompressed directly to the intended location. + + + +LZ4 delta updates are therefore encoded as plain udpates before the +compression step. The bootloader then decompresses this plain update +blob into free Flash memory between the compressed update and the +current firmware, while using the current firmware as dictionary. + + + +Once the decompression is complete, the plain update is available +in Flash and the compressed update can be invalidated. + + + +The process then continues by using the plain update as above. + + + +After the process is complete, the new firmware is in place and the +update can be invalidated. + + + +As can be seen, while LZ4 delta updates are quite small and can thus +be transmitted very efficiently over-the-air, the two-step decompression +method uses much more Flash memory than any of the other methods. + +### LZ4 In-Place Delta Updates (`lz4-dict-inplace`) + +:construction: *Coming soon...* + +By carefully coding the update and making use of a temporary scratch +buffer, the delta update can be extracted directly to its final location +in Flash memory. This dramatically reduces the amount of free space +necessary on the device. The scratch buffer's size can be variable, +thereby allowing the trading of compression efficiency against Flash +memory requirements. + + + +The new firmware is uncompressed one block at a time into a temporary +scratch buffer, before it is copied to its final location. The update +blob contains itegrity protection data for each block to allow transactional +execution of this update method. + + + +While not the entire previous firmware is available as a dictionary +for every block with this method, it still yields compression efficiency +comparable to regular delta updates in practice. + +After the process is complete, the new firmware is in place and the +update can be invalidated. + + From 67124a908891e39b1bace7e3154b05829a6ad8d6 Mon Sep 17 00:00:00 2001 From: Michael Kuyper Date: Thu, 8 Nov 2018 23:55:10 -0500 Subject: [PATCH 03/10] fwtool: directly executable, optionally prefix output for integration in other build processes, also print size in decimal --- tools/fwtool/fwtool.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) mode change 100644 => 100755 tools/fwtool/fwtool.py diff --git a/tools/fwtool/fwtool.py b/tools/fwtool/fwtool.py old mode 100644 new mode 100755 index 3f59d45..70302d4 --- a/tools/fwtool/fwtool.py +++ b/tools/fwtool/fwtool.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # _____ _ __ _ # /__ \_ __ __ _ ___| | __ /\ \ \___| |_ # / /\/ '__/ _` |/ __| |/ // \/ / _ \ __| @@ -16,6 +17,7 @@ import argparse import struct import sys +import os from intelhex import IntelHex from binascii import crc32 @@ -173,34 +175,36 @@ def main(self, args:NS) -> int: class MkUpdateCommand(Command): - def __init__(self, subs:SPA) -> None: + def __init__(self, subs:SPA, prefix:str='') -> None: p = super().create_parser(subs, 'mkupdate', 'Create a firmware update file') p.add_argument('FWFILE', type=str, help='the firmware file to process') p.add_argument('UPFILE', type=str, help='the update file to create') + self.prefix = prefix def main(self, args:NS) -> int: - print('Processing %s' % args.FWFILE) + print('%sProcessing %s' % (self.prefix, args.FWFILE)) fw = Firmware(args.FWFILE) up = Update.create(Update.TYPE_PLAIN, fw) - print('Creating %s' % args.UPFILE) + print('%sCreating %s' % (self.prefix, args.UPFILE)) up.tofile(args.UPFILE) return 0 class PatchFwCommand(Command): - def __init__(self, subs:SPA) -> None: + def __init__(self, subs:SPA, prefix:str='') -> None: p = super().create_parser(subs, 'patch', 'Patch a firmware file with CRC and length.') p.add_argument('FWFILE', type=str, help='the firmware to process') p.add_argument('--check-only', action='store_true', help='only check CRC and length values, do not patch') + self.prefix = prefix def main(self, args:NS) -> int: - print('Processing %s' % args.FWFILE) + print('%sProcessing %s' % (self.prefix, args.FWFILE)) fw = Firmware(args.FWFILE) if not args.check_only: fw.patch() fw.tofile(args.FWFILE) - print('CRC: 0x%08x (%s)' % (fw.hcrc, 'ok' if fw.hcrc == fw.crc else 'invalid')) - print('Size: 0x%08x (%s)' % (fw.hsize, 'ok' if fw.hsize == fw.size else 'invalid')) + print('%sCRC: 0x%08x (%s)' % (self.prefix, fw.hcrc, 'ok' if fw.hcrc == fw.crc else 'invalid')) + print('%sSize: 0x%08x: %d bytes (%s)' % (self.prefix, fw.hsize, fw.hsize, 'ok' if fw.hsize == fw.size else 'invalid')) return 0 if fw.hcrc == fw.crc and fw.hsize == fw.size else 1 @@ -210,8 +214,10 @@ def main(self, args:NS) -> int: subs = parser.add_subparsers(dest='COMMAND') subs.required = True # type: ignore # http://bugs.python.org/issue9253 - PatchFwCommand(subs) - MkUpdateCommand(subs) + prefix = os.environ.get('STDOUT_PREFIX', '') + + PatchFwCommand(subs, prefix) + MkUpdateCommand(subs, prefix) args = parser.parse_args() sys.exit(args.func(args)) From ea70fb1197de9c3b5339bf473b32370b7b133a65 Mon Sep 17 00:00:00 2001 From: Michael Kuyper Date: Thu, 8 Nov 2018 23:56:50 -0500 Subject: [PATCH 04/10] make boottab platform-specific, expose crc32 function to firmware --- src/arm/stm32lx/bootloader.c | 1 + src/arm/stm32lx/bootloader_impl.h | 1 + src/arm/stm32lx/boottab.h | 31 +++++++++++++++++++++++++++++++ src/common/bootloader.h | 11 ----------- 4 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 src/arm/stm32lx/boottab.h diff --git a/src/arm/stm32lx/bootloader.c b/src/arm/stm32lx/bootloader.c index 48a0f8a..64616f7 100644 --- a/src/arm/stm32lx/bootloader.c +++ b/src/arm/stm32lx/bootloader.c @@ -356,4 +356,5 @@ __attribute__((section(".boot.boottab"))) const boot_boottab boottab = { .version = 0x100, .update = set_update, .panic = fw_panic, + .crc32 = boot_crc32, }; diff --git a/src/arm/stm32lx/bootloader_impl.h b/src/arm/stm32lx/bootloader_impl.h index 419a4f4..0b23e6e 100644 --- a/src/arm/stm32lx/bootloader_impl.h +++ b/src/arm/stm32lx/bootloader_impl.h @@ -17,6 +17,7 @@ #define _bootloader_impl_h_ #include "bootloader.h" +#include "boottab.h" // Convert pointer to Thumb function (set least-significant bit) #define THUMB_FUNC(p) ((void*) (((intptr_t) (p)) | 1)) diff --git a/src/arm/stm32lx/boottab.h b/src/arm/stm32lx/boottab.h new file mode 100644 index 0000000..12bfcc0 --- /dev/null +++ b/src/arm/stm32lx/boottab.h @@ -0,0 +1,31 @@ +/* _____ _ __ _ + * /__ \_ __ __ _ ___| | __ /\ \ \___| |_ + * / /\/ '__/ _` |/ __| |/ // \/ / _ \ __| + * / / | | | (_| | (__| '/ /\ / __/ |_ + * \_\ |_| \__,_|\___|_|\_\_\ \_\\___|\__| + * + * Copyright (c) 2016-2018 Trackio International AG + * All rights reserved. + * + * This file is subject to the terms and conditions + * defined in file 'LICENSE', which is part of this + * source code package. + * + */ + +#ifndef _boottab_h_ +#define _boottab_h_ + +#include "bootloader.h" + +// Bootloader information table on STM32 + +typedef struct { + uint32_t version; // version of boot loader (values below 256 are reserved for legacy bootloaders) + __attribute__((noreturn)) + void (*panic) (uint32_t reason, uint32_t addr); // bootloader panic function + uint32_t (*update) (void* ptr, hash32* hash); // function to set firmware update pointer + uint32_t (*crc32) (void* buf, uint32_t nwords); // calculate CRC32 +} boot_boottab; + +#endif diff --git a/src/common/bootloader.h b/src/common/bootloader.h index 5dd8af2..58e2534 100644 --- a/src/common/bootloader.h +++ b/src/common/bootloader.h @@ -69,17 +69,6 @@ typedef union { } hash32; -// Bootloader information table -// TODO: move to platform-specific section ? -typedef struct { - uint32_t version; // version of boot loader (values below 256 are reserved for legacy bootloaders) - __attribute__((noreturn)) - void (*panic) (uint32_t reason, uint32_t addr); // bootloader panic function - uint32_t (*update) (void* ptr, hash32* hash); // function to set firmware update pointer - // TODO: extensions -} boot_boottab; - - // Firmware header typedef struct { uint32_t crc; // firmware CRC From 2bb3be1d97d1558664a9d789a09907ff172a9ab0 Mon Sep 17 00:00:00 2001 From: Michael Kuyper Date: Fri, 9 Nov 2018 22:44:44 -0500 Subject: [PATCH 05/10] fixed missing include --- src/common/update.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/update.h b/src/common/update.h index e84003f..0f4def0 100644 --- a/src/common/update.h +++ b/src/common/update.h @@ -16,6 +16,8 @@ #ifndef _update_h_ #define _update_h_ +#include "bootloader.h" + uint32_t update (void* ctx, boot_uphdr* fwup, bool install); // glue functions From c397df2934b5213447346e0aca8675b1fc359e5d Mon Sep 17 00:00:00 2001 From: Michael Kuyper Date: Fri, 9 Nov 2018 22:47:01 -0500 Subject: [PATCH 06/10] support for unicorn-based simulation --- build/boards/simul-unicorn/Makefile | 3 + build/makefiles/unicorn.mk | 47 ++++++ src/arm/unicorn/bootloader.c | 227 ++++++++++++++++++++++++++++ src/arm/unicorn/bootloader_impl.h | 40 +++++ src/arm/unicorn/boottab.h | 41 +++++ src/arm/unicorn/ld/bootloader.ld | 28 ++++ src/arm/unicorn/ld/mem.ld | 20 +++ 7 files changed, 406 insertions(+) create mode 100644 build/boards/simul-unicorn/Makefile create mode 100644 build/makefiles/unicorn.mk create mode 100644 src/arm/unicorn/bootloader.c create mode 100644 src/arm/unicorn/bootloader_impl.h create mode 100644 src/arm/unicorn/boottab.h create mode 100644 src/arm/unicorn/ld/bootloader.ld create mode 100644 src/arm/unicorn/ld/mem.ld diff --git a/build/boards/simul-unicorn/Makefile b/build/boards/simul-unicorn/Makefile new file mode 100644 index 0000000..9fcc53d --- /dev/null +++ b/build/boards/simul-unicorn/Makefile @@ -0,0 +1,3 @@ +FLAVOR := unicorn + +include ../main.mk diff --git a/build/makefiles/unicorn.mk b/build/makefiles/unicorn.mk new file mode 100644 index 0000000..4efe56b --- /dev/null +++ b/build/makefiles/unicorn.mk @@ -0,0 +1,47 @@ +include $(MKDIR)/arm.mk + +VPATH += $(SRCDIR)/arm/unicorn +VPATH += $(SRCDIR)/common +VPATH += $(SRCDIR)/common/micro-ecc + +SRCS += bootloader.c + +SRCS += update.c +#SRCS += lz4.c +#SRCS += sha2.c +#SRCS += uECC.c + +#DEFS += LZ4_FLASHWRITE +#DEFS += LZ4_PAGEBUFFER_SZ=64 +DEFS += UP_PAGEBUFFER_SZ=64 + +FLAGS += -mcpu=cortex-m0plus +FLAGS += -I$(SRCDIR)/common + +CFLAGS += -Wall +CFLAGS += -Os + +LDFLAGS += -T$(SRCDIR)/arm/unicorn/ld/mem.ld +LDFLAGS += -T$(SRCDIR)/arm/unicorn/ld/bootloader.ld + +OBJS = $(addsuffix .o,$(basename $(SRCS))) + +bootloader: $(OBJS) + +bootloader.hex: bootloader + $(HEX) $< $@ + +default: bootloader.hex + + +clean: + rm -f *.o *.d *.map bootloader bootloader.hex bootloader.bin + +.PHONY: clean default + + +MAKE_DEPS := $(MAKEFILE_LIST) # before we include all the *.d files + +-include $(OBJS:.o=.d) + +$(OBJS): $(MAKE_DEPS) diff --git a/src/arm/unicorn/bootloader.c b/src/arm/unicorn/bootloader.c new file mode 100644 index 0000000..cd806fc --- /dev/null +++ b/src/arm/unicorn/bootloader.c @@ -0,0 +1,227 @@ +/* _____ _ __ _ + * /__ \_ __ __ _ ___| | __ /\ \ \___| |_ + * / /\/ '__/ _` |/ __| |/ // \/ / _ \ __| + * / / | | | (_| | (__| '/ /\ / __/ |_ + * \_\ |_| \__,_|\___|_|\_\_\ \_\\___|\__| + * + * Copyright (c) 2016-2018 Trackio International AG + * All rights reserved. + * + * This file is subject to the terms and conditions + * defined in file 'LICENSE', which is part of this + * source code package. + * + */ + +#include "update.h" +#include "boottab.h" + + +// ------------------------------------------------ +// Memory + +extern uint32_t _estack; +extern uint32_t _ebl; + +#define RAM_BASE 0x10000000 +#define RAM_SIZE (16 * 1024) +#define FLASH_BASE 0x20000000 +#define FLASH_SIZE (128 * 1024) +#define EEPROM_BASE 0x30000000 +#define EEPROM_SIZE (8 * 1024) + +#define FW_BASE ((uint32_t) (&_ebl)) +#define CONFIG_BASE EEPROM_BASE + + +// ------------------------------------------------ +// CRC-32 + +static void crc32 (uint32_t* pcrc, unsigned char* buf, uint32_t len) { + int i; + uint32_t byte, crc, mask; + + crc = ~(*pcrc); + while (len-- != 0) { + byte = *buf++; + crc = crc ^ byte; + for (i = 7; i >= 0; i--) { + mask = -(crc & 1); + crc = (crc >> 1) ^ (0xEDB88320 & mask); + } + } + *pcrc = ~crc; +} + +static uint32_t boot_crc32 (void* buf, uint32_t nwords) { + uint32_t crc = 0; + crc32(&crc, buf, nwords * 4); + return crc; +} + + +// ------------------------------------------------ +// Supervisor call + +__attribute__((naked, noinline)) +static void svc (uint32_t id, uint32_t p1, uint32_t p2, uint32_t p3) { + asm("svc 0"); + __builtin_unreachable(); +} + + +// ------------------------------------------------ +// Panic + +__attribute__((noreturn, naked, noinline)) +static void fw_panic (uint32_t reason, uint32_t addr) { + svc(BOOT_SVC_PANIC, BOOT_PANIC_TYPE_FIRMWARE, reason, addr); + __builtin_unreachable(); +} + +__attribute__((noreturn, naked, noinline)) +static void boot_panic (uint32_t reason) { + svc(BOOT_SVC_PANIC, BOOT_PANIC_TYPE_BOOTLOADER, reason, 0); + __builtin_unreachable(); +} + + +// ------------------------------------------------ +// Flash functions + + +// ------------------------------------------------ +// Update glue functions + +uint32_t up_install_init (void* ctx, uint32_t size, void** pdst) { + return BOOT_OK; +} + +void up_flash_wr_page (void* ctx, void* dst, void* src) { +} + +void up_flash_unlock (void* ctx) { +} + +void up_flash_lock (void* ctx) { +} + + +// ------------------------------------------------ +// Update functions + +typedef struct { + uint32_t fwupdate1; // 0x00 pointer to valid update + uint32_t fwupdate2; // 0x04 pointer to valid update + hash32 hash; // 0x08 SHA-256 hash of valid update + + uint8_t rfu[24]; // 0x28 RFU +} boot_config; + +static void do_install (boot_uphdr* fwup) { +#if 0 + uint32_t funcbuf[WR_FL_HP_WORDS]; + up_ctx uc = { + .wf_func = prep_wr_fl_hp(funcbuf), + .fwup = fwup, + }; + update(&uc, fwup, true); +#endif +} + +static uint32_t set_update (void* ptr, hash32* hash) { +#if 0 + uint32_t rv; + if (ptr == NULL) { + rv = BOOT_OK; + } else { + rv = update(NULL, ptr, false); + } + if (rv == BOOT_OK) { + boot_config* cfg = (boot_config*) BOOT_CONFIG_BASE; + // unlock EEPROM + FLASH->PEKEYR = 0x89ABCDEF; // FLASH_PEKEY1 + FLASH->PEKEYR = 0x02030405; // FLASH_PEKEY2 + // copy hash + if (hash) { + for (int i = 0; i < 8; i++) { + ee_write(&cfg->hash.w[i], hash->w[i]); + } + } + // set update pointer + ee_write(&cfg->fwupdate1, (uint32_t) ptr); + ee_write(&cfg->fwupdate2, (uint32_t) ptr); + // relock EEPROM + FLASH->PECR |= FLASH_PECR_PELOCK; + } + return rv; +#endif + return 0; +} + + +// ------------------------------------------------ +// Bootloader information table + +static const boot_boottab boottab = { + .version = 0x100, + .update = set_update, + .panic = fw_panic, + .crc32 = boot_crc32, + .svc = svc, +}; + +// ------------------------------------------------ +// Bootloader main entry point + +void* bootloader (void) { + boot_fwhdr* fwh = (boot_fwhdr*) FW_BASE; + boot_config* cfg = (boot_config*) CONFIG_BASE; + + uint32_t flash_sz = FLASH_SIZE; + + if (cfg->fwupdate1 == cfg->fwupdate2) { + boot_uphdr* fwup = (boot_uphdr*) cfg->fwupdate1; + if (fwup != NULL + && ((intptr_t) fwup & 3) == 0 + && (intptr_t) fwup >= FLASH_BASE + && sizeof(boot_uphdr) <= flash_sz - ((intptr_t) fwup - FLASH_BASE) + && fwup->size >= sizeof(boot_uphdr) + && (fwup->size & 3) == 0 + && fwup->size <= flash_sz - ((intptr_t) fwup - FLASH_BASE) + && boot_crc32(((unsigned char*) fwup) + 8, (fwup->size - 8) >> 2) == fwup->crc + && true /* TODO hardware id match */ ) { + do_install(fwup); + } + } + + // verify integrity of current firmware + if (fwh->size < sizeof(boot_fwhdr) + || fwh->size > (flash_sz - (FW_BASE - FLASH_BASE)) + || boot_crc32(((unsigned char*) fwh) + 8, (fwh->size - 8) >> 2) != fwh->crc) { + boot_panic(BOOT_PANIC_REASON_CRC); + } + + // clear fwup pointer in EEPROM if set + if (cfg->fwupdate1 != 0 || cfg->fwupdate2 != 0) { + set_update(NULL, NULL); + } + + // call entry point + ((void (*) (const boot_boottab*)) fwh->entrypoint)(&boottab); + + // not reached + boot_panic(BOOT_PANIC_REASON_FWRETURN); +} + + +// ------------------------------------------------ +// Bootloader header + +__attribute__((section(".boot.header"))) const struct { + uint32_t init_sp; + uint32_t init_pc; +} boothdr = { + .init_sp = (uint32_t) &_estack, + .init_pc = (uint32_t) bootloader, +}; diff --git a/src/arm/unicorn/bootloader_impl.h b/src/arm/unicorn/bootloader_impl.h new file mode 100644 index 0000000..cc3f401 --- /dev/null +++ b/src/arm/unicorn/bootloader_impl.h @@ -0,0 +1,40 @@ +/* _____ _ __ _ + * /__ \_ __ __ _ ___| | __ /\ \ \___| |_ + * / /\/ '__/ _` |/ __| |/ // \/ / _ \ __| + * / / | | | (_| | (__| '/ /\ / __/ |_ + * \_\ |_| \__,_|\___|_|\_\_\ \_\\___|\__| + * + * Copyright (c) 2016-2018 Trackio International AG + * All rights reserved. + * + * This file is subject to the terms and conditions + * defined in file 'LICENSE', which is part of this + * source code package. + * + */ + +#ifndef _bootloader_impl_h_ +#define _bootloader_impl_h_ + +#include "bootloader.h" +#include "boottab.h" + +extern uint32_t _ebl; +#define BOOT_FW_BASE ((uint32_t) (&_ebl)) + +#define BOOT_CONFIG_BASE 0x30000000 +#define BOOT_CONFIG_SZ 64 + + +// ------------------------------------------------ +// Bootloader configuration + +typedef struct { + uint32_t fwupdate1; // 0x00 pointer to valid update + uint32_t fwupdate2; // 0x04 pointer to valid update + hash32 hash; // 0x08 SHA-256 hash of valid update + + uint8_t rfu[24]; // 0x28 RFU +} boot_config; + +#endif diff --git a/src/arm/unicorn/boottab.h b/src/arm/unicorn/boottab.h new file mode 100644 index 0000000..70e482d --- /dev/null +++ b/src/arm/unicorn/boottab.h @@ -0,0 +1,41 @@ +/* _____ _ __ _ + * /__ \_ __ __ _ ___| | __ /\ \ \___| |_ + * / /\/ '__/ _` |/ __| |/ // \/ / _ \ __| + * / / | | | (_| | (__| '/ /\ / __/ |_ + * \_\ |_| \__,_|\___|_|\_\_\ \_\\___|\__| + * + * Copyright (c) 2016-2018 Trackio International AG + * All rights reserved. + * + * This file is subject to the terms and conditions + * defined in file 'LICENSE', which is part of this + * source code package. + * + */ + +#ifndef _boottab_h_ +#define _boottab_h_ + +#include "bootloader.h" + +// Bootloader information table on unicorn + +typedef struct { + uint32_t version; // version of boot loader + __attribute__((noreturn)) + void (*panic) (uint32_t reason, uint32_t addr); // bootloader panic function + uint32_t (*update) (void* ptr, hash32* hash); // function to set firmware update pointer + uint32_t (*crc32) (void* buf, uint32_t nwords); // calculate CRC32 + void* svc; // supervisor call +} boot_boottab; + + +// Supervisor call IDs + +enum { + BOOT_SVC_PANIC, // panic: p1=type, p2=reason, p3=addr + + BOOT_SVC_FWBASE = 0x80 // all supervisor call ids below are reserved for bootloader +}; + +#endif diff --git a/src/arm/unicorn/ld/bootloader.ld b/src/arm/unicorn/ld/bootloader.ld new file mode 100644 index 0000000..9674d43 --- /dev/null +++ b/src/arm/unicorn/ld/bootloader.ld @@ -0,0 +1,28 @@ +/* _____ _ __ _ + * /__ \_ __ __ _ ___| | __ /\ \ \___| |_ + * / /\/ '__/ _` |/ __| |/ // \/ / _ \ __| + * / / | | | (_| | (__| '/ /\ / __/ |_ + * \_\ |_| \__,_|\___|_|\_\_\ \_\\___|\__| + * + * Copyright (c) 2016-2018 Trackio International AG + * All rights reserved. + * + * This file is subject to the terms and conditions + * defined in file 'LICENSE', which is part of this + * source code package. + * + */ + +_estack = ORIGIN(RAM) + LENGTH(RAM); +_ebl = ORIGIN(BLFLASH) + LENGTH(BLFLASH); + +SECTIONS { + .boot : { + . = ALIGN(4); + KEEP(*(.boot.header)) + . = ALIGN(4); + *(.boot*) + *(.text*) + *(.rodata*) + } >BLFLASH +} diff --git a/src/arm/unicorn/ld/mem.ld b/src/arm/unicorn/ld/mem.ld new file mode 100644 index 0000000..a621825 --- /dev/null +++ b/src/arm/unicorn/ld/mem.ld @@ -0,0 +1,20 @@ +/* _____ _ __ _ + * /__ \_ __ __ _ ___| | __ /\ \ \___| |_ + * / /\/ '__/ _` |/ __| |/ // \/ / _ \ __| + * / / | | | (_| | (__| '/ /\ / __/ |_ + * \_\ |_| \__,_|\___|_|\_\_\ \_\\___|\__| + * + * Copyright (c) 2016-2018 Trackio International AG + * All rights reserved. + * + * This file is subject to the terms and conditions + * defined in file 'LICENSE', which is part of this + * source code package. + * + */ + +MEMORY { + RAM(xrw) : ORIGIN = 0x10000000, LENGTH = 16K + BLFLASH(rx) : ORIGIN = 0x20000000, LENGTH = 4K + FWFLASH(rx) : ORIGIN = 0x20000000 + LENGTH(BLFLASH), LENGTH = 128K - LENGTH(BLFLASH) +} From 59a10cf01d7717847e140e44f18df56f1ac2307d Mon Sep 17 00:00:00 2001 From: Michael Kuyper Date: Mon, 12 Nov 2018 17:09:47 -0500 Subject: [PATCH 07/10] handle the case where the SVC call returns --- src/arm/unicorn/bootloader.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/arm/unicorn/bootloader.c b/src/arm/unicorn/bootloader.c index cd806fc..b5c476d 100644 --- a/src/arm/unicorn/bootloader.c +++ b/src/arm/unicorn/bootloader.c @@ -60,19 +60,11 @@ static uint32_t boot_crc32 (void* buf, uint32_t nwords) { } -// ------------------------------------------------ -// Supervisor call - -__attribute__((naked, noinline)) -static void svc (uint32_t id, uint32_t p1, uint32_t p2, uint32_t p3) { - asm("svc 0"); - __builtin_unreachable(); -} - - // ------------------------------------------------ // Panic +static void svc (uint32_t id, uint32_t p1, uint32_t p2, uint32_t p3); // fwd decl + __attribute__((noreturn, naked, noinline)) static void fw_panic (uint32_t reason, uint32_t addr) { svc(BOOT_SVC_PANIC, BOOT_PANIC_TYPE_FIRMWARE, reason, addr); @@ -86,6 +78,18 @@ static void boot_panic (uint32_t reason) { } + +// ------------------------------------------------ +// Supervisor call + +__attribute__((naked, noinline)) +static void svc (uint32_t id, uint32_t p1, uint32_t p2, uint32_t p3) { + asm("svc 0"); + // SVC call should not return, but might if not handled correctly + boot_panic(0xdeadbeef); +} + + // ------------------------------------------------ // Flash functions From e2e6f8077113c4aae9ad00ab0d3ec79e68fa51ee Mon Sep 17 00:00:00 2001 From: Michael Kuyper Date: Mon, 10 Dec 2018 13:57:53 -0500 Subject: [PATCH 08/10] avoid common filename conflict --- src/arm/stm32lx/bootloader.c | 2 +- src/arm/stm32lx/{hw.h => bootloader_hw.h} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/arm/stm32lx/{hw.h => bootloader_hw.h} (97%) diff --git a/src/arm/stm32lx/bootloader.c b/src/arm/stm32lx/bootloader.c index 64616f7..e9e1d83 100644 --- a/src/arm/stm32lx/bootloader.c +++ b/src/arm/stm32lx/bootloader.c @@ -15,7 +15,7 @@ #include "bootloader_impl.h" #include "update.h" -#include "hw.h" +#include "bootloader_hw.h" // ------------------------------------------------ diff --git a/src/arm/stm32lx/hw.h b/src/arm/stm32lx/bootloader_hw.h similarity index 97% rename from src/arm/stm32lx/hw.h rename to src/arm/stm32lx/bootloader_hw.h index 5ecd70e..f80b708 100644 --- a/src/arm/stm32lx/hw.h +++ b/src/arm/stm32lx/bootloader_hw.h @@ -13,8 +13,8 @@ * */ -#ifndef _hw_h_ -#define _hw_h_ +#ifndef _bootloader_hw_h_ +#define _bootloader_hw_h_ // ------------------------------------------------ #if defined(STM32L0) From 53e0e7e39384442371f7d803d5f361632f43e18f Mon Sep 17 00:00:00 2001 From: Michael Kuyper Date: Tue, 11 Dec 2018 10:35:22 -0500 Subject: [PATCH 09/10] fixed fwtool for non-contiguous hex-files --- tools/fwtool/fwtool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/fwtool/fwtool.py b/tools/fwtool/fwtool.py index 70302d4..8462802 100755 --- a/tools/fwtool/fwtool.py +++ b/tools/fwtool/fwtool.py @@ -36,7 +36,7 @@ def __init__(self, fw:Union[bytearray,bytes,str,BinaryIO], be:Optional[bool]=Non ih = IntelHex() ih.loadhex(fw) self.base = ih.minaddr() - self.fw = bytearray(ih.gets(self.base, (ih.maxaddr() + 1) - self.base)) + self.fw = bytearray(ih.tobinstr()) else: with open(fw, 'rb') as f: self.fw = bytearray(f.read()) From f2bcd6cab925e7433724bde92495cce71e484d86 Mon Sep 17 00:00:00 2001 From: Michael Kuyper Date: Wed, 19 Dec 2018 16:56:09 -0500 Subject: [PATCH 10/10] fixed bug in GPIO macro --- src/arm/stm32lx/bootloader_hw.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arm/stm32lx/bootloader_hw.h b/src/arm/stm32lx/bootloader_hw.h index f80b708..110ac67 100644 --- a/src/arm/stm32lx/bootloader_hw.h +++ b/src/arm/stm32lx/bootloader_hw.h @@ -52,7 +52,7 @@ // ------------------------------------------------ // GPIO definition -#define GPIO(p,n,flags) ((('A'-(p)) << 8) | (n) | (flags)) +#define GPIO(p,n,flags) ((((p)-'A') << 8) | (n) | (flags)) #define GPIO_F_ACTLOW (1 << 16)