banner
ekko

ekko's blog

时间不在于你拥有多少,而在于你怎样使用
github
xbox
email

openocd配置文件解析

rk3568.cfg#

[!/usr/local/share/openocd/scripts/target/rk3568.cfg]

# SPDX-License-Identifier: GPL-2.0-or-later
reset_config trst_and_srst separate
if { [info exists CHIPNAME] } {
  set _CHIPNAME $CHIPNAME
} else {
  set _CHIPNAME rk3568
}

#
# Main DAP
#
if { [info exists DAP_TAPID] } {
   set _DAP_TAPID $DAP_TAPID
} else {
   set _DAP_TAPID 0x2ba01477
}

adapter driver jlink
adapter speed 12000
transport select swd

# declare the one SWD tap to access the DAP
swd newdap $_CHIPNAME cpu -expected-id $_DAP_TAPID -ignore-version
# create the DAP
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu
target create $_CHIPNAME.ahb mem_ap -dap $_CHIPNAME.dap -ap-num 0
set _TARGETNAME $_CHIPNAME.lcore
# declare the 6 main application cores
set _smp_command ""

set $_TARGETNAME.base(0) 0x81004000
set $_TARGETNAME.base(1) 0x81005000
set $_TARGETNAME.base(2) 0x81006000
set $_TARGETNAME.base(3) 0x81007000
set $_TARGETNAME.cti(0) 0x81014000
set $_TARGETNAME.cti(1) 0x81015000
set $_TARGETNAME.cti(2) 0x81016000
set $_TARGETNAME.cti(3) 0x81017000


set _cores 4
for { set _core 0 } { $_core < $_cores } { incr _core 1 } {

    set _TARGETNAME $_CHIPNAME.lcore

    cti create cti$_core -dap $_CHIPNAME.dap -baseaddr [set $_TARGETNAME.cti($_core)] -ap-num 0

    target create \${_TARGETNAME}$_core aarch64 -dap $_CHIPNAME.dap -coreid  $_core -cti cti\$_core -dbgbase [set $_TARGETNAME.base($_core)]

    if { $_core != 0 } {
        ${_TARGETNAME}$_core configure -defer-examine
    } else {
        # uncomment to use hardware threads pseudo rtos
        # ${_TARGETNAME}$_core configure -rtos hwthread"
        ${_TARGETNAME}$_core configure -work-area-size 0x30000 -work-area-phys 0xff8c0000 \
                                -work-area-backup 0
    }
    set _smp_command "$_smp_command ${_TARGETNAME}$_core"
}
# Add the second flash bank.

#set QUADSPI 1
#set _FLASHNAME $_CHIPNAME.flash1
#flash bank $_FLASHNAME stmqspi 0 0x4000000 2 2 ${_TARGETNAME}0
#flash bank $_FLASHNAME fespi 0 0 0 0 ${_TARGETNAME}0 0xfe610000

target smp $_smp_command
targets rk3568.lcore0

本文主要以上述配置文件为例,讲解 [[openocd]] 每条配置命令的作用。

reset_config#

reset_config trst_and_srst separate

Command: reset_config mode_flag ...

该命令在目标配置脚本中显示或修改 JTAG 板和目标组合的复位配置。

本节前面的信息描述了该命令要解决的问题类型(参见 SRST 和 TRST 问题)。一般来说,该命令仅适用于电路板配置文件,用于描述 board doesn't connect TRST 等问题;或适用于用户配置文件,用于解决接口和电路板的特定组合所带来的限制。(一个不太可能的例子是将仅连接 TRST 的适配器与仅连接 SRST 的电路板一起使用)。

模式标志(mode_flag)选项可以任意顺序指定,但信号、组合、门、trst_type、srst_type 和 connect_type 每种类型一次只能指定一个。如果不为给定类型提供新值,其先前值(可能是默认值)将保持不变。例如,这意味着如果 JTAG 适配器要驱动 SRST,则必须明确地将其驱动为高电平(srst_push_pull),而无需对 TRST 做任何说明。

  • 信号可以指定连接哪些复位信号。例如,如果 JTAG 接口提供 SRST,但电路板没有正确连接该信号,则 OpenOCD 无法使用它。可能的值有:无(默认值)、trst_only、srst_only 和 trst_and_srst
