Skip to content

Commit e7fff81

Browse files
committed
Answer the review: three ways the split was still wrong
Qodo found three, and driving the wizard script through a DOM stub found a fourth that was mine. **The layout stopped following the chip.** The form opens on nor8m, and nor8m is a legal layout on a larger part, so `checkPartitionLayout` never took it away: picking NOR 16M left 8MB partitions on a 16MB camera and the edition limiter took Ultimate off with them. It now follows the chip until the visitor settles it themselves -- by using the menu, or by arriving on a permanent link that carries a `part` that is not the chip's own, which is as deliberate as clicking. **The rootfs was bounded by the image, not by its partition.** `layout` gave the rootfs part the whole chip-sized image as its limit. That was near enough while the layout was the chip -- an 8MB image ends 0xb0000 past the rootfs partition -- and is not now: a 16MB image laid out the 8MB way ends 0x8b0000 past it, and `download_full_image` takes the edition and the layout straight from the query string. `?fw_release=ultimate&flash_size=16&layout=8` would have written a 7MB rootfs from 0x250000 clean through rootfs_data. It is bounded by the overlay offset now, which is what the mtdparts says the partition is. **"This SoC needs a larger chip" reached chips that are large enough.** The Ultimate guard became the layout's, so the no-Lite branch under it now sees 16MB and 32MB parts, where the advice is not to buy a chip but to choose the other layout. It tells the two apart, like the warning beside it already did. **NAND lost Ultimate.** The layout menu keeps its value while it is hidden, so `checkPartitionLayout` handed back nor8m on a NAND part and the 8MB rootfs rule disabled an edition that eleven of the sixteen NAND boards are published as and nothing else. It answers with no layout where there is none to choose.
1 parent c769c17 commit e7fff81

5 files changed

Lines changed: 148 additions & 14 deletions

File tree

app/controllers/cameras/socs_controller.rb

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,10 +342,7 @@ def enforce_eight_meg_limit
342342
@camera.firmware_version = 'lite'
343343
flash.now[:warning] = eight_meg_warning
344344
else
345-
flash.now[:alert] =
346-
'The Ultimate edition does not fit an 8MB flash chip, and OpenIPC publishes no Lite build ' \
347-
'for this SoC on NOR. These instructions cannot produce a working camera on 8MB flash -- ' \
348-
'this SoC needs a larger chip.'
345+
flash.now[:alert] = no_lite_for_eight_meg_alert
349346
end
350347
end
351348

@@ -355,6 +352,24 @@ def eight_meg_rootfs_with_ultimate?
355352
@camera.partition_layout.eql?('nor8m') && @camera.firmware_version.eql?('ultimate')
356353
end
357354

355+
# "This SoC needs a larger chip" is the right advice for an 8MB part and the
356+
# wrong advice for a 16MB one wearing the 8MB layout, where the chip is
357+
# already big enough and the layout is the thing to change. The guard above
358+
# reaches both since it became the layout's, so this has to tell them apart
359+
# too -- it is the branch for a SoC published as Ultimate and nothing else,
360+
# hi3516cv6xx and hi3519dv500, where there is no Lite to fall back to.
361+
def no_lite_for_eight_meg_alert
362+
unless @camera.flash_type.eql?('nor8m')
363+
return 'The Ultimate edition does not fit the 8MB partition layout, and OpenIPC publishes ' \
364+
'no Lite build for this SoC on NOR. Choose the 16MB layout, which this chip is big ' \
365+
'enough for.'
366+
end
367+
368+
'The Ultimate edition does not fit an 8MB flash chip, and OpenIPC publishes no Lite build ' \
369+
'for this SoC on NOR. These instructions cannot produce a working camera on 8MB flash -- ' \
370+
'this SoC needs a larger chip.'
371+
end
372+
358373
# The chip when the chip is what limits them, and the layout when it is the
359374
# layout: a 5120KB rootfs partition is a 5120KB rootfs partition whether the
360375
# part around it is 8MB or 32MB, and on the larger ones there is something

app/models/firmware.rb

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,31 @@ def layout(uboot, kernel, rootfs, size)
233233
[
234234
Part.new('u-boot', uboot, 0, kernel_offset, 'the kernel offset'),
235235
Part.new('kernel', kernel, kernel_offset, rootfs_offset, 'the rootfs offset'),
236-
Part.new('rootfs', rootfs, rootfs_offset, size, 'the end of the image')
236+
Part.new('rootfs', rootfs, rootfs_offset, *rootfs_limit(size))
237237
]
238238
end
239239

