From 40677f724deb96ce81641311e2e8d66cc721ff36 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sat, 29 Aug 2026 20:24:25 +0300 Subject: [PATCH 1/2] Give SigmaStar and Ingenic the layout their bootloader has `FlashLayout::EIGHT_MEG_LAYOUT_VENDORS = %w[SigmaStar Ingenic]` was removed in #78 on the grounds that their bootloaders define mtdpartsnor16m and the uknor16m/urnor16m pair "down to the byte", the same as the HiSilicon and Goke ones. The repositories that carry those definitions -- u-boot-msc313e, u-boot-t20, u-boot-t40 -- are real, and no released binary is built from any of them, so no camera runs them. OpenIPC/firmware's .github/workflows/uboot.yml is what builds the bootloaders this site links. It clones openipc/u-boot-sigmastar and openipc/u-boot-ingenic and runs their build.sh; the SigmaStar one's spinor loop is `ssc377 ssc377d ssc377de ssc377qe ssc378de ssc378qe` against include/configs/infinity6c.h, which includes configs/sstar-common.h. That header is the whole environment every SSC3xx NOR camera boots with: kernaddr=0x50000 kernsize=0x200000 rootaddr=0x250000 rootsize=0x500000 rootmtd=5120k uknor / urnor / ubnor CONFIG_BOOTARGS "... mtdparts=NOR_FLASH:256k(boot),64k(env),2048k(kernel), ${rootmtd}(rootfs),-(rootfs_data) ..." A repo-wide grep for uknor8m|uknor16m|urnor8m|urnor16m|setnor8m|setnor16m| mtdpartsnor returns nothing in either repo. u-boot-ingenic's isvp_common.h carries the same unsuffixed uknor/urnor. **The 16MB image cannot boot.** One mtdparts string, a 2048k kernel inside it, and only ${rootmtd} varying: the rootfs starts at 0x250000 at every chip size. Handing these two NOR[16] put it at 0x350000. The image openipc.org serves for ssc377qe today has "hsqs" at 0x350000 and 0xff at 0x250000, and its env region at 0x40000 is blank -- a full-image flash erases the env, so the camera comes up on the compiled-in bootargs, looks for the rootfs at 0x250000, finds erased flash and panics on root mount. The 8MB-layout image of the same page has it at 0x250000 and boots. Since #130 the 16MB layout is what a 16MB chip selects by default, so this is what a SigmaStar visitor gets by pressing the button. **None of the macros exist.** `run setnor16m`, `run uknor16m; run urnor16m` and the printenv hint under them name variables no SigmaStar or Ingenic camera has. U-Boot answers `## Error: "uknor16m" not defined` and flashes nothing, with no failure the reader can see. This predates #78 -- the expert section has emitted suffixed macros for these vendors since it existed. So the pin was right for these two, even though its own comment gave a wrong reason for it. What replaces it is the same fact stated properly: they have one mtdparts, so 16MB means rootmtd=10240k at the same offset rather than a rootfs 1MB further up, and their macros are `uknor`/`urnor` with nothing after them. That keeps Ultimate reachable, which a flat pin to the 8MB numbers would not: its rootfs is 7832KB on ssc338q, 7252KB on ssc30kq and 6772KB on t31 against the 5120KB rootmtd defaults to. rootmtd is a plain variable, saved unexpanded and expanded at boot by `cmdnor`, so the step that a macro does elsewhere is a setenv here: setenv rootmtd 10240k; setenv rootsize 0xA00000 saveenv reset and where the layout is already the bootloader's own, that step is dropped rather than rendered as a `run setnor8m` under a heading calling it required. @flash_type_command is gone with it. It was the layout name, which is the macro suffix only for the bootloaders that name their macros after the layout, and the view read it in four places -- so the offsets and the commands could disagree. Camera answers both now, off the same suffix. --- app/controllers/cameras/socs_controller.rb | 18 ++-- app/helpers/installation_helper.rb | 29 ++++--- app/models/camera.rb | 63 +++++++++++++- app/models/firmware.rb | 7 +- app/models/flash_layout.rb | 81 +++++++++++++----- app/views/cameras/socs/update.html.erb | 44 +++++----- test/controllers/socs_controller_test.rb | 95 +++++++++++++++------- test/models/camera_test.rb | 90 ++++++++++++++++++++ test/models/firmware_test.rb | 28 +++++++ 9 files changed, 360 insertions(+), 95 deletions(-) diff --git a/app/controllers/cameras/socs_controller.rb b/app/controllers/cameras/socs_controller.rb index f71afa5..56b3266 100644 --- a/app/controllers/cameras/socs_controller.rb +++ b/app/controllers/cameras/socs_controller.rb @@ -107,26 +107,24 @@ def update # Against FLASH_CHIP rather than for blankness, because a chip this site # does not know is not a choice either. Camera#flash_size_hex and friends # fall through to their 8MB branch for anything unrecognised, while - # @flash_type_command below would go on to render `run setnor64m` and a - # printenv hint naming three variables no bootloader defines. Nothing + # Camera#bootloader_macro_suffix would go on to render `run setnor64m` and + # a printenv hint naming three variables no bootloader defines. Nothing # calls valid? on a Camera, so this is the only thing standing between the # query string and the commands. @camera.flash_type = @camera.soc.default_flash_chip unless @camera.flash_type.in?(Camera::FLASH_CHIP) - # The bootloader macros are named after the layout, not the chip. This - # used to be the flash type with `nor32m` rewritten to `nor16m`, which is - # the same answer for every combination the menu could then produce -- - # there is no mtdpartsnor32m anywhere upstream, so a 32MB part has always - # worn the 16MB layout. Camera#partition_layout says it directly now, and - # says it for the 8MB-layout-on-a-larger-chip case too. - # # After the flash type has settled, not before: the layout defaults to # the chip's own, so reading it first left the page telling a 16MB camera # to `run urnor16m` and then erasing from the 8MB overlay offset, 733,184 # bytes into what it had just written. That is the failure #60 described, # by another route. + # + # What the macros are called is Camera's answer now rather than a separate + # @flash_type_command read here. It was the layout name, which is right + # for the bootloaders that name their macros after the layout and wrong + # for the two that do not name them after anything -- and the view read it + # four times, so the offsets and the commands could disagree. warn_if_layout_changed permitted_params[:partition_layout] - @flash_type_command = @camera.partition_layout if @vendor.name.eql?("SigmaStar") && @camera.flash_type.eql?("nand") render 'cameras/socs/sigmastar_nand_is_weird' diff --git a/app/helpers/installation_helper.rb b/app/helpers/installation_helper.rb index b3ab054..bb004c7 100644 --- a/app/helpers/installation_helper.rb +++ b/app/helpers/installation_helper.rb @@ -147,7 +147,12 @@ def flashing_uboot(c) list_of_commands text end - def flashing_linux(c, c2) + # The suffix comes off the camera rather than being passed in beside it: the + # macros are named for the bootloader's own environment, and on SigmaStar and + # Ingenic that is `uknor`/`urnor` with nothing after it whatever layout is + # being installed. + def flashing_linux(c) + c2 = c.bootloader_macro_suffix text = [] text << do_not_copy_paste unless c.network_interface.eql?('wifi') @@ -179,22 +184,26 @@ def flashing_linux(c, c2) list_of_commands text end - # The three bootloader variables the instructions above actually named, for - # the hint that tells the reader to go and look them up. preparing_environment - # emits `run set…` and flashing_linux emits `run uk…; run ur…`, all from the - # same flash_type_command, so building the hint from it too keeps the three - # in step -- including the nor32m -> nor16m rewrite the controller does. + # The bootloader variables the instructions above actually named, for the hint + # that tells the reader to go and look them up. Camera builds the list from + # the same suffix the commands are built from, so the two stay in step -- + # including the nor32m -> nor16m rewrite and the vendors whose macros carry no + # suffix and have no `set…` to name. # # It used to be a fixed `uknor*, urnor*, setnor*`, which named nothing a NAND # reader had been given and nothing they could find in their own printenv. - def bootloader_variables_html(flash_type_command) - safe_join(%w[uk ur set].map { |prefix| tag.code("#{prefix}#{flash_type_command}") }, ', ') + def bootloader_variables_html(camera) + safe_join(camera.bootloader_variables.map { |name| tag.code(name) }, ', ') end - def preparing_environment(c2) + # Put the bootloader on the layout that was just flashed. One macro where + # there is one, and the `setenv` that macro would have done where there is + # not -- see Camera#layout_commands. Nothing at all when the layout is already + # the bootloader's default, which is why every caller checks first. + def preparing_environment(camera) text = [] text << do_not_copy_paste - text << "run set#{c2}" + text.concat(camera.layout_commands) list_of_commands text end diff --git a/app/models/camera.rb b/app/models/camera.rb index 6b31172..eed29c7 100644 --- a/app/models/camera.rb +++ b/app/models/camera.rb @@ -241,6 +241,63 @@ def partition_layout_name I18n.t("flash_layout.#{partition_layout}") end + # Whether this camera's bootloader carries one NOR mtdparts string and + # unsuffixed macros -- see FlashLayout. NAND is a separate environment with + # its own uknand/urnand/setnand and is not affected either way. + def fixed_mtdparts? + !nand? && FlashLayout.fixed_mtdparts?(soc&.vendor&.name) + end + + # The suffix this camera's bootloader macros actually carry. `uknor8m` and + # friends on HiSilicon and Goke, `uknand` on NAND, and plain `uknor`/`urnor` + # on SigmaStar and Ingenic, whose environment has no suffixed macro at all -- + # the page has been telling those cameras to `run uknor16m` since it first + # had an expert section, and U-Boot has been answering `## Error: "uknor16m" + # not defined` and flashing nothing. + def bootloader_macro_suffix + return 'nand' if nand? + return 'nor' if fixed_mtdparts? + + partition_layout + end + + # Whether the bootloader already boots this layout without being told. Every + # one of them defaults to the 8MB partitions, and a full-image flash leaves + # the env erased, so that default is what a freshly flashed camera comes up + # with. + # + # NAND is not one of them: mtdpartsubi is not a default anything falls back + # to, and a NAND camera has always been told to `run setnand` after a full + # image like it is now. + def default_bootloader_layout? + !nand? && layout_size <= 8 + end + + # What to run to put the bootloader on this layout, if anything. + # + # HiSilicon and Goke have a macro for it. SigmaStar and Ingenic do not: their + # mtdparts is one string with ${rootmtd} in it, saved unexpanded and expanded + # at boot by `cmdnor`, so the layout is changed by setting that variable and + # the erase length that goes with it. Empty when there is nothing to change, + # which is what rootmtd=5120k already is. + def layout_commands + return ["run set#{bootloader_macro_suffix}"] unless fixed_mtdparts? + return [] if default_bootloader_layout? + + ["setenv rootmtd #{rootfs_max_size.to_i(16) / 1024}k; setenv rootsize #{rootfs_max_size}", + 'saveenv', 'reset'] + end + + # The bootloader variables the instructions above actually named, for the hint + # that tells the reader to go and look them up. Built from the same suffix the + # commands are, so the two cannot drift -- and without a `set…` entry where no + # such variable exists, since the reader would not find it in their printenv. + def bootloader_variables + names = %w[uk ur].map { |prefix| "#{prefix}#{bootloader_macro_suffix}" } + names << "set#{bootloader_macro_suffix}" unless fixed_mtdparts? + names + end + # The NOR numbers come from FlashLayout, which reads them off the bootloader # environment. They used to be spelled out here keyed on firmware_version, # which agreed with the bootloader only for 8MB+Lite and 16MB+Ultimate; see @@ -249,8 +306,12 @@ def partition_layout_name # Keyed on the layout, not on the chip. The two agree for every combination # the menu offered before it grew a second field, and the whole point of the # second field is the ones where they do not. + # + # The vendor goes with it because two of them have a bootloader whose rootfs + # offset does not move between layouts. Without it a 16MB SigmaStar or Ingenic + # camera is handed 0x350000, which its bootloader never reads. def nor_layout - FlashLayout.nor(layout_size) + FlashLayout.nor(layout_size, soc&.vendor&.name) end def kernel_max_size diff --git a/app/models/firmware.rb b/app/models/firmware.rb index a0e586a..b646865 100644 --- a/app/models/firmware.rb +++ b/app/models/firmware.rb @@ -448,9 +448,12 @@ def rootfs_member # Same table the installation page renders from, so the image and the # instructions cannot describe different partition layouts. Keyed on the # layout rather than the size, which are the same thing for every image built - # before the wizard could tell them apart. + # before the wizard could tell them apart, and on the vendor, because + # SigmaStar and Ingenic have one mtdparts string whose rootfs offset does not + # move -- an image that put it where the other table says is one their + # bootloader cannot boot. def nor_layout - @nor_layout ||= FlashLayout.nor(@layout) + @nor_layout ||= FlashLayout.nor(@layout, @soc.vendor.name) end def kernel_offset diff --git a/app/models/flash_layout.rb b/app/models/flash_layout.rb index 2e4ce6a..0426f6b 100644 --- a/app/models/flash_layout.rb +++ b/app/models/flash_layout.rb @@ -33,30 +33,69 @@ class FlashLayout overlay_offset: 0xD50000 }.freeze }.freeze - # SigmaStar and Ingenic used to be pinned to the 8MB offsets whatever chip was - # chosen, on the grounds that their bootloaders defined no uknor/urnor macros - # to check against. They do. The repositories checked were u-boot-sigmastar - # and u-boot-ingenic, which are not what those SoCs ship; the real ones are - # per-SoC, and u-boot-t20, u-boot-t40 and u-boot-msc313e all carry the same - # pair as the Hisilicon and Goke bootloaders above, down to the byte: + # SigmaStar and Ingenic do not have that pair, and the bootloaders that do are + # not the ones those SoCs ship. # - # mtdpartsnor16m = 256k(boot),64k(env),3072k(kernel),10240k(rootfs),-(rootfs_data) - # uknor16m : sf erase 0x50000 0x300000 urnor16m : sf erase 0x350000 0xa00000 + # OpenIPC/firmware's .github/workflows/uboot.yml is what builds the binaries + # this site links. It clones openipc/u-boot-sigmastar and openipc/u-boot- + # ingenic and runs their build.sh; the SigmaStar one's spinor loop is + # `ssc377 ssc377d ssc377de ssc377qe ssc378de ssc378qe` against + # include/configs/infinity6c.h, which includes configs/sstar-common.h. That + # header is the entire environment every SSC3xx NOR camera boots with: # - # Only the mtd device name differs -- jz_sfc, NOR_FLASH, sfc. + # kernaddr=0x50000 kernsize=0x200000 + # rootaddr=0x250000 rootsize=0x500000 rootmtd=5120k + # uknor / urnor / ubnor <- no size suffix, and no setnor* at all + # CONFIG_BOOTARGS "... mtdparts=NOR_FLASH:256k(boot),64k(env),2048k(kernel), + # ${rootmtd}(rootfs),-(rootfs_data) ..." # - # The pin also could not survive Ultimate on 16MB. Ultimate's NOR rootfs is - # 7820KB on ssc338q and 6752KB on t31, and the 8MB layout gives rootfs 5120KB. - # There is no arrangement in which those two vendors offer Ultimate on a 16MB - # chip and keep the 8MB geometry. + # A repo-wide grep for uknor8m|uknor16m|urnor8m|urnor16m|setnor8m|setnor16m| + # mtdpartsnor returns nothing in either repo; u-boot-ingenic's + # include/configs/isvp_common.h carries the same unsuffixed uknor/urnor. + # u-boot-msc313e, u-boot-t20 and u-boot-t40 do define the suffixed pair, which + # is what the note this replaces was reading -- but no released binary is + # built from them, so no camera runs them. # - # This is the one place the chip size decides, for every vendor. What has to - # travel with it is the instruction to run `setnor16m`: every one of these - # bootloaders defaults mtdparts to the 8MB layout, and flashing a full image - # leaves the env erased, so a 16MB camera that is never told to switch boots - # with 8MB partitions. update.html.erb used to suppress that instruction for - # these same two vendors and no longer does. - def self.nor(flash_size_mb) - flash_size_mb.to_i <= 8 ? NOR[8] : NOR[16] + # So these two have one mtdparts string, the kernel partition is 2048k inside + # it, and the only thing that varies is ${rootmtd}. The rootfs starts at + # 0x250000 at every chip size; what a larger chip buys is a longer rootfs, not + # one further up. + # + # Handing them NOR[16] put the rootfs at 0x350000, which is where the images + # openipc.org serves today for ssc377qe have it: layout=16 carries "hsqs" at + # 0x350000 and 0xff at 0x250000, and its env region at 0x40000 is blank, so + # the camera comes up on the compiled-in bootargs, looks for the rootfs at + # 0x250000 and panics on root mount. layout=8 has it at 0x250000 and boots. + FIXED_MTDPARTS_VENDORS = %w[SigmaStar Ingenic].freeze + + # The same two questions the table above answers, for a bootloader whose + # rootfs cannot move. 16 is not a different partition map, it is `rootmtd` + # and the erase length that goes with it set to 10240k -- which is also the + # only way an Ultimate rootfs fits: 7832KB on ssc338q, 7252KB on ssc30kq and + # 6772KB on t31, against the 5120KB the default leaves. + FIXED_MTDPARTS_NOR = { + 8 => { kernel_offset: 0x50000, kernel_max_size: 0x200000, + rootfs_offset: 0x250000, rootfs_max_size: 0x500000, + overlay_offset: 0x750000 }.freeze, + 16 => { kernel_offset: 0x50000, kernel_max_size: 0x200000, + rootfs_offset: 0x250000, rootfs_max_size: 0xA00000, + overlay_offset: 0xC50000 }.freeze + }.freeze + + # Whether this vendor's NOR bootloader has one mtdparts string rather than a + # mtdpartsnor8m/mtdpartsnor16m pair to switch between. + def self.fixed_mtdparts?(vendor_name) + FIXED_MTDPARTS_VENDORS.include?(vendor_name.to_s) + end + + # The chip size decides which entry, and the vendor decides which table. What + # has to travel with the 16MB entry either way is the instruction to put the + # bootloader on it: all of these default to the 8MB partitions -- mtdparts on + # HiSilicon and Goke, rootmtd=5120k on SigmaStar and Ingenic -- and flashing a + # full image leaves the env erased, so that default is what boots. See + # Camera#layout_commands for what each family is told to run. + def self.nor(flash_size_mb, vendor_name = nil) + table = fixed_mtdparts?(vendor_name) ? FIXED_MTDPARTS_NOR : NOR + table[flash_size_mb.to_i <= 8 ? 8 : 16] end end diff --git a/app/views/cameras/socs/update.html.erb b/app/views/cameras/socs/update.html.erb index a8b1ad1..8b7a178 100644 --- a/app/views/cameras/socs/update.html.erb +++ b/app/views/cameras/socs/update.html.erb @@ -76,17 +76,15 @@ <% end %> <%= flashing_everything(@camera) %>