> **提示:** 如果您的电路板通过 JTAG 连接器提供 SRST 和/或 TRST,您必须声明,以便可以使用这些信号。
  • 该组合是一个可选值,指定损坏的复位信号实现。如果没有给出任何选项,则默认行为是单独的,表示一切正常。 srst_pulls_trst 表示测试逻辑与系统重置一起重置(例如 NXP LPC2000,“损坏的” 电路板布局),trst_pulls_srst 表示系统与测试逻辑一起重置(仅假设,我还没有看到硬件与这样的错误,并且可以解决)。组合意味着 srst_pulls_trst 和 trst_pulls_srst。

  • SRST_gates_jtag (默认)表示断定 SRST 时,JTAG 时钟将被门控。这意味着当 SRST 被断言时,JTAG 上不会发生任何通信。其反义词是 srst_nogate,表示在 SRST 处于活动状态时,可以安全地发出 JTAG 命令。

  • connect_type 标记控制描述 SRST 在连接目标时被断言的某些情况的标志。 connect_deassert_srst(默认)表示 SRST 在连接目标时不会被断言。与之相反的 connect_assert_srst,表示 SRST 将在任何目标连接之前被断言。只有部分目标支持该功能,例如 STM32 和 STR9。如果由于不正确的选项字节配置或非法程序执行导致无法连接目标,该功能将非常有用。

可选的 trst_type 和 srst_type 参数允许指定每条复位线的驱动模式。这些值仅影响支持不同驱动模式的 JTAG 接口,如 Amontec JTAGkey 和 JTAG Accelerator。此外,如果未连接相关信号(TRST 或 SRST),这些值必然会被忽略。

  • 测试复位信号 (TRST) 可能的 trst_type 驱动模式有默认的 trst_push_pull 和 trst_open_drain。大多数电路板将该信号连接到下拉,因此 JTAG TAP 除非连接到 JTAG 适配器,否则永远不会离开复位。
  • 系统复位信号 (SRST) 的 srst_type 驱动模式有默认的 srst_open_drain 和 srst_push_pull。大多数电路板将该信号连接至上拉器,并允许通过各种事件(包括系统上电和按下复位按钮)将该信号拉低。

info exit#

Config File Guidelines (OpenOCD User’s Guide)

Note

所有目标配置文件应以类似的代码开头,让电路板配置文件表达环境特定的差异。

电路板可以覆盖芯片名称,可能基于角色,
但默认值应与供应商使用的名称匹配

if { [info exists CHIPNAME] } {
   set  _CHIPNAME $CHIPNAME
} else {
   set  _CHIPNAME sam7x256
}
# 仅在可以更改的目标上使用 ENDIAN。
if { [info exists ENDIAN] } {
   set  _ENDIAN $ENDIAN
} else {
   set  _ENDIAN little
}
# TAP 标识符可能会随着芯片的成熟而变化,例如带有
# 新修订字段(这里的“3”)。选择一个好的默认值;您
# 可以将多个此类标识符传递给“jtag newtap”命令。
if { [info exists CPUTAPID ] } {
   set _CPUTAPID $CPUTAPID
} else {
   set _CPUTAPID 0x3f0f0f0f
}

记住: 电路板配置文件可以包含多个目标配置文件,或者多次使用相同的目标文件(至少更改 CHIPNAME)。

同样,目标配置文件应定义 _TARGETNAME(或 _TARGETNAME0 等)并在定义调试目标时使用:

set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME arm7tdmi -chain-position $_TARGETNAME

set#

set _CHIPNAME rk3568
set _DAP_TAPID 0x2ba01477
set _TARGETNAME $_CHIPNAME.lcore
set _smp_command ""
set $_TARGETNAME.base(0) 0x81004000
set $_TARGETNAME.base(1) 0x81005000
set $_TARGETNAME.base(2) 0x81006000
set $_TARGETNAME.base(3) 0x81007000
set $_TARGETNAME.cti(0) 0x81014000
set $_TARGETNAME.cti(1) 0x81015000
set $_TARGETNAME.cti(2) 0x81016000
set $_TARGETNAME.cti(3) 0x81017000
set _cores 4

set 为变量赋值命令,参数可以是字符串,数字,其他变量

adapter#

adapter driver jlink
adapter speed 12000

