From 9c7f7f6e241e4f238c5d9980d3c18fe20a7a9a58 Mon Sep 17 00:00:00 2001 From: bakacai Date: Thu, 29 Jan 2026 17:18:36 +0800 Subject: [PATCH] feat(ui): replace hex input with option-based stub configuration --- src/i18n/locales/en.json | 18 +- src/i18n/locales/zh.json | 16 +- src/views/StubConfigView.vue | 322 +++++++++++++++++++++++++++-------- 3 files changed, 279 insertions(+), 77 deletions(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 618fd58..096b634 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -244,10 +244,20 @@ "driverIndex": "Type", "capacity": "Capacity", "capacityHint": "Supports numbers, hexadecimal (0x...) or with units (16M)", + "capacityRequired": "Please enter capacity", + "requiredField": "Required", "manufacturerId": "Manufacturer ID", - "deviceType": "Device Type", - "densityId": "Density ID", - "flags": "Flags", + "deviceType": "Device ID1", + "densityId": "Device ID2", + "flags": "Flag", + "flagsNorDtr": "DTR supported", + "flagsNandDualPlane": "double plane", + "flagsNandPageSize": "Page size", + "flagsNandBlockSize": "Block size", + "flagsNandEccMode": "ECC mode", + "flagsOptionEnabled": "Enabled", + "flagsOptionDisabled": "Disabled", + "chipIdTooltip": "The first three bytes read by the 9Fh instruction.", "deviceInvalid": "Invalid", "sectionInvalid": "Invalid", "noDevices": "No FLASH devices added", @@ -366,7 +376,7 @@ } }, "stubConfig": { - "title": "", + "title": " ", "apply": "Apply Custom Stub Configuration", "invalidConfig": "Configuration contains errors and cannot be enabled", "configHasErrors": "Configuration contains errors, please check and correct on the Stub configuration page" diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index a088698..9cff6ee 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -244,10 +244,20 @@ "driverIndex": "Type", "capacity": "容量", "capacityHint": "支持数字、十六进制 (0x...) 或带单位 (16M)", - "manufacturerId": "制造商 ID", - "deviceType": "设备类型", - "densityId": "颗粒 ID", + "capacityRequired": "请输入容量", + "requiredField": "必填项", + "manufacturerId": "Manufacturer ID", + "deviceType": "Device ID1", + "densityId": "Device ID2", "flags": "标志", + "flagsNorDtr": "DTR 支持", + "flagsNandDualPlane": "双 plane", + "flagsNandPageSize": "Page 大小", + "flagsNandBlockSize": "Block 大小", + "flagsNandEccMode": "ECC 模式", + "flagsOptionEnabled": "开启", + "flagsOptionDisabled": "关闭", + "chipIdTooltip": "9Fh指令读取到的前三个byte", "deviceInvalid": "配置有误", "sectionInvalid": "配置有误", "noDevices": "尚未添加 FLASH 设备", diff --git a/src/views/StubConfigView.vue b/src/views/StubConfigView.vue index fdc0b49..b06cef4 100644 --- a/src/views/StubConfigView.vue +++ b/src/views/StubConfigView.vue @@ -44,7 +44,7 @@
{{ $t('stubConfig.flash.media') }} - @@ -108,69 +112,155 @@
-
- - - -
-
- - - -
-
- - -
@@ -551,6 +641,11 @@ type FlashDevice = { device_type: string; density_id: string; flags: string; + nor_dtr?: boolean; + nand_dual_plane?: boolean; + nand_page_size?: number; + nand_block_size?: number; + nand_ecc_mode?: number; capacity_bytes: string | number; expanded: boolean; capacityError?: string; @@ -615,6 +710,8 @@ const flashConfig = computed({ }, }); +const flashDevices = computed(() => flashConfig.value.devices as FlashDevice[]); + // PMIC 可用通道 const availablePmicChannels = [ '1v8_lvsw100_1', @@ -718,7 +815,7 @@ const validateCapacity = (device: any) => { const value = device.capacity_bytes; if (!value) { - device.capacityError = ''; + device.capacityError = t('stubConfig.flash.capacityRequired'); return; } @@ -787,7 +884,7 @@ const validateHexField = (device: any, fieldName: string) => { : 'flagsError'; if (!value) { - device[errorFieldName] = ''; + device[errorFieldName] = t('stubConfig.flash.requiredField'); return; } @@ -806,6 +903,67 @@ const validateHexField = (device: any, fieldName: string) => { device[errorFieldName] = t('stubConfig.flash.invalidHexValue'); }; +const parseFlagsByte = (flags: string) => { + const valueStr = String(flags || '').trim(); + const hexPattern = /^0x[0-9a-fA-F]{1,2}$/; + if (!hexPattern.test(valueStr)) { + return null; + } + return parseInt(valueStr, 16); +}; + +const parseFlagsToUi = (device: FlashDevice) => { + const parsed = parseFlagsByte(device.flags); + const byte = parsed === null ? 0 : parsed; + device.flagsError = parsed === null ? t('stubConfig.flash.invalidHexValue') : ''; + + if (device.media === 'nor') { + device.nor_dtr = (byte & 0x01) !== 0; + device.nand_dual_plane = false; + device.nand_page_size = 2048; + device.nand_block_size = 64; + device.nand_ecc_mode = 0; + return; + } + + device.nor_dtr = false; + device.nand_dual_plane = (byte & 0x02) !== 0; + device.nand_page_size = (byte & 0x04) !== 0 ? 4096 : 2048; + device.nand_block_size = (byte & 0x08) !== 0 ? 128 : 64; + const ecc = (byte >> 4) & 0x0f; + device.nand_ecc_mode = ecc <= 6 ? ecc : 0; +}; + +const updateFlagsFromUi = (device: FlashDevice) => { + let byte = 0; + const norDtr = device.nor_dtr ?? false; + const nandDualPlane = device.nand_dual_plane ?? false; + const nandPageSize = device.nand_page_size ?? 2048; + const nandBlockSize = device.nand_block_size ?? 64; + const nandEccMode = device.nand_ecc_mode ?? 0; + + if (device.media === 'nor') { + byte |= norDtr ? 0x01 : 0x00; + } else { + byte |= nandDualPlane ? 0x02 : 0x00; + byte |= nandPageSize === 4096 ? 0x04 : 0x00; + byte |= nandBlockSize === 128 ? 0x08 : 0x00; + const ecc = Math.min(6, Math.max(0, Number(nandEccMode) || 0)); + byte |= ecc << 4; + } + + device.flags = `0x${byte.toString(16).padStart(2, '0')}`; + device.flagsError = ''; +}; + +const validateFlashDeviceRequired = (device: FlashDevice) => { + validateCapacity(device); + validateHexField(device, 'manufacturer_id'); + validateHexField(device, 'device_type'); + validateHexField(device, 'density_id'); + validateHexField(device, 'flags'); +}; + const createFlashDevice = (overrides: Partial = {}): FlashDevice => { return { id: `${Date.now()}-${Math.random().toString(16).slice(2)}`, @@ -814,7 +972,12 @@ const createFlashDevice = (overrides: Partial = {}): FlashDevice => manufacturer_id: '', device_type: '', density_id: '', - flags: '', + flags: '0x00', + nor_dtr: false, + nand_dual_plane: false, + nand_page_size: 2048, + nand_block_size: 64, + nand_ecc_mode: 0, capacity_bytes: '', // 16MB expanded: false, capacityError: '', @@ -851,7 +1014,10 @@ const addFlashDevice = () => { logStore.addMessage(t('stubConfig.flash.maxReached'), true); return; } - flashConfig.value.devices.push(createFlashDevice()); + const device = createFlashDevice(); + updateFlagsFromUi(device); + validateFlashDeviceRequired(device); + flashConfig.value.devices.push(device); }; // 移除 FLASH 设备 @@ -859,6 +1025,18 @@ const removeFlashDevice = (index: number) => { flashConfig.value.devices.splice(index, 1); }; +const onFlashMediaChange = (device: FlashDevice) => { + if (device.media === 'nor') { + device.nor_dtr = device.nor_dtr ?? false; + } else { + device.nand_dual_plane = device.nand_dual_plane ?? false; + device.nand_page_size = device.nand_page_size ?? 2048; + device.nand_block_size = device.nand_block_size ?? 64; + device.nand_ecc_mode = device.nand_ecc_mode ?? 0; + } + updateFlagsFromUi(device); +}; + const collapseAllFlashDevices = () => { flashConfig.value.devices.forEach(device => { device.expanded = false; @@ -1018,8 +1196,8 @@ const applyImportedConfig = (config: any) => { // flash if (Array.isArray(config.flash)) { - flashConfig.value.devices = config.flash.slice(0, 12).map((f: any) => - createFlashDevice({ + flashConfig.value.devices = config.flash.slice(0, 12).map((f: any) => { + const device = createFlashDevice({ media: f.media === 'nand' ? 'nand' : 'nor', driver_index: Number(f.driver_index) || 0, manufacturer_id: f.manufacturer_id ?? '0x00', @@ -1027,8 +1205,11 @@ const applyImportedConfig = (config: any) => { density_id: f.density_id ?? '0x00', flags: f.flags ?? '0x00', capacity_bytes: f.capacity_bytes ?? '0x1000000', - }) - ); + }); + parseFlagsToUi(device); + validateFlashDeviceRequired(device); + return device; + }); } }; @@ -1090,6 +1271,7 @@ onMounted(async () => { await loadConfigFromLocal(); collapseAllFlashDevices(); ensurePinIds(); + flashConfig.value.devices.forEach(device => parseFlagsToUi(device)); }); // 监听配置变化,自动保存到Pinia store和本地