<%= t('firmware.installation.flashing_full.continue') %>

- <%# SigmaStar and Ingenic were exempted here in 64d37db and 00332d1 and - are not any more. Their bootloaders default mtdparts to the 8MB - layout like every other one, a full-image flash leaves the env - erased so that default is what the camera boots with, and the - expert section below has always told them to run setnor16m - regardless -- so the exemption made this page contradict itself. - It also cannot coexist with Ultimate on 16MB: that rootfs is - 7820KB on ssc338q against the 5120KB the 8MB layout allows. %> - <% unless @flash_type_command.eql?('nor8m') %> + <%# Every one of these bootloaders defaults mtdparts to the 8MB layout + and a full-image flash leaves the env erased, so that default is + what the camera boots with and anything else has to be set here. + Asked of the camera rather than of the layout name: SigmaStar and + Ingenic have no set* macro to run, and what they are given instead + is the setenv that macro would have done. %> + <% unless @camera.default_bootloader_layout? %>

<%= t('firmware.installation.flashing_full.continue2') %>

- <%= preparing_environment(@flash_type_command) %> + <%= preparing_environment(@camera) %> <% end %> @@ -126,15 +124,21 @@ -

<%= t('firmware.installation.flashing_footfs.title') %>

-
-
-
-

<%= t('firmware.installation.flashing_footfs.info') %>