Debug Adapter Configuration (OpenOCD User’s Guide)

Config Command: adapter driver name

使用适配器驱动程序 name 连接到目标。
名称列表详见 Interface-Drivers
Command: adapter speed max_speed_kHz

非零速度以 KHZ 为单位。因此:3000 是 3mhz。JTAG 接口通常支持有限数量的速度。实际使用的速度不会超过指定的速度。

芯片数据表通常包括最高 JTAG 时钟速率。实际速率通常是 CPU 核心时钟的函数,通常低于该峰值速率。例如,大多数 ARM 核心最多接受 CPU 时钟的六分之一。

速度 0(khz)选择 RTCK 方法。请参见 FAQ RTCK。如果您的系统使用 RTCK,则在设置后无需更改 JTAG 时钟。并非所有接口、电路板或目标都支持 “rtck”。如果接口设备不支持它,尝试使用 RTCK 时会返回错误。

transport select and swd newdap#

transport select swd
swd newdap $_CHIPNAME cpu -expected-id $_DAP_TAPID -ignore-version

Transport-Configuration

SWD(串行线调试)是 ARM 特定的传输方式,暴露一个调试访问点(DAP,必须明确声明)。(SWD 使用的信号线比 JTAG 少。) SWD 以调试为导向,不支持边界扫描测试。闪存编程支持建立在调试支持之上。(一些处理器同时支持 JTAG 和 SWD。)