240+
# The rootfs may not run past its own partition, which on NOR is where the
241+
# overlay starts and not the end of the image.
242+
#
243+
# The two were near enough the same number while the layout was the chip: an
244+
# 8MB image laid out the 8MB way ends 0xb0000 past the rootfs partition, and
245+
# the difference only mattered for a rootfs already too big for the partition
246+
# to mount. A 16MB image laid out the 8MB way ends 0x8b0000 past it, and
247+
# download_full_image takes the edition and the layout straight from the
248+
# query string -- so `?fw_release=ultimate&flash_size=16&layout=8` would have
249+
# written a 7MB rootfs from 0x250000 clean through rootfs_data, and the
250+
# camera would have mounted a squashfs whose tail the overlay then formatted
251+
# over.
252+
#
253+
# NAND keeps the end of the image, which is where its rootfs ends by
254+
# construction: image_size is the rootfs offset plus the payload.
255+
def rootfs_limit(size)
256+
return [size, 'the end of the image'] if nand?
257+
258+
[nor_layout[:overlay_offset], 'the rootfs partition']
259+
end
260+
240261
# Build beside the destination and rename into place.
241262
#
242263
# This used to build in Dir.tmpdir and hand the result to FileUtils.mv. In

app/views/cameras/socs/show.html.erb

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,25 @@
145145
return chip === 'nor8m' ? ['nor8m'] : ['nor8m', 'nor16m'];
146146
}
147147

148+
// The layout a chip wears unless it is told otherwise: the largest one it
149+
// can hold, which is the last entry allowedLayouts returns.
150+
function naturalLayout(chip) {
151+
const allowed = allowedLayouts(chip);
152+
return allowed.length ? allowed[allowed.length - 1] : '';
153+
}
154+
155+
// Whether the visitor has settled the layout for themselves. Until they
156+
// have, it follows the chip, so choosing NOR 16M gets the 16MB layout --
157+
// which is what the single menu this replaced always produced.
158+
//
159+
// Without it the 8MB layout the form opens on survived every later chip
160+
// change, because it is a legal choice on a larger part and so never
161+
// tripped the not-allowed branch. Picking NOR 16M then quietly gave a 16MB
162+
// camera 8MB partitions, and the edition limiter below took Ultimate away
163+
// with it.
164+
let layoutChosen = false;
165+
148166
// Narrow the layout menu to the chip, and answer what is now selected.
149-
// Landing on the last allowed entry rather than the first makes the
150-
// chip's own layout the default, which is what this form produced when
151-
// the two were a single field.
152167
function checkPartitionLayout() {
153168
const el = document.querySelector('#camera_partition_layout');
154169
const chip = document.querySelector('#camera_flash_type').value;
@@ -159,9 +174,17 @@
159174
const o = el.options.item(i);
160175
o.disabled = !allowed.includes(o.value);
161176
}
162-
if (allowed.length && !allowed.includes(el.value)) el.value = allowed[allowed.length - 1];
177+
if (allowed.length && !(layoutChosen && allowed.includes(el.value))) {
178+
el.value = naturalLayout(chip);
179+
}
163180

164-
return el.value;
181+
// Nothing, rather than whatever the hidden menu happens to hold, when
182+
// the chip has no NOR layout to choose. The menu keeps its value while
183+
// it is hidden, so NAND was reading back `nor8m` and having Ultimate
184+
// taken off it by a rootfs partition it does not have -- eleven of the
185+
// sixteen boards with a NAND build are published as Ultimate and
186+
// nothing else.
187+
return allowed.length ? el.value : '';
165188
}
166189

