diff --git a/data/catalogue/goke.yml b/data/catalogue/goke.yml index 1fb59c94..a79c1a1f 100644 --- a/data/catalogue/goke.yml +++ b/data/catalogue/goke.yml @@ -128,7 +128,9 @@ socs: load_address: '0x42000000' sdk: '' kernel: '' - uboot_filename: u-boot-gk7202v300-universal.bin + uboot_filename: u-boot-gk7202v300-nor.bin + uboot_nand_filename: u-boot-gk7202v300-nand.bin + nor_split: true linux_filename: openipc.gk7202v300-nor-lite.tgz notes: '' urlname: gk7202v300 @@ -142,7 +144,9 @@ socs: load_address: '0x42000000' sdk: '' kernel: '' - uboot_filename: u-boot-gk7205v200-universal.bin + uboot_filename: u-boot-gk7205v200-nor.bin + uboot_nand_filename: u-boot-gk7205v200-nand.bin + nor_split: true linux_filename: openipc.gk7205v200-nor-lite.tgz notes: '' urlname: gk7205v200 @@ -156,7 +160,9 @@ socs: load_address: '0x42000000' sdk: '' kernel: '' - uboot_filename: u-boot-gk7205v200-universal.bin + uboot_filename: u-boot-gk7205v200-nor.bin + uboot_nand_filename: u-boot-gk7205v200-nand.bin + nor_split: true linux_filename: openipc.gk7205v210-nor-lite.tgz notes: '' urlname: gk7205v210 @@ -170,7 +176,9 @@ socs: load_address: '0x42000000' sdk: '' kernel: '' - uboot_filename: u-boot-gk7205v300-universal.bin + uboot_filename: u-boot-gk7205v300-nor.bin + uboot_nand_filename: u-boot-gk7205v300-nand.bin + nor_split: true linux_filename: openipc.gk7205v300-nor-lite.tgz notes: '' urlname: gk7205v300 @@ -314,7 +322,9 @@ socs: load_address: '0x42000000' sdk: '' kernel: '' - uboot_filename: u-boot-gk7605v100-universal.bin + uboot_filename: u-boot-gk7605v100-nor.bin + uboot_nand_filename: u-boot-gk7605v100-nand.bin + nor_split: true linux_filename: openipc.gk7605v100-nor-lite.tgz notes: '' urlname: gk7605v100 diff --git a/service/internal/catalogue/catalogue.go b/service/internal/catalogue/catalogue.go index 999aa20a..7f279fd3 100644 --- a/service/internal/catalogue/catalogue.go +++ b/service/internal/catalogue/catalogue.go @@ -31,7 +31,13 @@ type SoC struct { // NANDBoard is the build name upstream publishes this SoC's NAND // firmware under, when that is not the NOR one: GK7205V510 has a NOR // build of its own, but its NAND firmware is the GK7205V500 build. - NANDBoard string `yaml:"nand_board"` + NANDBoard string `yaml:"nand_board"` + // NORSplit is a SoC whose firmware also ships the split NOR layout, + // openipc.-nor--split.tgz: one firmware.bin (a hashed + // FIT, then the squashfs at the next 64 KiB) written at 0x50000, which + // the kernel divides into kernel, rootfs and rootfs_data at boot. Its + // -nor.bin bootloader boots that and the fixed slots alike. + NORSplit bool `yaml:"nor_split"` LinuxFilename string `yaml:"linux_filename"` LoadAddress string `yaml:"load_address"` Vendor *Vendor `yaml:"-"` diff --git a/service/internal/firmware/testdata/boards.json b/service/internal/firmware/testdata/boards.json index 0fd7e03e..87d42a0a 100644 --- a/service/internal/firmware/testdata/boards.json +++ b/service/internal/firmware/testdata/boards.json @@ -256,7 +256,8 @@ "vendor": "Goke", "model": "GK7202V300", "board": "gk7202v300", - "uboot": "u-boot-gk7202v300-universal.bin", + "uboot": "u-boot-gk7202v300-nor.bin", + "uboot_nand": "u-boot-gk7202v300-nand.bin", "nor_lite": "openipc.gk7202v300-nor-lite.tgz", "nand_lite": "openipc.gk7202v300-nand-lite.tgz" }, @@ -265,7 +266,8 @@ "vendor": "Goke", "model": "GK7205V200", "board": "gk7205v200", - "uboot": "u-boot-gk7205v200-universal.bin", + "uboot": "u-boot-gk7205v200-nor.bin", + "uboot_nand": "u-boot-gk7205v200-nand.bin", "nor_lite": "openipc.gk7205v200-nor-lite.tgz", "nand_lite": "openipc.gk7205v200-nand-lite.tgz" }, @@ -274,7 +276,8 @@ "vendor": "Goke", "model": "GK7205V210", "board": "gk7205v200", - "uboot": "u-boot-gk7205v200-universal.bin", + "uboot": "u-boot-gk7205v200-nor.bin", + "uboot_nand": "u-boot-gk7205v200-nand.bin", "nor_lite": "openipc.gk7205v200-nor-lite.tgz", "nand_lite": "openipc.gk7205v200-nand-lite.tgz" }, @@ -283,7 +286,8 @@ "vendor": "Goke", "model": "GK7205V300", "board": "gk7205v300", - "uboot": "u-boot-gk7205v300-universal.bin", + "uboot": "u-boot-gk7205v300-nor.bin", + "uboot_nand": "u-boot-gk7205v300-nand.bin", "nor_lite": "openipc.gk7205v300-nor-lite.tgz", "nand_lite": "openipc.gk7205v300-nand-lite.tgz" }, @@ -376,7 +380,8 @@ "vendor": "Goke", "model": "GK7605V100", "board": "gk7605v100", - "uboot": "u-boot-gk7605v100-universal.bin", + "uboot": "u-boot-gk7605v100-nor.bin", + "uboot_nand": "u-boot-gk7605v100-nand.bin", "nor_lite": "openipc.gk7605v100-nor-lite.tgz", "nand_lite": "openipc.gk7605v100-nand-lite.tgz" }, diff --git a/service/internal/firmware/testdata/manifests.json b/service/internal/firmware/testdata/manifests.json index dd425555..e9592591 100644 --- a/service/internal/firmware/testdata/manifests.json +++ b/service/internal/firmware/testdata/manifests.json @@ -79,22 +79,6 @@ "rootfs": 2424832 } }, - { - "urlname": "gk7202v300", - "vendor": "Goke", - "flash_type": "nand", - "release": "lite", - "size": null, - "layout": null, - "filename": "openipc-gk7202v300-nand-lite.bin", - "bytes": 7194624, - "sha256": "bde2516085374dd4ba4d32cb72820527302f9cfd30ce608b525530dfd72ff589", - "offsets": { - "u-boot": 0, - "kernel": 1048576, - "rootfs": 4194304 - } - }, { "urlname": "gk7202v300", "vendor": "Goke", diff --git a/service/internal/firmware/testdata/release-index.json b/service/internal/firmware/testdata/release-index.json index d9e68b06..e36f8e31 100644 --- a/service/internal/firmware/testdata/release-index.json +++ b/service/internal/firmware/testdata/release-index.json @@ -765,18 +765,54 @@ "updated_at": "2026-06-06T23:55:56Z", "release": "nightly-20260606-a9329aa" }, + "u-boot-gk7202v300-nand.bin": { + "size": 325533, + "digest": "sha256:fbf43c9f19c2b4e26d7d86dbca07d06f71287fb1461a4148a90e96ea4f46aad9", + "updated_at": "2026-10-17T17:29:44Z", + "release": "latest" + }, + "u-boot-gk7202v300-nor.bin": { + "size": 221985, + "digest": "sha256:9b5471f7a8c621014ca714c2ae6643d08504fc997a163733fd967a422e95a157", + "updated_at": "2026-10-17T17:29:46Z", + "release": "latest" + }, "u-boot-gk7202v300-universal.bin": { "size": 249483, "digest": "sha256:b34886828dd1f91f08ffe6ff531e775727594f69826cf9aaf54ba21afea3a7e0", "updated_at": "2026-09-16T11:52:45Z", "release": "latest" }, + "u-boot-gk7205v200-nand.bin": { + "size": 325529, + "digest": "sha256:e32ae464dfda75926b6ab05ca265f563ac905f2af9a2bce11f89902476bb006c", + "updated_at": "2026-10-17T17:29:48Z", + "release": "latest" + }, + "u-boot-gk7205v200-nor.bin": { + "size": 222065, + "digest": "sha256:4466459fd3f817bba30becb87e17ad39c110091f6a882a7c8c38b8bc928866cf", + "updated_at": "2026-10-17T17:29:50Z", + "release": "latest" + }, "u-boot-gk7205v200-universal.bin": { "size": 253915, "digest": "sha256:b075d4da346be94c22673fa0e8b0119c5541294831023a3b4e2fb210490d80c0", "updated_at": "2026-09-16T11:52:47Z", "release": "latest" }, + "u-boot-gk7205v300-nand.bin": { + "size": 326213, + "digest": "sha256:66f278812aac69a8f945f9f4f79f04eea7072282c48fd34261afa2f2c17af9b0", + "updated_at": "2026-10-17T17:29:51Z", + "release": "latest" + }, + "u-boot-gk7205v300-nor.bin": { + "size": 222885, + "digest": "sha256:32ea1a426127a05b3f9c32ce67b926bd62030f8f05638cd2f5b823c375347fbd", + "updated_at": "2026-10-17T17:29:53Z", + "release": "latest" + }, "u-boot-gk7205v300-universal.bin": { "size": 257243, "digest": "sha256:87ab3ad1208ca9676455dfff383fc553290b361d62807df220fbe9dbdd9c3180", @@ -819,6 +855,18 @@ "updated_at": "2026-09-16T11:48:27Z", "release": "latest" }, + "u-boot-gk7605v100-nand.bin": { + "size": 319121, + "digest": "sha256:07485ec2e8c6813c84ae79f41f0d60838991da3a7cff2bdaef83e7f026e6f710", + "updated_at": "2026-10-17T17:30:06Z", + "release": "latest" + }, + "u-boot-gk7605v100-nor.bin": { + "size": 215781, + "digest": "sha256:764b543498e2f9c857607edf9095a6fdf8d6efd3232dd9fcadc38802d9a22b07", + "updated_at": "2026-10-17T17:30:08Z", + "release": "latest" + }, "u-boot-gk7605v100-universal.bin": { "size": 253611, "digest": "sha256:a881114e9e6882030551854b742a6c3d963f25fecd66b60f12215e200fa1d5c7", diff --git a/service/internal/wizard/camera.go b/service/internal/wizard/camera.go index 3ec897b7..02fef634 100644 --- a/service/internal/wizard/camera.go +++ b/service/internal/wizard/camera.go @@ -80,6 +80,10 @@ type camera struct { server string mac string layout string // what was asked for; partitionLayout() decides what it is + // split installs the split NOR layout's one firmware.bin (see + // catalogue.SoC.NORSplit); the exporter sets it where that package is + // published for this board and edition. + split bool } func (c *camera) nand() bool { return c.flashType == "nand" } @@ -383,5 +387,25 @@ func (c *camera) fullImageFilename() string { } func (c *camera) firmwareFilename() string { + if c.split { + return splitFilename(c.board, c.edition) + } return fmt.Sprintf("openipc.%s-%s-%s.tgz", c.board, c.flashTypeType(), c.edition) } + +// splitFilename is the split NOR package for a board and edition. +func splitFilename(board, edition string) string { + return fmt.Sprintf("openipc.%s-nor-%s-split.tgz", board, edition) +} + +// splitOffset is where firmware.bin goes: right after the environment, where +// the kernel slot starts on the fixed layout. +const splitOffset = 0x50000 + +// splitEraseSize runs from splitOffset to the end of the chip: the rest of the +// flash is the one firmware partition, and nothing of an old layout may be +// left for the bootloader's scan or the overlay to find. +func (c *camera) splitEraseSize() string { + size, _ := strconv.ParseInt(strings.TrimPrefix(c.flashSizeHex(), "0x"), 16, 64) + return fmt.Sprintf("0x%x", size-splitOffset) +} diff --git a/service/internal/wizard/export.go b/service/internal/wizard/export.go index 73015fb4..f1d742f6 100644 --- a/service/internal/wizard/export.go +++ b/service/internal/wizard/export.go @@ -369,6 +369,7 @@ func (e *exporter) camera(ft string, layout *string, edition, iface, sd, mac str if layout != nil { c.layout = *layout } + c.split = e.soc.NORSplit && c.scansLayout() && e.published(splitFilename(c.board, edition)) return c } diff --git a/service/internal/wizard/lines.go b/service/internal/wizard/lines.go index 97551bb7..0c8311b8 100644 --- a/service/internal/wizard/lines.go +++ b/service/internal/wizard/lines.go @@ -317,6 +317,23 @@ func (c *camera) flashingLinux() []string { } return append(text, "reset") } + if c.split { + // One image, firmware.bin, at 0x50000, the rest of the chip erased + // in the same guarded line: the kernel finds the FIT and the squashfs + // in it and makes rootfs_data of what is left. + fw := "firmware.bin." + c.board + off, erase := hexUpper(splitOffset), c.splitEraseSize() + text = c.probe(text) + if c.sdWifi() { + text = append(text, c.guardedFlash("fatload mmc 0:1 "+la+" "+fw, off, erase, "${filesize}")) + } else { + text = append(text, + c.guardedFlash("tftpboot "+la+" "+fw, off, erase, "${filesize}"), + "# if there is no tftpboot but tftp then run this instead", + c.guardedFlash("tftp "+la+" "+fw, off, erase, "${filesize}")) + } + return append(text, "reset") + } switch { case c.sdWifi(): text = c.writeLinux(text, "fatload mmc 0:1 "+la+" ") diff --git a/service/internal/wizard/testdata/documents.json b/service/internal/wizard/testdata/documents.json index daba0985..4814a413 100644 --- a/service/internal/wizard/testdata/documents.json +++ b/service/internal/wizard/testdata/documents.json @@ -27,10 +27,10 @@ "gk7102s": "17d649ae799eb907d97c1799c7a6aee84248b43128e2808dfdc6a2accfb85f77", "gk7201v200": "0ad54ac650f3bf21044c8dfe502b925a5757f134447363d4a80f4989d712cd4d", "gk7201v300": "78dc314f538a02185f6df48c0079aa7a930a5586379e66607dbdad2ab760cb72", - "gk7202v300": "1dc00795727a869fb8af28740e069f1d22cbaca756d1146e6f21024255410216", - "gk7205v200": "4193ed27fdda4a591eca0e8e8edd0658af9835b7e7b9097df324f43732da54a0", - "gk7205v210": "602279ec9783561a29fad610e9d8c5662ff5379ec1056fc4da562efd1375c2e4", - "gk7205v300": "18b7d4cb3a9990112e8c73178af241d0d8b5b7be852ec62b78d6c0b6f60449af", + "gk7202v300": "c2d4e644b9482021191833b67092090e782e214e4967e6fd7b7a37ae63e4c2ce", + "gk7205v200": "91189f0539d10bf27373645c8cb77f28d89a0ca54892bfc12d547e7daf2f4a7d", + "gk7205v210": "f3dc3584d8a91954114df24083a59d2b0627a95cbc5620eb39479bfb76b1e834", + "gk7205v300": "d839ef7a0752afa630fa9c8303ea38f087f03fcc6b3cb8d8595f942f2595cf75", "gk7205v500": "c1a561e2d2270d643ccface15233499f931ca735b91b63368e1b298526fa98e6", "gk7205v510": "1555a335002054e30e2319155eead4fa25469ddadf845d3d418b55634206024a", "gk7205v530": "73a662d8d94a284159c6bdbb9b28a3ec3ab805d22a47bca059f816d8e72ddba9", @@ -40,7 +40,7 @@ "gk7206v11a": "b75715efb0a24a1ba8c5d52f24e7027c154579e6183f9fb79a33a41586e07aae", "gk7206v11at": "49a40d882995f6fb50a19e89ed202364ab46ea206b2985fb5967e07f07e9e219", "gk7206v12a": "c385a5d0e1fdef7989190cad0c4b6ac68760156de211a4f19c3f5ee334b688b2", - "gk7605v100": "3754171f4ed215e77c42cfe3a7f307a2f8d72a6177378b15c34a4c4ba62e1dba", + "gk7605v100": "162fdbdaa9827a9208bd417edcda8c61df5a1959b71821bce1f5e6be45ee5f03", "gm8135": "af0b20e25eddbbd209215bcf031c5e6adf0142566d79ff3c1f37f17d370fb115", "gm8136": "5dfdadc9ac561d9969893e3959e6914090855e731ab60fd1b4d3ae353f2309d7", "hi3516av100": "b5712535c365f3b9b233f2e811d4881174388c29b25000e2b4323328ee4c70a4", diff --git a/service/internal/wizard/wizard_test.go b/service/internal/wizard/wizard_test.go index ef593810..fe744f27 100644 --- a/service/internal/wizard/wizard_test.go +++ b/service/internal/wizard/wizard_test.go @@ -576,3 +576,90 @@ func TestScanningBootloaderNORInstall(t *testing.T) { t.Errorf("a -universal bootloader lost its macros: %s\n%s", old.bootloader(), strings.Join(old.flashingLinux(), "\n")) } } + +// withAssets is the fixture index plus the named assets, published in one +// nightly. +func withAssets(t *testing.T, names ...string) *firmware.Index { + t.Helper() + _, idx := inputs(t) + var assets []firmware.Asset + for _, a := range idx.Assets() { + assets = append(assets, a) + } + for _, n := range names { + assets = append(assets, firmware.Asset{Name: n, Size: 1, Release: "nightly-20261017-e8d577a"}) + } + aliases := map[string]string{} + for _, chip := range []string{"gk7205v210", "xm550"} { + if b := idx.CanonicalBoard(chip); b != chip { + aliases[chip] = b + } + } + return firmware.NewIndex(idx.Build, assets, aliases, nil) +} + +// The split NOR layout (catalogue nor_split): where the -split package is +// published for the board and edition, the Linux step writes its one +// firmware.bin at 0x50000 and erases to the end of the chip in the same +// guarded line, and the page links that package. Without it the install stays +// the two-image one of a scanning bootloader. +func TestSplitNORInstall(t *testing.T) { + cat, plainIdx := inputs(t) + idx := withAssets(t, + "u-boot-gk7205v300-nor.bin", + "openipc.gk7205v300-nor-lite-split.tgz", + "openipc.gk7205v300-nor-ultimate-split.tgz", + "openipc.gk7205v200-nor-lite-split.tgz", + ) + mk := func(idx *firmware.Index, soc, ft, edition string) *camera { + s := cat.SoC(soc) + e := &exporter{soc: s, idx: idx, board: firmware.Board(s, idx), + poolIDs: map[string]string{}, varIDs: map[string]string{}} + layout := ft + return e.camera(ft, &layout, edition, "eth", "nosd", ethaddr) + } + for _, tc := range []struct { + soc, ft, edition, board, erase string + }{ + {"gk7205v300", "nor16m", "lite", "gk7205v300", "0xfb0000"}, + {"gk7205v300", "nor8m", "ultimate", "gk7205v300", "0x7b0000"}, + // gk7205v210 is published as gk7205v200. + {"gk7205v210", "nor8m", "lite", "gk7205v200", "0x7b0000"}, + } { + c := mk(idx, tc.soc, tc.ft, tc.edition) + if !c.split { + t.Fatalf("%s %s %s: not a split install", tc.soc, tc.ft, tc.edition) + } + if got, want := c.firmwareFilename(), "openipc."+tc.board+"-nor-"+tc.edition+"-split.tgz"; got != want { + t.Errorf("%s: firmware %s, want %s", tc.soc, got, want) + } + linux := strings.Join(c.flashingLinux(), "\n") + want := "tftpboot 0x42000000 firmware.bin." + tc.board + " && sf erase 0x50000 " + tc.erase + + " && sf write 0x42000000 0x50000 ${filesize}" + if !strings.Contains(strings.ToLower(linux), strings.ToLower(want)) { + t.Errorf("%s %s lacks %q:\n%s", tc.soc, tc.ft, want, linux) + } + for _, gone := range []string{"uImage.", "rootfs.squashfs.", "sf lock", "run uk", "run ur"} { + if strings.Contains(linux, gone) { + t.Errorf("%s %s still writes %q:\n%s", tc.soc, tc.ft, gone, linux) + } + } + c.iface, c.sd = "wifi", "sd" + if sd := strings.Join(c.flashingLinux(), "\n"); !strings.Contains(sd, "fatload mmc 0:1 0x42000000 firmware.bin."+tc.board+" && sf erase 0x50000 "+tc.erase) { + t.Errorf("%s %s SD path does not write firmware.bin:\n%s", tc.soc, tc.ft, sd) + } + } + + // Not published (this edition, or this nightly): the two images. + for _, c := range []*camera{ + mk(idx, "gk7205v210", "nor8m", "ultimate"), + mk(plainIdx, "gk7205v300", "nor16m", "lite"), + } { + if c.split { + t.Errorf("%s %s: split without a published package", c.soc.Model, c.edition) + } + if !strings.Contains(strings.Join(c.flashingLinux(), "\n"), "rootfs.squashfs.") { + t.Errorf("%s %s: no two-image install to fall back to", c.soc.Model, c.edition) + } + } +}