使用命令 transport select swd 选择 SWD 传输。除非您的适配器使用 hla 接口驱动程序(在这种情况下命令为 transport select hla_swd)或 [st-link 接口驱动程序](https://openocd.org/doc/html/Debug-Adapter-Configuration.html#st_005flink_005fdap_005finterface)(在这种情况下命令为 transport select dapdirect_swd)。

Config Command: swd newdap ...

声明一个使用 SWD 传输的单个 DAP。参数目前与 "jtag newtap" 相同,但预计会发生变化。

更新的 SWD 设备(SW-DP v2 或 SWJ-DP v2)支持 SWD 协议的多点扩展:两个或多个设备可以连接到一个 SWD 适配器。如果 DAP 配置了 -dp-id-instance-id 参数,则 SWD 传输在多点模式下工作,无论创建了多少 DAP。

并非所有适配器和适配器驱动程序都支持 SWD 多点。只有以下适配器驱动程序支持 SWD 多点:cmsis_dap(使用 CMSIS-DAP 版本 2.0 的适配器)、ftdi、所有基于 bitbang 的适配器。

TAP-Declaration-Commands

Config Command: jtag newtap chipname tapname configparams...
声明一个新的 TAP,带有点名 chipname.tapname,并根据各种 configparams 进行配置。

chipname 是芯片的符号名称。通常目标配置文件使用 $_CHIPNAME,默认为芯片供应商提供的型号名称,但可以覆盖。

tapname 反映该 TAP 的角色,应遵循以下约定:

  • bs – 如果这是一个单独的 TAP,则用于边界扫描;
  • cpu – 芯片的主 CPU,或者在同时具有 ARM 和 DSP CPU 的芯片上使用 arm 和 dsp,在具有两个 ARM 的芯片上使用 arm1 和 arm2,依此类推;
  • etb – 用于嵌入式跟踪缓冲区(例如:ARM ETB11);
  • flash – 如果芯片具有闪存 TAP,例如 str912;
  • jrc – 用于 JTAG 路由控制器(例如:许多德州仪器芯片上的 ICEPick 模块,如 Beagleboards 上的 OMAP3530);
  • tap – 仅应用于具有单个 TAP 的 FPGA 或 CPLD 类设备;
  • unknownN – 如果您不知道该 TAP 的用途(N 是一个数字);
  • 当不确定时 – 使用芯片制造商在其数据表中的名称。例如,Freescale i.MX31 具有一个带 JTAG TAP 的 SDMA(智能 DMA);该 TAP 应命名为 sdma

每个 TAP 至少需要以下 configparams:

  • -irlen NUMBER
    指令寄存器的位长度,例如 4 或 5 位。

TAP 还可以提供可选的 configparams:

  • -disable (或 -enable
    使用 -disable 参数标记在使用 TRST 或 JTAG 状态机的 RESET 状态重置后未链接到扫描链的 TAP。您可以使用 -enable 突出显示默认状态(TAP 已链接)。请参见 启用和禁用 TAPs

  • -expected-id NUMBER
    非零 number 表示您期望在检查扫描链时找到的 32 位 IDCODE。这些代码并非所有 JTAG 设备都要求。根据需要重复该选项,如果可能出现多个 ID 代码(例如,多个版本)。如果要抑制关于找到但未包含在列表中的 IDCODE 值的警告,请将 number 指定为零。

    如果可能,请提供此值,因为它可以让 OpenOCD 知道它看到的扫描链不正确。这些值在供应商的芯片文档中提供,通常是技术参考手册。有时您可能需要探测 JTAG 硬件以找到这些值。请参见 自动探测

  • -ignore-version
    指定此选项以忽略 -expected-id 选项中的 JTAG 版本字段。当供应商发布多个版本的芯片,或对几个大致兼容的芯片使用相同的 JTAG 级 ID 时,忽略版本字段可能比更新配置文件以处理所有各种芯片 ID 更实际。版本字段定义为 IDCODE 的第 28-31 位。

  • -ignore-bypass
    指定此选项以忽略 idcode 的 “旁路” 位。一些供应商在此位上放置了无效的 idcode。指定此选项以忽略此位,并且不将此 TAP 视为旁路模式。

  • -ircapture NUMBER
    TAP 在进入 IRCAPTURE 状态时加载到 JTAG 移位寄存器的位模式,例如 0x01。JTAG 要求该值的两个 LSB 为 01。默认情况下,-ircapture 和 -irmask 设置为验证该两位值。如果您知道其他位,可以提供它们,或者指示 TAP 不符合 JTAG 规范。

  • -irmask NUMBER
    与 -ircapture 一起使用的掩码,以验证指令扫描是否正常工作。OpenOCD 不使用此类扫描,除非验证 JTAG 扫描链操作似乎没有问题。

  • -ignore-syspwrupack
    指定此选项以在初始检查和检查粘性错误位时忽略 ARM DAP DP CTRL/STAT 寄存器中的 CSYSPWRUPACK 位。通常在设置 CSYSPWRUPREQ 位后检查此位,但某些设备在稍后才设置确认位。

dap create#

dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu

dap declaration

Command: dap create dap_name -chain-position dotted.name configparams...

声明一个名为 dap_name 的 DAP 实例,链接到 JTAG tap dotted.name。这还创建了一个新命令(dap_name),用于各种目的,包括额外配置。系统中每个 JTAG tap 只能有一个 DAP。

DAP 还可以提供可选的 configparams:

  • -adiv5 指定它是 ADIv5 DAP。如果未指定,则为默认值。
  • -adiv6 指定它是 ADIv6 DAP。
  • -ignore-syspwrupack 指定此选项以在初始检查和检查粘性错误位时忽略 ARM DAP DP CTRL/STAT 寄存器中的 CSYSPWRUPACK 位。通常在设置 CSYSPWRUPREQ 位后检查此位,但某些设备在稍后才设置确认位。
  • -dp-id number
    SWD DPv2 多点的调试端口识别号。该 number 写入 DP TARGETSEL 的第 0..27 位。在单个连接设备中查找 id 号,请读取 DP TARGETID:device.dap dpreg 0x24 使用 TARGETID 的第 0..27 位。
  • -instance-id number
    SWD DPv2 多点的实例识别号。该 number 写入 DP TARGETSEL 的第 28..31 位。在单个连接设备中查找实例号,请读取 DP DLPIDR:device.dap dpreg 0x34 实例号在 DLPIDR 值的第 28..31 位。

target create and ${_TARGETNAME}$_core configure -defer-examine#

target create $_CHIPNAME.ahb mem_ap -dap $_CHIPNAME.dap -ap-num 0
target create ${_TARGETNAME}$_core aarch64 -dap $_CHIPNAME.dap -coreid $_core -cti cti$_core -dbgbase [set $_TARGETNAME.base($_core)]
${_TARGETNAME}$_core configure -defer-examine
${_TARGETNAME}$_core configure -work-area-size 0x30000 -work-area-phys 0xff8c0000 -work-area-backup 0

Config Command: target create target_name type configparams...

此命令创建一个 GDB 调试目标,引用特定的 JTAG tap。它将该目标输入到列表中,并创建一个新命令(target_name),用于各种目的,包括额外配置。

  • target_name ... 是调试目标的名称。根据约定,这应与与此目标相关联的 TAP 的 dotted.name 相同,必须在此处使用 -chain-position dotted.name configparam 指定。

    此名称还用于创建目标对象命令,在此称为 $target_name,在其他地方需要识别目标。

  • type ... 指定目标类型。请参见 target types

  • configparams ... 所有参数都可以由 $target_name configure 接受。如果目标是大端,则在此处使用 -endian big

    您 must 在此处设置 -chain-position dotted.name 或 -dap dap_name

Command: $target_name configure configparams...

此命令接受的选项也可以作为参数指定给 target create。其值可以稍后通过使用 $target_name cget 命令逐一查询。

警告: 在设置后更改其中一些内容是危险的。例如,将目标从一个 TAP 移动到另一个 TAP;以及更改其字节序。

  • -chain-position dotted.name – 命名用于访问此目标的 TAP。

  • -dap dap_name – 命名用于访问此目标的 DAP。请参见 DAP declaration,了解如何创建和管理 DAP 实例。

  • -endian (big|little) – 指定 CPU 使用大端或小端约定

  • -event event_name event_body – 请参见 Target Events。请注意,这会更新命名事件处理程序的列表。用两个不同的事件名称调用两次会分配两个不同的处理程序,但用相同的事件名称调用两次只分配一个处理程序。

    当前目标在处理程序代码开始之前临时覆盖为发出事件的目标,处理程序完成后切换回。

  • -work-area-backup (0|1) – 指示工作区是否备份;默认情况下,不备份。如果可能,请使用不需要备份的工作区域,因为执行备份会减慢操作。例如,SRAM 块的开头可能会被大多数构建系统使用,但结尾通常未使用。

  • -work-area-size size – 指定工作区域大小,以字节为单位。无论使用物理地址还是虚拟地址,均适用相同的大小。

  • -work-area-phys address – 设置在没有 MMU 活动时使用的工作区域基地址。

  • -work-area-virt address – 设置在 MMU 活动时使用的工作区域基地址。除非在具有 MMU 的目标上,否则不要指定此值。 该值通常应与当前操作系统设置的 -work-area-phys 地址的静态映射相对应。

  • -rtos rtos_type – 为目标启用 rtos 支持, rtos_type 可以是 auto、none、eCos、ThreadX、FreeRTOS、linux、ChibiOS、embKernel、mqx、uCOS-III、nuttx、RIOT、Zephyr、rtkernel,见 RTOS Support

  • -defer-examine – 跳过初始 JTAG 链扫描和重置后的目标检查。需要手动调用 arp_examine 以访问目标进行调试。

  • -ap-num ap_number – 设置目标的 DAP 访问端口。在 ADIv5 DAP 中,ap_number 是目标连接的 DAP AP 的数字索引。在 ADIv6 DAP 中,ap_number 是目标连接的 DAP AP 的基地址。使用此选项的系统中,多个独立核心连接到同一 DAP 的不同访问端口。

  • -cti cti_name – 设置连接到目标的交叉触发接口(CTI)。目前,只有 aarch64 目标使用此选项,这是目标运行控制的强制配置。请参见 ARM Cross-Trigger Interface,了解如何声明和控制 CTI 实例。

  • -gdb-port number – 请参见命令 gdb_port 以获取参数 number 的可能值,这些值不仅是数字值。使用此选项仅覆盖此目标的全局参数,该参数通过命令 gdb_port 设置。请参见 命令 gdb_port

  • -gdb-max-connections number – 实验性:设置允许的目标最大 GDB 连接数。默认值为 1。number 的负值表示无限连接。请参见 使用 GDB 作为非侵入式内存检查器

但是上述内容中没有关于-coreid-dbgbase选项的说明,在 SMP 相关章节有提到一点:
Define-CPU-targets-working-in-SMP

Note

在设置目标后,您可以定义在 SMP 中工作的目标列表。

set _TARGETNAME_1 $_CHIPNAME.cpu1
set _TARGETNAME_2 $_CHIPNAME.cpu2
target create $_TARGETNAME_1 cortex_a -chain-position $_CHIPNAME.dap \
-coreid 0 -dbgbase $_DAP_DBG1
target create $_TARGETNAME_2 cortex_a -chain-position $_CHIPNAME.dap \
-coreid 1 -dbgbase $_DAP_DBG2
#定义 2 个在 smp 中工作的目标。
target smp $_CHIPNAME.cpu2 $_CHIPNAME.cpu1

在上述 cortex_a 示例中,2 个 CPU 在 SMP 中工作。在 SMP 中,仅创建一个 GDB 实例:

  • 一组硬件断点在列表中的所有目标上设置相同的断点。
  • halt 命令触发列表中所有目标的停止。
  • resume 命令触发上下文写入和列表中所有目标的重启。
  • 在断点后:由断点停止的目标显示在 GDB 会话中。
  • 为切换 / 检索 GDB 会话中显示的目标实现了专用 GDB 串行协议数据包,见 使用 OpenOCD SMP 与 GDB

SMP 行为可以动态禁用 / 启用。在 cortex_a 上实现了以下命令。

  • cortex_a smp on : 启用 SMP 模式,行为如上所述。
  • cortex_a smp off : 禁用 SMP 模式,当前目标是 GDB 会话中显示的目标,只有该目标现在由 GDB 会话控制。此行为在系统启动期间非常有用。
  • cortex_a smp : 显示当前 SMP 模式。
  • cortex_a smp_gdb : 显示 / 修复在 GDB 会话中显示的核心 ID,见以下示例。
cortex_a smp_gdb
gdb coreid  0 -> -1
#0 : coreid 0 is displayed to GDB ,
#-> -1 : next resume triggers a real resume
cortex_a smp_gdb 1
gdb coreid  0 -> 1
#0 :coreid 0 is displayed to GDB ,
#->1  : next resume displays coreid 1 to GDB
resume
cortex_a smp_gdb
gdb coreid  1 -> 1
#1 :coreid 1 is displayed to GDB ,
#->1 : next resume displays coreid 1 to GDB
cortex_a smp_gdb -1
gdb coreid  1 -> -1
#1 :coreid 1 is displayed to GDB,
#->-1 : next resume triggers a real resume

上述例子中除了 target create 之外,以及 target smp 命令也有涉及到

cti create#

cti create cti$_core -dap $_CHIPNAME.dap -baseaddr [set $_TARGETNAME.cti($_core)] -ap-num 0

ARM-Cross_002dTrigger-Interface

Command: cti create cti_name -dap dap_name -ap-num apn -baseaddr base_address

创建一个 CTI 实例 cti_name 在 DAP 实例 dap_name 上,位于 MEM-AP apn。在 ADIv5 DAP 中,apn 是 CTI 连接的 DAP AP 的数字索引。在 ADIv6 DAP 中,apn 是 CTI 连接的 DAP AP 的基地址。base_address 必须与相应 MEM-AP 上 CTI 的基地址匹配。所有参数都是必需的。这创建了一个新命令 $cti_name,用于各种目的,包括额外配置。

targets#

targets rk3568.lcore0

所有已设置的目标都是一个列表的一部分,每个成员都有一个名称。该名称通常应与 TAP 名称相同。您可以使用 targets(复数!)命令显示列表。此显示通常只有一个 CPU;如果有多个,可能如下所示:

TargetNameTypeEndianTapNameState
0* at91rm9200.cpuarm920tlittleat91rm9200.cpurunning
1 MyTargetcortex_mlittlemychip.footap-disabled

该列表的一个成员是 current target,许多命令隐式引用它。它是标记为目标名称旁边的 * 的目标。特别是,内存地址通常指的是当前目标所看到的地址空间。像 mdw(内存显示字)和 flash erase_address(擦除 NOR 闪存块)这样的命令就是例子;还有很多其他的。

几个命令让您检查目标列表:

Command: target current

返回当前目标的名称。

Command: target names

列出列表中所有当前目标的名称。

foreach t [target names] {
puts [format "Target: %s\n" $t]
}

Command: targets [name]

注意:此命令的名称是复数。其他目标命令名称是单数。

如果没有参数,此命令以用户友好的形式显示所有已知目标的表。

如果有参数,此命令将当前目标设置为给定名称的目标;这只适用于有多个目标的电路板。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。