Linux設(shè)備驅(qū)動(dòng)統(tǒng)一模型解析
2. 設(shè)備樹解析流程
2.1.內(nèi)核啟動(dòng)并獲取設(shè)備樹
在uboot引導(dǎo)內(nèi)核的時(shí)候,會(huì)將設(shè)備樹在物理內(nèi)存中的物理起始內(nèi)存地址傳遞給Linux內(nèi)核,然后Linux內(nèi)核在unflattern_device_tree中解析設(shè)備鏡像,并利用掃描到的信息創(chuàng)建由device node構(gòu)成的鏈表,全局變量of_allnodes指向鏈表的根節(jié)點(diǎn),設(shè)備樹的每一個(gè)節(jié)點(diǎn)都由一個(gè)struct device_node與之對(duì)應(yīng)。unflatten_device_tree的意思是解開設(shè)備樹,在這個(gè)函數(shù)里調(diào)用了__unflatten_device_tree這一函數(shù):
*
* __unflatten_device_tree - create tree of device_nodes from flat blob
*
* unflattens a device-tree, creating the
* tree of struct device_node. It also fills the "name" and "type"
* pointers of the nodes so the normal device-tree walking functions
* can be used.
* @blob: The blob to expand
* @m(xù)ynodes: The device_node tree created by the call
* @dt_alloc: An allocator that provides a virtual address to memory
* for the resulting tree
static void __unflatten_device_tree(struct boot_param_h(yuǎn)eader *blob,
struct device_node **mynodes,
void * (*dt_alloc)(u64 size, u64 align))
所以,現(xiàn)在為止,我們得到了一個(gè)名為of_allnodes的struct *device_node,它指向了設(shè)備樹展開后的device_node樹,后續(xù)的操作都是基于device_node樹。
2.2.創(chuàng)建platform_device
內(nèi)核從啟動(dòng)到創(chuàng)建設(shè)備的過程大致如下:在do_initcalls中會(huì)傳遞level給do_initcall_level來調(diào)用不同層次的初始化函數(shù),level的對(duì)應(yīng)關(guān)系見linux-3.10/include/linux/init.h 第196行。在這個(gè)初始化過程中,會(huì)調(diào)用一個(gè)customize_machine的函數(shù)。
2.3.Platform driver注冊(cè)流程
此節(jié)分析Platform driver的注冊(cè)流程,以memctrl驅(qū)動(dòng)的注冊(cè)為例分析。關(guān)于系統(tǒng)調(diào)用驅(qū)動(dòng)初始化函數(shù)的流程分析,參考自動(dòng)初始化機(jī)制章節(jié)。本章節(jié)分析從設(shè)備驅(qū)動(dòng)文件的xxx_init函數(shù)開始分析。
2.3.1. struct platform_driver
platform_driver是在device_driver之上的一層封裝,其結(jié)構(gòu)如下:
struct platform_driver {
int (*probe)(struct platform_device *); 探測(cè)函數(shù)
int (*remove)(struct platform_device *); 驅(qū)動(dòng)卸載時(shí)執(zhí)行
void (*shutdown)(struct platform_device *); 關(guān)機(jī)時(shí)執(zhí)行函數(shù)
int (*suspend)(struct platform_device *, pm_message_t state); 掛起函數(shù)
int (*resume)(struct platform_device *); 恢復(fù)函數(shù)
struct device_driver driver; 管理的driver對(duì)象
const struct platform_device_id *id_table; 匹配時(shí)使用
};
2.3.2. struct device_driver
struct device_driver是系統(tǒng)提供的基本驅(qū)動(dòng)結(jié)構(gòu):
struct device_driver {
const char *name; 驅(qū)動(dòng)名稱
struct bus_type *bus; 所屬總線
struct module *owner; 模塊擁有者
const char *mod_name; 內(nèi)建的模塊使用
bool suppress_bind_attrs; 是否綁定到sysfs
const struct of_device_id *of_match_table; 設(shè)備樹匹配表
const struct acpi_device_id *acpi_match_table; ACPI匹配表
int (*probe) (struct device *dev); 探測(cè)設(shè)備
int (*remove) (struct device *dev); 與設(shè)備脫離時(shí)調(diào)用
void (*shutdown) (struct device *dev); 在關(guān)機(jī)時(shí)關(guān)閉設(shè)備
int (*suspend) (struct device *dev, pm_message_t state); 使設(shè)備進(jìn)入睡眠模式調(diào)用
int (*resume) (struct device *dev); 喚醒設(shè)備時(shí)調(diào)用
const struct attribute_group **groups; 自動(dòng)創(chuàng)建的默認(rèn)屬性組
const struct dev_pm_ops *pm; 設(shè)備的功耗管理
struct driver_private *p; 驅(qū)動(dòng)的私有數(shù)據(jù)
};
2.3.3. platform_driver_register
Platform_driver的注冊(cè)接口是platform_driver_register,其定義如下:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type; 設(shè)置總線類型
if (drv->probe) 確認(rèn)定義了probe函數(shù)
drv->driver.probe = platform_drv_probe; 里面實(shí)際調(diào)用的是drv的probe函數(shù)
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
platform_driver_register接口是為注冊(cè)總線驅(qū)動(dòng)做一些準(zhǔn)備工作,定義了總線類型,設(shè)置了driver的部分接口,最后driver_register會(huì)向總線注冊(cè)驅(qū)動(dòng)
2.3.4. driver_registerint driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
BUG_ON(!drv->bus->p);
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods", drv->name);
other = driver_find(drv->name, drv->bus); 檢查驅(qū)動(dòng)是否已經(jīng)注冊(cè)
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv); driver_register的主要工作放在了這里
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups); 主要是在sysfs添加驅(qū)動(dòng)屬性
if (ret) {
bus_remove_driver(drv);
return ret;
}
kobject_uevent(&drv->p->kobj, KOBJ_ADD); 涉及到uevent,暫時(shí)不分析
return ret;
}
2.3.5. bus_add_driver
由以上分析可知,驅(qū)動(dòng)的注冊(cè),重點(diǎn)在bus_add_driver()函數(shù),它會(huì)向總線添加驅(qū)動(dòng):
Drivers/base/bus.c
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv; 包含與驅(qū)動(dòng)相關(guān)的kobject和klist結(jié)構(gòu)
int error = 0;
bus = bus_get(drv->bus); 獲取設(shè)備所屬的總線類型
if (!bus)
return -EINVAL;
pr_debug("bus: '%s': add driver %s", bus->name, drv->name);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
if (error)
goto out_unregister;
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) { 如果設(shè)置了自動(dòng)探測(cè)
error = driver_attach(drv);
if (error)
goto out_unregister;
}
module_add_driver(drv->owner, drv);
error = driver_create_file(drv, &driver_attr_uevent);
if (error) {
printk(KERN_ERR "%s: uevent attr (%s) failed",
__func__, drv->name);
}
error = driver_add_attrs(bus, drv);
if (error) {
How the hell do we get out of this pickle? Give up
printk(KERN_ERR "%s: driver_add_attrs(%s) failed",
__func__, drv->name);
}
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
if (error) {
Ditto
printk(KERN_ERR "%s: add_bind_files(%s) failed",
__func__, drv->name);
}
}
return 0;
out_unregister:
kobject_put(&priv->kobj);
kfree(drv->p);
drv->p = NULL;
out_put_bus:
bus_put(bus);
return error;
}
2.3.6. driver_attach
driver_attach會(huì)嘗試綁定設(shè)備和驅(qū)動(dòng)。編譯總線上的所有設(shè)備,然驅(qū)動(dòng)挨個(gè)嘗試匹配,如果driver_probe_device()返回0且@dev->driver被設(shè)置,就代表找到了一對(duì)兼容的設(shè)備驅(qū)動(dòng)。
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
EXPORT_SYMBOL_GPL(driver_attach);
2.3.7. __driver_attach
對(duì)于每一個(gè)總線的設(shè)備,driver_attach都會(huì)調(diào)用__driver_attach來嘗試與驅(qū)動(dòng)匹配。
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
*
* driver_probe_device() will spit a warning if there
* is an error.
if (!driver_match_device(drv, dev)) 匹配設(shè)備和驅(qū)動(dòng),這里調(diào)用的是platform_match
return 0;
if (dev->parent) Needed for USB
device_lock(dev->parent);
device_lock(dev); 設(shè)置互斥鎖,防止其他進(jìn)程訪問設(shè)備資源
if (!dev->driver)
如果設(shè)備沒有驅(qū)動(dòng),則為設(shè)備探測(cè)驅(qū)動(dòng),這個(gè)函數(shù)與注冊(cè)設(shè)備調(diào)用的是同一個(gè)函數(shù)
driver_probe_device(drv, dev);
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
driver_probe_device里調(diào)用really_probe函數(shù),并在really_probe中調(diào)用驅(qū)動(dòng)文件中的probe函數(shù),對(duì)于memctrl驅(qū)動(dòng)而言,就是xxxx_memctrl_probe函數(shù)。至此,platfprm driver就注冊(cè)好了。
2.4.Platform Bus的匹配原則
由以上的代碼分析得知,注冊(cè)platform device時(shí),會(huì)調(diào)用__device_attach -> driver_match_device,注冊(cè)platform driver時(shí),會(huì)調(diào)用__driver_attach -> driver_match_device,也就是說設(shè)備和驅(qū)動(dòng)都會(huì)調(diào)用到這個(gè)函數(shù):
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
drv->bus->match,這是驅(qū)動(dòng)綁定的總線提供的匹配函數(shù),這里注冊(cè)的是platform總線設(shè)備,而platform總線的定義參考3.2.6 platform_bus_type。Platform對(duì)應(yīng)的match函數(shù)為:platform_match:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
Attempt an OF style match first
if (of_driver_match_device(dev, drv))
return 1;
Then try ACPI style match
if (acpi_driver_match_device(dev, drv))
return 1;
Then try to match against the id table
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) 。 NULL;
fall-back to driver name match
return (strcmp(pdev->name, drv->name) == 0);
}
2.4.1. of_driver_match_device
根據(jù)驅(qū)動(dòng)的of_match_table判斷是否有驅(qū)動(dòng)與之匹配。對(duì)memctrl驅(qū)動(dòng)而言,其of_match_table如下:
static struct of_device_id xxxx_memctrl_of_match[] = {
{ .compatible = "xxxx,memctrl", },
{},
};
of_driver_match_device的執(zhí)行流程如下:
所以重點(diǎn)應(yīng)該在__of_match_node函數(shù):
2.4.1.1. __of_match_nodestatic const struct of_device_id *__of_match_node(const struct of_device_id *matches, const struct device_node *node)
{
if (!matches)
return NULL;
while (matches->name[0] || matches->type[0] || matches->compatible[0]) {
int match = 1;
if (matches->name[0]) 查找名字
match &= node->name && !strcmp(matches->name, node->name);
if (matches->type[0]) 查找類型
match &= node->type && !strcmp(matches->type, node->type);
if (matches->compatible[0]) 查找屬性,檢測(cè)節(jié)點(diǎn)的compatible是否與驅(qū)動(dòng)的一致
match &= __of_device_is_compatible(node, matches->compatible);
if (match)
return matches;
matches++;
}
return NULL;
}
3. 使用設(shè)備資源
4. 自動(dòng)初始化機(jī)制
4.1.編譯到內(nèi)核
4.1.1. module_init宏展開
Linux中每一個(gè)模塊都有一個(gè)module_init函數(shù),并且有且只有一個(gè),其定義如下:
*
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
#define module_init(x) __initcall(x);
__initcall(x)定義如下:
#define __initcall(fn) device_initcall(fn)
device_initcall(fn)定義如下:
#define device_initcall(fn) __define_initcall(fn, 6)
__define_initcall的定義如下:
initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
* by link order.
* For backwards compatibility, initcall() puts the call in
* the device init subsection.
*
* The `id' arg to __define_initcall() is needed so that multiple initcalls
* can point at the same handler without causing duplicate-symbol build errors.
#define __define_initcall(fn, id)
static initcall_t __initcall_##fn##id __used
__attribute__((__section__(".initcall" #id ".init"))) = fn
Initcalls現(xiàn)在按照功能分組到單獨(dú)的子部分。子部分內(nèi)部的順序由鏈接順序決定。為了向后兼容,initcall()將調(diào)用放到device init小節(jié)中。需要定義initcall()的’id’參數(shù),以便多個(gè)initcall可以指向同一個(gè)處理程序,而不會(huì)導(dǎo)致重復(fù)符號(hào)構(gòu)建錯(cuò)誤。若不理解上述代碼的用法,可以參考__attribute__的section用法和C語(yǔ)言宏定義中#和##的用法。所以將__define_initcall展開將會(huì)是下面的內(nèi)容:
假設(shè)__define_initcall(led_init, 6)
Static initcall_t __initcall_led_init6 __used
__attribute__((__section__(".initcall6.init"))) = led_init
即是定義了一個(gè)類型為initcall_t的函數(shù)指針變量__initcall_led_init6,并賦值為led_init,該變量在鏈接時(shí)會(huì)鏈接到section(.initcall6.init)。
4.1.2. 鏈接腳本
在linux3.10/arch/arm/kernel/vmlinux.lds.S中:
......
SECTIONS line 54
{
......
.init.data : { line 202
#ifndef CONFIG_XIP_KERNEL
INIT_DATA
#endif
INIT_SETUP(16)
INIT_CALLS
CON_INITCALL
SECURITY_INITCALL
INIT_RAM_FS
}
......
}
在linux3.10/include/asm-generic/vmlinux.lds.h中:
#define VMLINUX_SYMBOL(x) __VMLINUX_SYMBOL(x)
#define __VMLINUX_SYMBOL(x) x
...... line 664
#define INIT_CALLS_LEVEL(level)
VMLINUX_SYMBOL(__initcall##level##_start) = .;
*(.initcall##level##.init)
*(.initcall##level##s.init)
#define INIT_CALLS
VMLINUX_SYMBOL(__initcall_start) = .;
*(.initcallearly.init)
INIT_CALLS_LEVEL(0)
INIT_CALLS_LEVEL(1)
INIT_CALLS_LEVEL(2)
INIT_CALLS_LEVEL(3)
INIT_CALLS_LEVEL(4)
INIT_CALLS_LEVEL(5)
INIT_CALLS_LEVEL(rootfs)
INIT_CALLS_LEVEL(6)
INIT_CALLS_LEVEL(7)
VMLINUX_SYMBOL(__initcall_end) = .;
......
所以 INIT_CALLS_LEVEL(6)會(huì)展開為:
__initcall6_start = .; *(.initcall6.init) *(.initcall6s.init)
所以__initcall_led_init6會(huì)鏈接到
section(.initcall6.init)
4.1.3. 初始化
內(nèi)核啟動(dòng)流程為:
do_initcall_level的主要內(nèi)容如下:
linux3.10/init/main.c line 744
static void __init do_initcall_level(int level)
{
.....
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
由代碼可知,內(nèi)核會(huì)依次調(diào)用level段存儲(chǔ)的初始化函數(shù)。比如對(duì)于模塊來說level等于6。
4.2.動(dòng)態(tài)加載的模塊(.ko)4.2.1. Module_init展開
如果設(shè)置為編譯成動(dòng)態(tài)加載的模塊(.ko),module_init的展開形式與編譯到內(nèi)核不一樣。
Each module must use one module_init().
#define module_init(initfn)
static inline initcall_t __inittest(void) 檢查定義的函數(shù)是否符合initcall_t類型
{ return initfn; }
int init_module(void) __attribute__((alias(#initfn)));
alias屬性是GCC的特有屬性,將定義init_module為函數(shù)initfn的別名,所以module_init(initfn)的作用就是定義一個(gè)變量名 init_module,其地址和initfn是一樣的。
4.2.2. *mod.c文件
編譯成module的模塊都會(huì)自動(dòng)產(chǎn)生一個(gè)*.mod.c的文件,例如:
struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_M(jìn)ODNAME,
.init = init_module,
#ifdef CONFIG_M(jìn)ODULE_UNLOAD
.exit = cleanup_module,
#endif
.a(chǎn)rch = MODULE_ARCH_INIT,
};
即定義了一個(gè)類型為module的全局變量__this_module,其成員.init就是上文由module_init定義的init_module變量。并且__this_module會(huì)被鏈接到section(".gnu.linkonce.this_module")。
4.2.3. 動(dòng)態(tài)加載
insmod是busybox提供的用戶層命令:路徑busybox/modutils/ insmod.c
insmod_main
bb_init_module
init_module
路徑busybox/modutils/modutils.c:
#define init_module(mod, len, opts) .
syscall(__NR_init_module, mod, len, opts)該系統(tǒng)調(diào)用對(duì)應(yīng)內(nèi)核層的sys_init_module函數(shù)
路徑:kernel/module.c
SYSCALL_DEFINE3(init_module,…)
//加載模塊的ko文件,并解釋各個(gè)section,重定位
mod = load_module(umod, len, uargs);
//查找section(".gnu.linkonce.this_module")
modindex = find_sec(hdr, sechdrs, secstrings,".gnu.linkonce.this_module");
//找到Hello_module.mod.c定義的module數(shù)據(jù)結(jié)構(gòu)
mod = (void *)sechdrs[modindex].sh_addr;
if (mod->init != NULL)
ret = do_one_initcall(mod->init); //調(diào)用initfn.
4.3.__attribute__的section用法
__define_initcall使用了gcc的 __attribute__眾多屬性中的section子項(xiàng),其使用方式為:
__attribute__((__section__("section_name")))
其作用是將作用的函數(shù)或數(shù)據(jù)放入指定的名為”section_name”的段。
4.4. C語(yǔ)言宏定義中#和##的用法4.4.1. 一般用法
我們使用#把宏參數(shù)變?yōu)橐粋(gè)字符串。
#define PRINT(FORMAT,VALUE)
printf("The value of"#VALUE"is " FORMAT"",VALUE)
調(diào)用:printf("%d",x+3); --> 打。篢he value of x+3 is 20
這是因?yàn)椤盩he value of”#VALUE”is ” FORMAT””實(shí)際上是包含了”The value of “,#VALUE,”is “,F(xiàn)ORMAT,”” 五部分字符串,其中VALUE和FORMAT被宏參數(shù)的實(shí)際值替換了。
用##把兩個(gè)宏參數(shù)貼合在一起
#define ADD_TO_SUM(sum_number,val) sum##sum_bumber+=(val)
調(diào)用:ADD_TO_SUM(2,100); --> 打印:sum2+=(100)
需要注意的是凡宏定義里有用'#'或'##'的地方宏參數(shù)是不會(huì)再展開。
4.4.2. '#'和'##'的一些應(yīng)用特例合并匿名變量名#define ___ANONYMOUS1(type, var, line) type var##line
#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line)
#define ANONYMOUS(type) __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int); 即 static int _anonymous70; 70表示該行行號(hào);第一層:ANONYMOUS(static int); --> __ANONYMOUS0(static int, LINE);第二層: --> ___ANONYMOUS1(static int, _anonymous, 70);第三層: --> static int _anonymous70;即每次只能解開當(dāng)前層的宏,所以__LINE__在第二層才能被解開;
填充結(jié)構(gòu)#define FILL(a) {a, #a}
enum IDD{OPEN, CLOSE};
typedef struct MSG{
IDD id;
const char msg;
}MSG;
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相當(dāng)于:
MSG _msg[] = {{OPEN, OPEN},
{CLOSE, CLOSE}};
記錄文件名#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);
得到一個(gè)數(shù)值類型所對(duì)應(yīng)的字符串緩沖大。efine _TYPE_BUF_SIZE(type) sizeof #type
#define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type)
char buf[TYPE_BUF_SIZE(INT_M(jìn)AX)];
-- char buf[_TYPE_BUF_SIZE(0x7fffffff)];
-- char buf[sizeof 0x7fffffff];
這里相當(dāng)于:
char buf[11];
- END -

發(fā)表評(píng)論
請(qǐng)輸入評(píng)論內(nèi)容...
請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字
最新活動(dòng)更多
-
3月27日立即報(bào)名>> 【工程師系列】汽車電子技術(shù)在線大會(huì)
-
4月30日立即下載>> 【村田汽車】汽車E/E架構(gòu)革新中,新智能座艙挑戰(zhàn)的解決方案
-
5月15-17日立即預(yù)約>> 【線下巡回】2025年STM32峰會(huì)
-
即日-5.15立即報(bào)名>>> 【在線會(huì)議】安森美Hyperlux™ ID系列引領(lǐng)iToF技術(shù)革新
-
5月15日立即下載>> 【白皮書】精確和高效地表征3000V/20A功率器件應(yīng)用指南
-
5月16日立即參評(píng) >> 【評(píng)選啟動(dòng)】維科杯·OFweek 2025(第十屆)人工智能行業(yè)年度評(píng)選
推薦專題
- 1 UALink規(guī)范發(fā)布:挑戰(zhàn)英偉達(dá)AI統(tǒng)治的開始
- 2 北電數(shù)智主辦酒仙橋論壇,探索AI產(chǎn)業(yè)發(fā)展新路徑
- 3 降薪、加班、裁員三重暴擊,“AI四小龍”已折戟兩家
- 4 “AI寒武紀(jì)”爆發(fā)至今,五類新物種登上歷史舞臺(tái)
- 5 國(guó)產(chǎn)智駕迎戰(zhàn)特斯拉FSD,AI含量差幾何?
- 6 光計(jì)算迎來商業(yè)化突破,但落地仍需時(shí)間
- 7 東陽(yáng)光:2024年扭虧、一季度凈利大增,液冷疊加具身智能打開成長(zhǎng)空間
- 8 地平線自動(dòng)駕駛方案解讀
- 9 封殺AI“照騙”,“淘寶們”終于不忍了?
- 10 優(yōu)必選:營(yíng)收大增主靠小件,虧損繼續(xù)又逢關(guān)稅,能否乘機(jī)器人東風(fēng)翻身?