167190
function allowedEditions(chip, layout) {
@@ -232,6 +255,19 @@
232255

233256
document.querySelector('#generate-mac-address').addEventListener('click', generateMacAddress);
234257
document.querySelector('#camera_flash_type').addEventListener('change', checkFlashSize);
235-
document.querySelector('#camera_partition_layout').addEventListener('change', checkFlashSize);
258+
document.querySelector('#camera_partition_layout').addEventListener('change', function () {
259+
layoutChosen = true;
260+
checkFlashSize();
261+
});
262+
263+
// Settled before the first narrowing, and after the chip has: a page that
264+
// opens on a layout that is not its chip's own got there from a permanent
265+
// link carrying `part`, and that is as deliberate as using the menu. A link
266+
// written before this field existed carries none, so it opens on the chip's
267+
// own layout and means exactly what it always did.
268+
useAnOfferedFlashType();
269+
layoutChosen = allowedLayouts(document.querySelector('#camera_flash_type').value).length > 0 &&
270+
document.querySelector('#camera_partition_layout').value
271+
!== naturalLayout(document.querySelector('#camera_flash_type').value);
236272
checkFlashSize();
237273
</script>

test/controllers/socs_controller_test.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,49 @@ def submit(soc, flash_type, firmware_version: 'lite', partition_layout: nil, loc
272272
end
273273
end
274274

275+
# The other half of that rule, for a SoC with no Lite build to fall back to.
276+
# "This SoC needs a larger chip" is right for an 8MB part and wrong here: the
277+
# chip is 16MB and it is the layout on it that Ultimate does not fit.
278+
test 'a SoC published only as Ultimate is told to change the layout, not the chip' do
279+
soc = instructable_soc('TS3516EVE50')
280+
281+
with_release_index("openipc.#{soc.board}-nor-ultimate.tgz") do
282+
submit(soc, 'nor16m', firmware_version: 'ultimate', partition_layout: 'nor8m')
283+
284+
assert_match 'Choose the 16MB layout, which this chip is big enough for', response.body
285+
assert_no_match(/needs a larger chip/, response.body)
286+
end
287+
end
288+
289+
test 'an 8MB chip with no Lite build is still told it needs a larger one' do
290+
soc = instructable_soc('TS3516EVE60')
291+
292+
with_release_index("openipc.#{soc.board}-nor-ultimate.tgz") do
293+
submit(soc, 'nor8m', firmware_version: 'ultimate')
294+
295+
assert_match 'needs a larger chip', response.body
296+
end
297+
end
298+
299+
# The layout menu follows the chip until the visitor settles it themselves,
300+
# so picking a larger chip does not leave the 8MB layout the form opens on
301+
# sitting on it -- which would quietly hand a 16MB camera 8MB partitions and
302+
# take Ultimate away with them.
303+
test 'the layout menu follows the chip until the visitor picks one' do
304+
soc = instructable_soc('TS3516EVE70')
305+
306+
with_release_index(*every_edition_for(soc)) do
307+
get "/cameras/vendors/#{@vendor.to_param}/socs/#{soc.to_param}"
308+
309+
assert_response :success
310+
assert_match(/el.value = naturalLayout\(chip\);/, response.body)
311+
assert_match(/layoutChosen = true;/, response.body)
312+
# ...and answers with no layout at all where there is none to choose, so
313+
# a hidden menu still holding nor8m cannot take Ultimate off a NAND part.
314+
assert_match(/return allowed.length \? el.value : '';/, response.body)
315+
end
316+
end
317+
275318
test 'the permanent link carries the layout so it can be reopened' do
276319
soc = instructable_soc('TS3516EVE40')
277320

test/models/firmware_test.rb

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ def leftover_temp_files(firmware)
313313

314314
# --- a part too big for its slot ---
315315

316-
test 'a rootfs too large for the flash is refused rather than written past the end' do
316+
test 'a rootfs too large for its partition is refused rather than written past the end' do
317317
# openipc-hi3516ev200-nor-ultimate-8mb.bin was found in the production
318318
# cache at 9,465,856 bytes -- 0x250000 plus a 7MB Ultimate rootfs, in a
319319
# file whose name promises 8MB. IO.binwrite past the end grows the file
@@ -327,15 +327,34 @@ def leftover_temp_files(firmware)
327327
assert_not File.exist?(fw.filepath), 'an image larger than its flash must not be left to be served'
328328
end
329329

330-
test 'a rootfs that exactly fills the flash is still built' do
331-
exact = "\xC3".b * (8.megabytes - 0x250000)
330+
test 'a rootfs that exactly fills its partition is still built' do
331+
# 0x250000 to 0x750000 -- the 5120KB the 8MB mtdparts gives the rootfs, not
332+
# the 0x5b0000 that is left before the end of an 8MB image. The last
333+
# 0xb0000 of the chip is rootfs_data, and a squashfs written into it is one
334+
# the kernel cannot mount anyway: root is mtdblock3, which is 5120KB long.
335+
exact = "\xC3".b * (0x750000 - 0x250000)
332336
fw = build(model: 'hi3516ev200', vendor: 'HiSilicon', flash_type: 'nor', size: 8,
333337
members: { 'uImage.hi3516ev200' => KERNEL, 'rootfs.squashfs.hi3516ev200' => exact })
334338
fw.generate
335339

336340
assert_equal 8.megabytes, File.size(fw.filepath)
337341
end
338342

343+
# Room in the image is not room in the partition. This one fits a 16MB chip
344+
# twice over and does not fit the 8MB layout that was asked for with it --
345+
# and download_full_image takes both straight from the query string, so
346+
# nothing upstream of here refuses the combination.
347+
test 'a rootfs that fits the chip but not its partition is refused' do
348+
oversize = "\xC3".b * (0x750000 - 0x250000 + 1)
349+
fw = build(model: 'hi3516ev204', vendor: 'HiSilicon', flash_type: 'nor', size: 16, layout: 8,
350+
members: { 'uImage.hi3516ev204' => KERNEL, 'rootfs.squashfs.hi3516ev204' => oversize })
351+
352+
error = assert_raises(Firmware::PayloadTooLarge) { fw.generate }
353+
assert_match(/rootfs/, error.message)
354+
assert_match(/the rootfs partition/, error.message)
355+
assert_not File.exist?(fw.filepath)
356+
end
357+
339358
test 'a kernel that would run into the rootfs is refused' do
340359
oversize = "\xA5".b * (0x250000 - 0x50000 + 1)
341360
fw = build(model: 'hi3516ev200', vendor: 'HiSilicon', flash_type: 'nor', size: 8,

0 commit comments

Comments
 (0)