- <%= preparing_environment(@flash_type_command) %> -

<%= t('firmware.installation.flashing_footfs.continue') %>

+ <%# Skipped where there is nothing to run. This step used to render + unconditionally, so a SigmaStar or Ingenic reader was given `run + setnor8m` -- a variable their bootloader does not define -- under a + heading telling them it was required. %> + <% if @camera.layout_commands.any? %> +

<%= t('firmware.installation.flashing_footfs.title') %>

+
+
+
+

<%= t('firmware.installation.flashing_footfs.info') %>

+ <%= preparing_environment(@camera) %> +

<%= t('firmware.installation.flashing_footfs.continue') %>

+
-
+ <% end %>

<%= t('firmware.installation.flashing_footfs2.title') %>

@@ -154,7 +158,7 @@

<%= t('.sdcard_required_3') %>

<% end %> - <%= flashing_linux(@camera, @flash_type_command) %> + <%= flashing_linux(@camera) %>
@@ -174,7 +178,7 @@ <% end %> -

<%= t('firmware.info_html', commands: bootloader_variables_html(@flash_type_command)) %>

+

<%= t('firmware.info_html', commands: bootloader_variables_html(@camera)) %>

diff --git a/test/controllers/socs_controller_test.rb b/test/controllers/socs_controller_test.rb index d93bb1c..18be934 100644 --- a/test/controllers/socs_controller_test.rb +++ b/test/controllers/socs_controller_test.rb @@ -609,12 +609,11 @@ def submit(soc, flash_type, firmware_version: 'lite', partition_layout: nil, loc # --- SigmaStar and Ingenic on 16MB --- - # FlashLayout pinned these two vendors to the 8MB offsets whatever chip was - # picked, so the page told a 16MB camera to `run uknor16m; run urnor16m` -- - # writing the rootfs to 0x350000..0xd50000 using the bootloader's own macros - # -- and then erased from 0x750000, 733,184 bytes inside it. u-boot-msc313e, - # u-boot-t20 and u-boot-t40 all define mtdpartsnor16m identically to the - # Hisilicon and Goke ones, so there was never a reason to treat them apart. + # The bootloader these two actually ship -- openipc/u-boot-sigmastar and + # openipc/u-boot-ingenic, which is what OpenIPC/firmware's uboot.yml builds + # the released binaries from -- has one NOR mtdparts string with a fixed + # 2048k kernel and ${rootmtd} after it. The rootfs is at 0x250000 at every + # chip size, and there is no uknor16m, urnor16m or setnor16m to run. def soc_of(vendor_name) vendor = Vendor.create!(name: vendor_name) Soc.create!(vendor:, model: 'TS338Q', status: 'done', load_address: '0x82000000', @@ -622,54 +621,88 @@ def soc_of(vendor_name) linux_filename: 'openipc.ts338q-nor-lite.tgz') end - # FlashLayout pinned these two vendors to the 8MB offsets whatever chip was - # picked, so the page told a 16MB camera to `run uknor16m; run urnor16m` -- - # writing the rootfs to 0x350000..0xd50000 using the bootloader's own macros - # -- and then erased from 0x750000, 733,184 bytes inside it. - def assert_sixteen_meg_offsets(vendor_name) + # A 16MB layout on these two means rootmtd=10240k, not a rootfs 1MB further + # up. Handing them the other table put it at 0x350000, where their bootloader + # never looks: the full image openipc.org serves for ssc377qe today carries + # "hsqs" at 0x350000, 0xff at 0x250000 and a blank env at 0x40000, so the + # camera boots on the compiled-in bootargs and panics on root mount. + def assert_rootfs_stays_at_250000(vendor_name) soc = soc_of(vendor_name) with_release_index(*every_edition_for(soc)) do submit(soc, 'nor16m', firmware_version: 'ultimate') - assert_match 'sf erase 0xD50000', response.body + assert_match 'sf erase 0xC50000', response.body + assert_no_match(/sf erase 0xD50000/, response.body) assert_no_match(/sf erase 0x750000/, response.body) end end - # Those offsets are only right if the camera is running the 16MB mtdparts, and - # every one of these bootloaders defaults to the 8MB one. A full-image flash - # leaves the env erased, so that default is what boots. These two vendors used - # to be the only ones not told to run setnor16m afterwards. - def assert_told_to_remap_partitions(vendor_name) + # Whatever is run has to exist. `run uknor16m` returns `## Error: "uknor16m" + # not defined` on these cameras and flashes nothing, silently. + def assert_unsuffixed_macros(vendor_name) soc = soc_of(vendor_name) with_release_index(*every_edition_for(soc)) do - submit(soc, 'nor16m') + submit(soc, 'nor16m', firmware_version: 'ultimate') + + assert_match 'run uknor; run urnor', response.body + assert_no_match(/uknor16m|urnor16m|uknor8m|urnor8m/, response.body) + assert_no_match(/setnor/, response.body) + end + end + + # The 16MB layout still has to be switched on -- rootmtd defaults to 5120k and + # a full-image flash leaves the env erased -- but with the setenv their + # bootloader understands rather than a macro it does not define. + def assert_told_to_set_rootmtd(vendor_name) + soc = soc_of(vendor_name) + + with_release_index(*every_edition_for(soc)) do + submit(soc, 'nor16m', firmware_version: 'ultimate') - # The expert section further down emits `run setnor16m` for everybody and - # always has, so matching the string alone proves nothing. What was - # suppressed is the copy of it in the full-image section, which arrives - # with the flashing_full.continue2 sentence in front of it. assert_match 'remap ROM partitioning according to your flash size', response.body - assert_equal 2, response.body.scan('run setnor16m').size + assert_match 'setenv rootmtd 10240k; setenv rootsize 0xA00000', response.body end end - test 'a 16MB SigmaStar submission gets the 16MB layout like everyone else' do - assert_sixteen_meg_offsets('SigmaStar') + test 'a 16MB SigmaStar submission keeps the rootfs at 0x250000' do + assert_rootfs_stays_at_250000('SigmaStar') end - test 'a 16MB Ingenic submission gets the 16MB layout like everyone else' do - assert_sixteen_meg_offsets('Ingenic') + test 'a 16MB Ingenic submission keeps the rootfs at 0x250000' do + assert_rootfs_stays_at_250000('Ingenic') end - test 'a 16MB SigmaStar camera is told to run setnor16m after a full flash' do - assert_told_to_remap_partitions('SigmaStar') + test 'a SigmaStar camera is given the macros its bootloader defines' do + assert_unsuffixed_macros('SigmaStar') end - test 'a 16MB Ingenic camera is told to run setnor16m after a full flash' do - assert_told_to_remap_partitions('Ingenic') + test 'an Ingenic camera is given the macros its bootloader defines' do + assert_unsuffixed_macros('Ingenic') + end + + test 'a 16MB SigmaStar camera is told to set rootmtd after a full flash' do + assert_told_to_set_rootmtd('SigmaStar') + end + + test 'a 16MB Ingenic camera is told to set rootmtd after a full flash' do + assert_told_to_set_rootmtd('Ingenic') + end + + # The 8MB layout is what rootmtd=5120k already is, so there is nothing to run + # and nothing to tell the reader. This step used to render regardless, under a + # heading saying it was required, naming a variable that does not exist. + test 'a SigmaStar camera on the default layout is told to run nothing' do + soc = soc_of('SigmaStar') + + with_release_index(*every_edition_for(soc)) do + submit(soc, 'nor16m', partition_layout: 'nor8m') + + assert_match 'sf erase 0x750000', response.body + assert_no_match(/setenv rootmtd/, response.body) + assert_no_match(/run set/, response.body) + end end # --- the pages that send the visitor to the wiki instead --- diff --git a/test/models/camera_test.rb b/test/models/camera_test.rb index eae0aa6..3a6f56f 100644 --- a/test/models/camera_test.rb +++ b/test/models/camera_test.rb @@ -196,6 +196,96 @@ def camera(flash_type:, firmware_version: 'ultimate') end end + # --- bootloaders with one mtdparts string --- + + # openipc/u-boot-sigmastar and openipc/u-boot-ingenic -- the repositories + # OpenIPC/firmware's uboot.yml builds the released binaries from -- carry one + # NOR mtdparts with a fixed 2048k kernel and ${rootmtd} behind it, plain + # uknor/urnor, and no setnor* of any kind. + StubVendor = Struct.new(:name) + StubSoc = Struct.new(:vendor) + + def camera_of(vendor_name, flash_type:, firmware_version: 'lite', layout: nil) + c = camera(flash_type:, firmware_version:) + c.soc = StubSoc.new(StubVendor.new(vendor_name)) + c.partition_layout = layout if layout + c + end + + test 'a 16MB SigmaStar chip keeps the rootfs where its bootloader looks' do + c = camera_of('SigmaStar', flash_type: 'nor16m', firmware_version: 'ultimate') + + assert_equal 16, c.layout_size + assert_equal '0x250000', c.rootfs_offset, 'the rootfs does not move on this bootloader' + assert_equal '0xA00000', c.rootfs_max_size, 'what a 16MB chip buys is rootmtd=10240k' + assert_equal '0x200000', c.kernel_max_size, 'the kernel partition is 2048k in the bootargs' + assert_equal '0xC50000', c.overlay_offset + assert_equal '0x3b0000', c.overlay_max_size + end + + test 'Ingenic gets the same treatment as SigmaStar' do + assert_equal '0x250000', camera_of('Ingenic', flash_type: 'nor16m').rootfs_offset + end + + test 'a 16MB chip from any other vendor still moves the rootfs to 0x350000' do + assert_equal '0x350000', camera_of('HiSilicon', flash_type: 'nor16m').rootfs_offset + assert_equal '0x300000', camera_of('HiSilicon', flash_type: 'nor16m').kernel_max_size + end + + test 'SigmaStar macros carry no size suffix, and there is no set macro to name' do + c = camera_of('SigmaStar', flash_type: 'nor16m') + + assert_equal 'nor', c.bootloader_macro_suffix + assert_equal %w[uknor urnor], c.bootloader_variables + end + + test 'other vendors keep the suffixed macros they define' do + c = camera_of('HiSilicon', flash_type: 'nor16m') + + assert_equal 'nor16m', c.bootloader_macro_suffix + assert_equal %w[uknor16m urnor16m setnor16m], c.bootloader_variables + end + + # rootmtd=5120k is the 8MB layout, so a camera already on it has nothing to + # run. The page used to render `run setnor8m` here, which these bootloaders + # answer with `## Error: "setnor8m" not defined`. + test 'the default layout asks a SigmaStar camera to run nothing' do + c = camera_of('SigmaStar', flash_type: 'nor16m', layout: 'nor8m') + + assert_equal 8, c.layout_size + assert_empty c.layout_commands + assert_equal '0x750000', c.overlay_offset + end + + test 'the 16MB layout is set with the variable the bootloader has' do + c = camera_of('SigmaStar', flash_type: 'nor16m', firmware_version: 'ultimate') + + assert_equal ['setenv rootmtd 10240k; setenv rootsize 0xA00000', 'saveenv', 'reset'], + c.layout_commands + end + + test 'a macro-defining vendor is still told to run one' do + assert_equal ['run setnor16m'], camera_of('HiSilicon', flash_type: 'nor16m').layout_commands + assert_equal ['run setnor8m'], camera_of('HiSilicon', flash_type: 'nor8m').layout_commands + end + + # NAND is a separate environment -- uknand, urnand, setnand and mtdpartsubi -- + # and none of this touches it. + test 'nand keeps its own macros whatever the vendor' do + c = camera_of('SigmaStar', flash_type: 'nand', firmware_version: 'ultimate') + + assert_not c.fixed_mtdparts? + assert_equal 'nand', c.bootloader_macro_suffix + assert_equal ['run setnand'], c.layout_commands + end + + # Every existing test builds a Camera with no SoC at all, and the offsets they + # pin have to keep coming out of the table they always did. + test 'a camera with no SoC falls through to the layout table it always used' do + assert_equal '0x350000', camera(flash_type: 'nor16m').rootfs_offset + assert_equal 'nor16m', camera(flash_type: 'nor16m').bootloader_macro_suffix + end + # --- editions that are not published --- test 'an edition upstream does not build is replaced, and says what was asked for' do diff --git a/test/models/firmware_test.rb b/test/models/firmware_test.rb index c406988..b2bdbfa 100644 --- a/test/models/firmware_test.rb +++ b/test/models/firmware_test.rb @@ -149,6 +149,34 @@ def leftover_temp_files(firmware) assert_equal ("\xFF".b * 0x100), image[16.megabytes - 0x100, 0x100] end + # The image openipc.org serves for ssc377qe today has "hsqs" at 0x350000 and + # 0xff at 0x250000, and its env region at 0x40000 is blank -- so the camera + # comes up on the bootloader's compiled-in bootargs, which say the rootfs + # starts at 0x250000, finds erased flash there and panics on root mount. + # openipc/u-boot-sigmastar has one mtdparts string and the rootfs offset in it + # does not move; 16MB means rootmtd=10240k, not a rootfs 1MB further up. + test 'a 16MB SigmaStar image keeps the rootfs at the only offset its bootloader reads' do + fw = build(model: 'ssc338q', vendor: 'SigmaStar', flash_type: 'nor', size: 16, + members: { 'uImage.ssc338q' => KERNEL, 'rootfs.squashfs.ssc338q' => SQUASHFS }) + fw.generate + image = IO.binread(fw.filepath) + + assert_equal 16.megabytes, image.bytesize + assert_equal KERNEL, image[0x50000, KERNEL.bytesize] + assert_equal SQUASHFS, image[0x250000, SQUASHFS.bytesize] + assert_equal ("\xFF".b * 0x100), image[0x350000, 0x100], 'nothing belongs at the HiSilicon offset' + # rootfs_data begins after a 10240KB rootfs, not after a 5120KB one. + assert_equal ("\xFF".b * 0x100), image[0xC50000, 0x100] + end + + test 'a 16MB Ingenic image does the same' do + fw = build(model: 't31', vendor: 'Ingenic', flash_type: 'nor', size: 16, + members: { 'uImage.t31' => KERNEL, 'rootfs.squashfs.t31' => SQUASHFS }) + fw.generate + + assert_equal SQUASHFS, IO.binread(fw.filepath)[0x250000, SQUASHFS.bytesize] + end + # A request parameter, like the size beside it, so it is refused before it # can decide where anything is written. test 'a layout larger than the chip is refused' do From de27c623f8ee16c37a15a793438f363ffb4a8517 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sat, 29 Aug 2026 20:27:27 +0300 Subject: [PATCH 2/2] Assert the 16MB agreement per vendor rather than once for everybody The test that pinned every vendor to 0x350000 was written when every vendor had the same answer. It still checks the thing it was for -- that the image and the page cannot describe different layouts -- but reads the offset each bootloader is actually on. A model of its own per vendor with it: the cache filename carries the model and not the vendor, so one model built under three vendors has the second and third read back the first's image. Nothing in production can hit that, a model belongs to one vendor, but a test that did would pass on a stale file. --- test/models/firmware_test.rb | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/test/models/firmware_test.rb b/test/models/firmware_test.rb index b2bdbfa..1e97b61 100644 --- a/test/models/firmware_test.rb +++ b/test/models/firmware_test.rb @@ -585,22 +585,31 @@ def leftover_temp_files(firmware) end end - # These two used to be pinned to the 8MB offsets whatever chip was chosen, so - # this asserted 0x250000. u-boot-msc313e, u-boot-t20 and u-boot-t40 all define - # mtdpartsnor16m exactly as the Hisilicon and Goke bootloaders do, and their - # Ultimate rootfs -- 7820KB on ssc338q -- does not fit the 5120KB the 8MB - # layout allows, so the pin could not survive Ultimate on 16MB either. What - # this test is for is unchanged: the image and the page must not describe - # different layouts, whichever offsets are right. - test 'no vendor gets offsets of its own: image and page agree on 16MB' do - %w[SigmaStar Ingenic HiSilicon].each do |vendor| - fw = build(model: 'ssc338q', vendor: vendor, flash_type: 'nor', size: 16, release: 'lite', - members: { 'uImage.ssc338q' => KERNEL, 'rootfs.squashfs.ssc338q' => SQUASHFS }) + # What this test is for is unchanged -- the image and the page must not + # describe different layouts -- but what the right offset is depends on the + # bootloader, so it is asserted per vendor rather than once for everybody. + # 0x350000 where mtdparts switches wholesale between mtdpartsnor8m and + # mtdpartsnor16m; 0x250000 where there is one mtdparts string with a fixed + # 2048k kernel and only ${rootmtd} behind it, which is what + # openipc/u-boot-sigmastar and openipc/u-boot-ingenic ship. + # + # A model of its own per vendor, not one model wearing three: the cache + # filename carries the model and not the vendor, so building the same model + # under three vendors in one test has the second and third read back the + # first's image. Nothing in production can hit that -- a model belongs to one + # vendor -- but a test that did would pass on a stale file. + test 'the image and the page agree on 16MB, on whichever offsets the vendor is on' do + { 'SigmaStar' => ['ssc338q', 0x250000], + 'Ingenic' => ['t31x', 0x250000], + 'HiSilicon' => ['hi3516ev300', 0x350000] }.each do |vendor, (model, offset)| + fw = build(model: model, vendor: vendor, flash_type: 'nor', size: 16, release: 'lite', + members: { "uImage.#{model}" => KERNEL, "rootfs.squashfs.#{model}" => SQUASHFS }) fw.generate camera = Camera.new(flash_type: 'nor16m', firmware_version: 'lite', soc: fw_soc(vendor)) - assert_equal '0x350000', camera.rootfs_offset, "#{vendor} is not on the 16MB rootfs offset" - assert_equal SQUASHFS, IO.binread(fw.filepath)[0x350000, SQUASHFS.bytesize], + assert_equal format('0x%X', offset), camera.rootfs_offset, + "#{vendor} is not on the offset its bootloader reads" + assert_equal SQUASHFS, IO.binread(fw.filepath)[offset, SQUASHFS.bytesize], "#{vendor}: the image does not match the page" end end