chore: migrate project into clean repository

This commit is contained in:
yuuux
2026-08-13 16:50:52 +08:00
commit d1d25a09e7
27405 changed files with 9422808 additions and 0 deletions

48
arcs-sdk/.clang-format Normal file
View File

@@ -0,0 +1,48 @@
# 使用 LLVM 作为基础格式化风格
BasedOnStyle: LLVM
# 允许在宏定义之间对齐,即使它们之间有注释。
AlignConsecutiveMacros: AcrossComments
# 不允许短块(如 if、else、for 等语句)在单行中编写,强制每个语句都在新的一行
AllowShortBlocksOnASingleLine: Never
# 不允许短的 case 标签在单行中
AllowShortCaseLabelsOnASingleLine: false
# 不允许短枚举在单行中
AllowShortEnumsOnASingleLine: false
# 不允许短函数在单行中
AllowShortFunctionsOnASingleLine: None
# 不允许短的 if 语句在单行中
AllowShortIfStatementsOnASingleLine: false
# 不允许短的循环语句在单行中
AllowShortLoopsOnASingleLine: false
AttributeMacros:
- __aligned
- __deprecated
- __packed
- __printf_like
- __syscall
- __syscall_always_inline
- __subsystem
# 在位域的冒号后面添加空格
BitFieldColonSpacing: After
# 在 Linux 风格中,打开的括号应该放在行尾
BreakBeforeBraces: Linux
# 每行的最大字符数限制为 120
ColumnLimit: 120
# 构造函数初始化列表的缩进宽度为 4 个空格
ConstructorInitializerIndentWidth: 4
# 续行的缩进宽度为 4 个空格
ContinuationIndentWidth: 4
# case 标签不进行缩进,保持与 switch 语句对齐
IndentCaseLabels: false
# 代码缩进宽度为 4 个空格
IndentWidth: 4
# 在控制语句(如 if、for 等)中,强制使用括号,即使语句只有一行
InsertBraces: true
# 在控制语句的左括号前添加空格,但对于控制宏不添加
SpaceBeforeParens: ControlStatementsExceptControlMacros
# 不对包含的头文件进行排序
SortIncludes: Never
# 不使用制表符,所有缩进都使用空格
UseTab: Never

View File

@@ -0,0 +1,41 @@
根据当前的代码修改生成简洁的中文 git 提交信息。
执行以下步骤:
1. 并行运行 `git status``git diff --cached` 查看已暂存的改动
2. 如果有已暂存的改动staged只针对这些改动生成提交信息
3. 如果没有已暂存的改动,查看所有未暂存改动 (`git diff`),并询问是否 add 所有改动
4. 分析修改内容,识别模块和改动类型
5. 生成提交信息,包含标题和详细说明
6. 询问用户是否创建 commit如果确认则使用 `git commit -s` 添加个人签名
## 提交信息格式
```
<type>(<scope>): <简短标题>
<详细说明2-3行说明
- 具体改动内容
- 改动原因
- 影响范围>
```
## Type 类型
- `feat`: 新功能
- `fix`: 修复问题
- `refactor`: 重构
- `perf`: 性能优化
- `docs`: 文档
- `style`: 格式
- `test`: 测试
- `chore`: 构建工具
## 要求
- **智能识别**:优先使用已 staged 的改动,如果没有则提示用户
- **标题**一行不超过50字符
- **详细说明**2-3行说明具体改动、原因和影响
- **scope**:具体模块名(如 lisa_uart, lisa_gpio
- **提交命令**
- 如果有 staged 改动:使用 `git commit -s`
- 如果没有 staged 改动且用户确认 add先运行 `git add -A`,再 `git commit -s`
- **不要**添加 Claude Code 的 Co-Authored-By 信息

View File

@@ -0,0 +1,93 @@
---
name: changelog
description: 根据 git 提交记录生成或更新 CHANGELOG.md 文件,自动分类提交并按语义化版本管理
---
# CHANGELOG 生成器
自动从 git 提交记录生成符合项目规范的 CHANGELOG.md。
## 执行步骤
1. **读取现有 CHANGELOG.md**
- 解析最新版本号(如 0.1.1
- 识别最新版本的日期
2. **获取 git 提交记录**
- 尝试查找最新的 git tag
- 如果有 tag获取从最新 tag 到 HEAD 的所有提交
- 如果没有 tag根据最新版本日期获取之后的所有提交
- 使用命令:`git log --pretty=format:"%h|%s|%b|%ad" --date=short`
3. **解析和分类提交**
- 从提交信息中提取 `type(scope): description` 格式
- 按 type 分类:
- `feat`**Added** 部分
- `fix`**Fixed** 部分
- `refactor`, `perf`, `style`**Changed** 部分
- `chore`, `docs`, `test` → 根据内容判断或忽略
- 按 scope 分组相同类型的改动
- 保留提交的详细说明body中的关键信息
4. **生成新版本内容**
- 询问用户新版本号(提供自动递增建议,如 0.1.1 → 0.1.2
- 使用当前日期YYYY-MM-DD 格式)
- 生成格式:
```markdown
## [新版本号] - YYYY-MM-DD:
- All changes since 上个版本号
### Changed:
- scope: 变更说明
### Fixed:
- scope: 修复说明
### Added:
- scope: 新增说明
### Deprecated:
```
5. **预览和确认**
- 显示生成的新版本内容
- 询问用户是否插入到 CHANGELOG.md
- 如果确认,将新内容插入到文件顶部(在 `# Change Log` 标题之后)
## 格式要求
- **缩进**:使用 2 空格缩进
- **分组**:相同 scope 的改动合并,使用子列表
- **排序**
- 部分顺序Changed → Fixed → Added → Deprecated
- 每部分内按 scope 字母排序
- **空行**:各部分之间保留空行
## 示例输出
```markdown
## [0.1.2] - 2025-01-14:
- All changes since 0.1.1
### Changed:
- drivers/lisa_uart: 防止传输过程中重新配置
### Fixed:
- drivers/lisa_gpio: 修复中断被重复触发的问题
- drivers/lisa_flash: 修复边界检测
### Added:
- boards: 新增rgb pinmux适配
- components/new_feature: 新增功能组件
```
## 注意事项
- **不要**自动 git commit 生成的 CHANGELOG
- **只处理** CHANGELOG.md 文件,不修改其他文件
- 如果提交信息格式不规范,尽量智能提取关键信息
- 对于合并提交Merge commit可以忽略或提取实际改动
- 保持与现有 CHANGELOG.md 相同的格式风格

View File

@@ -0,0 +1,168 @@
---
name: sample-doc-review
description: 使用 Samples_Spec.md 规范对示例文档进行全面审查,生成详细审查报告
---
# 示例文档审查器
基于 `samples/Samples_Spec.md` 规范对 ARCS SDK 示例文档进行全面审查,检查结构、格式、内容和语言规范性。
## 快速使用
直接在对话中使用以下任一方式触发:
```
审查 samples/modules/sys_heap/README.md
检查 lisa_gpio 示例文档
审查 sys_heap
```
## 执行步骤
### 1. 读取规范文档
**必须先读取** `samples/Samples_Spec.md` 文件,获取完整的审查标准:
- 标准章节结构和顺序
- 各章节的详细规范
- 不同类型示例的差异要求
- 格式规范
- 语言规范
- AI 审查文档清单
### 2. 确定示例文档路径
- 如果用户提供了文档路径,直接使用
- 如果用户只提供了示例名称(如 "sys_heap"),在 `samples/` 目录下搜索对应的 README.md
- 显示找到的文档路径供用户确认
### 3. 读取并分析目标文档
- 读取目标 README.md 文件
- 根据路径自动识别示例类型:
- `samples/drivers/devices/` → Devices
- `samples/modules/` → Modules
- `samples/network/` → Network
- `samples/drivers/hal/` → HAL
### 4. 执行全面检查
严格按照 `Samples_Spec.md` 中的 **"AI 审查文档清单"** 部分逐项检查:
1. **结构检查**10 项)
2. **格式检查**9 项)
3. **内容检查**8 项)
4. **语言检查**6 项)
5. **类型特定检查**(根据示例类型)
### 5. 生成审查报告
使用 `Samples_Spec.md` 中定义的 **"审查报告格式"** 生成报告:
```markdown
# 文档审查报告
**文档路径**: {文件路径}
**示例类型**: {Devices/Modules/Network/HAL}
**审查日期**: {日期}
## 审查结果
**总体评分**: {通过/需修改/不合格}
## 问题清单
### 必需修改(阻塞问题)
{列出所有阻塞问题}
### 建议优化(非阻塞)
{列出所有优化建议}
## 符合规范项
{列出所有通过的检查项}
## 总结
{总体评价和建议}
```
## 评分标准
参考 `Samples_Spec.md` 的规范,但具体标准如下:
- **通过**:无阻塞问题,建议优化 ≤ 3 个
- **需修改**:有 1-3 个阻塞问题
- **不合格**:有 >3 个阻塞问题
### 阻塞问题定义
以下属于阻塞问题(必须修改):
1. 缺少必需章节
2. 标题格式严重错误
3. 章节顺序严重混乱
4. 核心 API 章节缺失Devices/Modules 类型)
5. 代码块未标注语言
6. API 表格格式错误
其他问题归为建议优化。
## 更多触发方式
### 完整路径
```
审查 samples/drivers/devices/lisa_uart/send_async_dma/README.md
帮我审查 http 示例文档是否符合规范
```
### 使用关键词
```
使用规范审查 sys_heap 示例
对 lisa_gpio 文档进行规范检查
验证 http 示例文档的规范性
```
## 使用示例
### 示例 1: 审查指定路径
```
用户:审查 samples/modules/sys_heap/README.md
AI[读取规范] → [读取文档] → [识别类型: Modules] → [执行检查] → [生成报告]
```
### 示例 2: 根据名称查找
```
用户:审查 sys_heap 示例文档
AI找到文档samples/modules/sys_heap/README.md
类型Modules
[开始审查...]
```
### 示例 3: 批量审查
```
用户:审查 samples/drivers/devices/lisa_gpio/ 下的所有文档
AI找到 3 个文档:
- output_basic/README.md
- input_basic/README.md
- interrupt/README.md
[逐个审查...]
```
## 特别注意
1. **规范引用**:所有检查标准以 `Samples_Spec.md` 为准,不要使用过时规范
2. **类型识别**:必须正确识别示例类型,应用对应的特定检查
3. **详细定位**:问题描述要包含章节名称和行号(如果适用)
4. **具体建议**:提供可直接应用的修改示例
5. **RST 引用检查**:编译和烧录章节优先推荐使用 `.. include:: /sample_build.rst``.. include:: /sample_flash.rst`
## 特殊情况处理
- **HAL 示例**:允许使用 `📖示例说明` 替代 `功能说明`,在报告中注明即可
- **特殊编译参数**:如果示例需要特殊编译参数,允许直接写命令而非 RST 引用,但需说明原因
- **文档不存在**:提示用户并建议可能的路径

10
arcs-sdk/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
guardian-out*/
build*/
listenai-tools/
.vscode
listenai-dev-tools
.cache
*.bak
.clangd
.cache/
.env/

146
arcs-sdk/CHANGELOG.md Normal file
View File

@@ -0,0 +1,146 @@
# Change Log
## [0.1.2] - 2026-01-15
- All changes since 0.1.1
### Added:
- algorithms/face_detect: 新增人脸识别算法组件和示例
- boards: 新增rgb pinmux适配
- components/app_player:
- 支持流式播放
- 支持焦点管理
- 添加单实例线程安全
- 支持文件系统音频播放
- components/cAT: 新增AT指令解析器模块及示例basic/demo/unsolicited
- components/coreMQTT: 新增MQTT客户端库及多种示例TCP/SSL/WebSocket/WSS/Agent
- components/quirc: 适配QRCode识别库
- components/libjpeg-turbo: 适配libjpeg-turbo库
- docs/tools: 新增LISA Pinmux Tool使用文档、cskburn烧录工具和Tone音频打包工具文档
- docs/get_started: 新增环境变量必须使用绝对路径的警告说明
- docs: 为示例文档自动添加源码位置链接、新增问题反馈入口
- drivers/lisa_camera: 支持set_reg和get_reg接口
- samples/algorithms/face_detect: 新增人脸识别算法组件示例
- samples/demo/face_detect: 新增人脸识别演示demo
- samples/network: 新增MQTT相关示例TCP/SSL/WebSocket/Agent
- samples/wifi_ble_coex: 新增WiFi蓝牙共存单核示例
- wifi: 支持WiFi快连功能
### Changed:
- algorithms: 算法组件prepare接口支持传入资源地址
- app_player: 调整app_player_play_opt_t配置移除throw_low_energy字段
- drivers/gc0328: RGB565格式默认为小尾端
- lisa_wifi: 在lisa_wifi任务中分发done回调
- modules/lvgl8: 更新lvgl8子模块
- startup/arcs/backtrace: 优先输出backtrace信息避免二次异常
- wifi_manager: 更新WiFi Manager修复断连时未报告reason code、连接时未禁用自动连接等问题
- wifi:
- 更新WiFi库至20251229版本
- 重定向wifi内部ls_read_temp_voltage函数实现
### Fixed:
- components/lisa_audio: 修复音频覆盖及回采帧同步问题
- components/lisa_log: 使用正确的API操作递归互斥锁
- drivers/lisa_camera: 停止和初始化时重置帧缓冲队列
- drivers/lisa_flash: 修复边界检测
- drivers/lisa_gpio: 修复中断被重复触发的问题
- drivers/lisa_pwm: 修复输出频率和设置不一致的问题
- drivers/lisa_uart: 防止传输过程中重新配置
- gc0328: 寄存器设置后添加延时
- system: 修复系统使用异步日志时崩溃信息无法输出的问题
- test: 修复DMA测试用例和efuse测试错误
### Deprecated:
## [0.1.1] - 2025-12-25:
- All changes since 0.1.0
### Changed:
- drivers/lisa_spi: 重构SPI驱动API,简化DMA配置流程
- 移除 `LISA_SPI_AUTO_TRANSFER` 传输模式
- 重命名 `LISA_SPI_PIO_TRANSFER``LISA_SPI_INTERRUPT_TRANSFER`
- 移除 `lisa_spi_configure_dma()` 接口,DMA配置合并到 `lisa_spi_configure()`
- 移除 `lisa_spi_unreserve_dma_channel()` 接口,DMA通道由驱动自动管理
- 移除配置结构体中的 `tx_dma_priority``rx_dma_priority` 字段
- DMA通道限制0~3
- drivers/lisa_rtc: 移除 `lisa_rtc_alarm_t``enabled` 字段,由 `lisa_rtc_enable_alarm` 统一管理
### Fixed:
- drivers/lisa_audio:
- 修复未使能CONFIG_LISA_AUDIO_PLAY_ECHO_ENABLE情况下编译错误
- 修复录音增益调节声道配置错误
- drivers/lisa_camera: 修复缓存不可用时丢帧问题
- drivers/lisa_i2c: 修复时钟非法参数问题
- drivers/lisa_pwm: 修复输出极性设置失败和多次配置问题
- drivers/lisa_adc: 修复偶现读取数据残留问题
- drivers/lisa_uart:
- 修复缓存模式下poll_in接口支持问题
- 修复DMA非法通道配置未报错
- 修复多级缓存模式下接收丢失数据问题
- drivers/lisa_gpio:
- 修复输入模式下写入操作未返回错误码
- 修复无效配置检查
- 修复无法读取debounce状态
- drivers/flash: 更新flash驱动初始化参数
- components/fs:
- 修复fatfs_statvfs中获取文件系统bsize错误
- 修复sqlite3_open_v2支持create并优化sample
- components/wifi: 修复IPC模式下wifi崩溃问题
- samples:部分示例文档整理
### Added:
- drivers/lisa_spi: 新增DMA通道合法性检查
- drivers/lisa_rtc: 增加参数合法性检查
- drivers/lisa_display:
- 新增ST7701S面板驱动
- 支持RGB并行总线和软件SPI命令总线
- 新增背光极性配置
- drivers/lisa_rgb: 新增lisa_rgb设备驱动
- drivers/lisa_camera: 支持可配置DVP频率和帧格式
- drivers/lisa_audio: 支持mic偏置电压配置选择
- components/app_player: 新增app_player组件,支持本地和网络音频播放、本地提示音播放
- components/work_queue: 新增work queue组件
- components/wifi: 更新WiFi库至20251211版本
- components/bluetooth: 20251211 BT更新
- samples/app_player: 新增本地音频和网络音频播放示例及单元测试
- samples/algorithms: 新增单麦唤醒算法示例
- samples/usb_camera: 新增USB摄像头示例及文档
- samples/rgb_bounce_buffer: 新增RGBBounce Buffer示例
- samples/modules: 新增cjson/collections-c/flexlayout/freetype/giflib/jpeg/mbedtls/mbedtls示例文档说明
- tools: 提供tone打包工具
### Deprecated:
## [0.1.0] - 2025-12-09:
- All changes since 0.0.22
### Changed:
- 调整SDK目录结构移除arcs-base
- cmake调整构建脚本build.sh构建命令需指定板型
- components: 移除display/touch/camera/flash/lisa_evs组件
### Fixed:
- components/lisa_websocket: 重构websocket组件解决内部依赖问题
- samples/network: 修复网络相关示例
### Added:
- drivers: 新增设备驱动UART/SPI/I2C/GPADC/PWM/GPIO/RTC/FLASH/SDMMC/WDT/HWTIMER/DISPLAY/TOUCH/CAMERA/QSPI_LCD/AUDIO/DVP
- samples/drivers: 新增设备驱动示例
- samples/bluetooth: 新增蓝牙广播和GATT服务示例
- samples/algorithms: 新增唤醒算法示例
- components/acomp: 新增唤醒算法组件
- components/lisa_evt_pub: 新增事件发布组件
- components/lisa_shell: 新增shell组件
- components/lisa_wifi: 新增wifi组件
- components/lisa_bluetooth: 新增蓝牙组件
- components/lisa_sntp: 新增sntp组件
- boards: 新增板型支持内置evb/mini板型
- docs: 首次部署在线文档并完善部分组件和示例文档
### Deprecated:
- samples/drivers: hal驱动示例不做维护建议使用新的设备驱动

6
arcs-sdk/CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
add_subdirectory(soc)
add_subdirectory(startup)
add_subdirectory(components)
add_subdirectory(drivers)
add_subdirectory(boards)
add_subdirectory(modules)

12
arcs-sdk/Kconfig Normal file
View File

@@ -0,0 +1,12 @@
menu "Arcs SDK"
rsource "startup/Kconfig"
rsource "soc/Kconfig"
rsource "components/Kconfig"
rsource "drivers/Kconfig"
rsource "boards/Kconfig"
rsource "modules/Kconfig"
rsource "cmake/Kconfig"
rsource "boards/Kconfig"
endmenu

201
arcs-sdk/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Support. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

44
arcs-sdk/README.md Normal file
View File

@@ -0,0 +1,44 @@
# ARCS SDK
## 简介
ARCS SDK 是一个专为嵌入式系统设计的轻量级软件开发工具包,提供了完整的驱动、组件库和 RTOS 支持。
### 主要特性
- **丰富的驱动支持**:提供 UART、GPIO、I2C、SPI、ADC/DAC、PWM、DMA、Flash、Display、Camera、Touch 等 20+ 外设驱动
- **RTOS 集成**:深度集成 FreeRTOS提供统一的 RTOS 抽象层
- **图形界面**:内置 LVGL GUI 框架v7/v8支持多种 LCD/EPD 显示设备
- **网络功能**:支持 HTTP/HTTPS、WebSocket、MQTT 等网络协议
- **多媒体能力**:支持 MP3、AAC 音频解码JPEG、PNG、GIF 图像处理
- **USB 支持**:集成 TinyUSB 协议栈
- **算法服务**:提供 OCR、TTS、翻译、拼读、语音唤醒等算法与服务能力接口。
- **开发工具**Shell、日志、单元测试、死机回溯等
## 目录结构
```
arcs-sdk/
├── boards/ ← 板级文件
├── soc/ ← soc相关文件
├── startup/ ← 系统启动相关初始化
├── drivers/ ← 驱动文件
├── cmake/ ← CMake构建扩展
├── components/ ← 自研软件库
├── modules/ ← 第三方开源库
├── tools/ ← 开发工具集
├── samples/ ← 示例代码
├── tests/ ← 测试代码
├── docs/ ← 文档构建
├── README.md ← 仓库文档
├── VERSION ← 版本信息
├── LICENSE ← 开源许可证
```
---
## 📚 资源链接
| 资源类型 | 链接 |
|---------|------|
| **在线文档** | https://docs2.listenai.com/arcs-sdk/latest/zh/html/index.html |
| **引脚配置工具** | https://tool.listenai.com/ls-pinmux-tool |

View File

@@ -0,0 +1,589 @@
# ARCS SDK 版本管理说明
## 文档目的
本文档说明了 ARCS SDK 的版本管理机制和使用方法。
---
## 目录
1. [概述](#概述)
2. [版本号组成](#版本号组成)
3. [文件结构](#文件结构)
4. [数据流转](#数据流转)
5. [版本变量参考](#版本变量参考)
6. [构建集成](#构建集成)
7. [版本检查机制](#版本检查机制)
8. [使用示例](#使用示例)
---
## 概述
### 设计理念
- **单一数据源**: 所有版本号定义在单个纯文本文件 (`VERSION`) 中
- **多种表示形式**: 版本号以多种格式编码(字符串、整数、十六进制)
- **编译时生成**: 在 CMake 配置阶段生成版本头文件
- **Git 集成**: 可选的 git 提交哈希作为构建版本标识
### 主要特性
- 语义化版本控制,支持可选的 TWEAK 组件
- 额外版本后缀支持(如 `-rc1``-beta`
- 为嵌入式系统提供十六进制版本编码
- 人类可读和机器可比较的格式
- 在 boot_banner 中自动显示版本信息
---
## 版本号组成
### 四级版本方案
```
MAJOR.MINOR.PATCH.TWEAK[-EXTRA]
1 . 10 . 0 . 0 [-rc1]
│ │ │ │ └─ 可选后缀alpha、beta、rc1 等)
│ │ │ └──────── 构建/微调版本号(通常为 0
│ │ └────────────── Bug 修复、补丁
│ └──────────────────── 新功能(向后兼容)
└────────────────────────── 破坏性变更
```
### 组件说明
| 组件 | 变量名 | 用途 | 何时递增 |
|-----------|---------------|---------|-------------------|
| MAJOR | `VERSION_MAJOR` | 破坏性 API 变更 | 引入 API 不兼容时 |
| MINOR | `VERSION_MINOR` | 新功能 | 添加向后兼容的新功能时 |
| PATCH | `PATCHLEVEL` | Bug 修复 | 仅修复 Bug 时 |
| TWEAK | `VERSION_TWEAK` | 构建/修订号 | 非常小的变更,通常为 0 |
| EXTRA | `EXTRAVERSION` | 预发布标签 | 预发布版本rc1、beta 等) |
### 版本字符串格式
不同场景需要不同的版本表示形式:
| 格式 | 示例 | 用途 |
|--------|---------|-------|
| 标准3 段) | `1.10.0` | 正式发布版本TWEAK=0 |
| 完整4 段) | `1.10.0.1` | 开发构建版本TWEAK>0 |
| 带后缀 | `1.10.0-rc1` | 预发布版本 |
| 十六进制 | `0x010A00` | C 代码中的版本比较 |
| 整数 | `68096` | 数值比较 |
### 开发构建版本 vs 正式发布版本
通过 `VERSION_TWEAK``EXTRAVERSION` 区分不同阶段的版本:
| 版本类型 | TWEAK | EXTRAVERSION | 显示格式 | 使用场景 |
|---------|-------|--------------|----------|---------|
| 正式发布 | 0 | 0 | `1.10.0` | 对外发布的稳定版本 |
| 开发构建 | 1, 2, 3... | 0 | `1.10.0.1` | 日常开发、内部测试 |
| Alpha内测 | 0 | alpha | `1.10.0-alpha` | 早期内部测试 |
| Beta公测 | 0 | beta | `1.10.0-beta` | 公开测试版本 |
| RC候选 | 0 | rc1, rc2... | `1.10.0-rc1` | 发布前的候选版本 |
**典型版本演进流程**:
```
1.10.0.1 → 1.10.0.2 → ... → 1.10.0-alpha → 1.10.0-beta → 1.10.0-rc1 → 1.10.0
```
**版本显示规则**:
1. 如果设置了 `EXTRAVERSION`非0显示 `MAJOR.MINOR.PATCH-EXTRA`(忽略 TWEAK
- 示例: `TWEAK=1, EXTRAVERSION=rc1` → 显示 `1.10.0-rc1`
2. 如果设置了 `TWEAK`非0显示 `MAJOR.MINOR.PATCH.TWEAK`
- 示例: `TWEAK=1, EXTRAVERSION=0` → 显示 `1.10.0.1`
3. 正式版本TWEAK=0, EXTRAVERSION=0显示 `MAJOR.MINOR.PATCH`
- 示例: `TWEAK=0, EXTRAVERSION=0` → 显示 `1.10.0`
**注意**: `SDK_VERSION_NUMBER` 不包含 `TWEAK`,因此 `1.10.0``1.10.0.1` 的版本比较值相同。如需区分开发构建,使用 `SDKVERSION`包含32位完整编码
---
## 文件结构
### 核心文件
```
arcs-sdk/
├── VERSION # [源文件] 版本定义文件
├── sdk_version.h.in # [模板] C 头文件模板
├── cmake/
│ ├── version.cmake # [解析器] 解析 VERSION 并设置变量
│ ├── gen_version_h.cmake # [生成器] 生成 C 头文件
│ ├── hex.cmake # [工具] 十六进制转换函数
│ └── listenai-cmake-config.cmake # [入口] 包含 version.cmake
└── startup/arcs/sysmain.c # [使用] boot_banner 打印版本
```
### 文件关系图
```mermaid
graph TD
A[VERSION] -->|读取| B[version.cmake]
B -->|设置变量| C[CMake 变量]
C -->|传递给| D[gen_version_h.cmake]
E[sdk_version.h.in] -->|模板| D
D -->|生成| F[sdk_version.h]
G[Git] -->|可选| D
B -->|使用| H[hex.cmake]
F -->|包含| I[sysmain.c]
I -->|显示| J[boot_banner]
```
---
## 数据流转
### 第 1 步: 版本定义VERSION 文件)
**文件**: `VERSION`
**位置**: SDK 根目录
**格式**: 键值对
```makefile
VERSION_MAJOR = 1
VERSION_MINOR = 10
PATCHLEVEL = 0
VERSION_TWEAK = 0
EXTRAVERSION = 0
```
**规则**:
- 纯文本文件(非 YAML/JSON
- 每行一个定义
- 仅数值EXTRAVERSION 可以是字母数字)
- EXTRAVERSION: 正式版本使用 `0`,预发布版本使用 `rc1`/`beta`/`alpha`
### 第 2 步: 版本解析version.cmake
**文件**: `cmake/version.cmake`
**被包含于**: `cmake/listenai-cmake-config.cmake:58`
**处理流程**:
```cmake
# 1. 读取 VERSION 文件
file(READ ${ARCS_SDK_BASE}/VERSION ver)
# 2. 使用正则表达式解析每个组件
string(REGEX MATCH "VERSION_MAJOR = ([0-9]*)" _ ${ver})
set(PROJECT_VERSION_MAJOR ${CMAKE_MATCH_1})
string(REGEX MATCH "VERSION_MINOR = ([0-9]*)" _ ${ver})
set(PROJECT_VERSION_MINOR ${CMAKE_MATCH_1})
string(REGEX MATCH "PATCHLEVEL = ([0-9]*)" _ ${ver})
set(PROJECT_VERSION_PATCH ${CMAKE_MATCH_1})
string(REGEX MATCH "VERSION_TWEAK = ([0-9]*)" _ ${ver})
set(PROJECT_VERSION_TWEAK ${CMAKE_MATCH_1})
string(REGEX MATCH "EXTRAVERSION = ([a-z0-9]*)" _ ${ver})
set(PROJECT_VERSION_EXTRA ${CMAKE_MATCH_1})
# 3. 构建组合版本
set(PROJECT_VERSION_WITHOUT_TWEAK ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
# 4. 处理 TWEAK可选的第 4 段)
if(PROJECT_VERSION_TWEAK AND NOT PROJECT_VERSION_TWEAK EQUAL 0)
set(PROJECT_VERSION ${PROJECT_VERSION_WITHOUT_TWEAK}.${PROJECT_VERSION_TWEAK})
else()
set(PROJECT_VERSION ${PROJECT_VERSION_WITHOUT_TWEAK})
endif()
# 5. 构建版本字符串(用于显示)
if(PROJECT_VERSION_EXTRA AND NOT PROJECT_VERSION_EXTRA STREQUAL "0")
set(SDK_VERSION_STRING "\"${PROJECT_VERSION_WITHOUT_TWEAK}-${PROJECT_VERSION_EXTRA}\"")
else()
set(SDK_VERSION_STRING "\"${PROJECT_VERSION_WITHOUT_TWEAK}\"")
endif()
```
**重要提示**: `SDK_VERSION_STRING` 不包含 TWEAK仅包含 EXTRA 后缀。
### 第 3 步: 数值编码
**目的**: 使 C 代码中能进行版本比较
```cmake
# 转换为整数24 位编码: MAJOR.MINOR.PATCH
# 格式: 0xMMNNPP (MM=主版本, NN=次版本, PP=补丁)
math(EXPR SDK_VERSION_NUMBER_INT "(${MAJOR} << 16) + (${MINOR} << 8) + (${PATCH})")
# 转换为十六进制字符串
to_hex(${SDK_VERSION_NUMBER_INT} SDK_VERSION_NUMBER)
# 结果: SDK_VERSION_NUMBER = "0x010A00" (版本 1.10.0)
# 包含 TWEAK 的完整版本32 位编码)
# 格式: 0xMMNNPPTT (MM=主版本, NN=次版本, PP=补丁, TT=微调)
math(EXPR SDKVERSION_INT "(${MAJOR} << 24) + (${MINOR} << 16) + (${PATCH} << 8) + (${TWEAK})")
to_hex(${SDKVERSION_INT} SDKVERSION)
# 结果: SDKVERSION = "0x010A0000" (版本 1.10.0.0)
```
**编码对比**:
| 版本 | SDK_VERSION_NUMBER | SDKVERSION |
|---------|-------------------|------------|
| 1.10.0.0 | 0x010A00 (68096) | 0x010A0000 (17432576) |
| 1.10.1.0 | 0x010A01 (68097) | 0x010A0100 (17432832) |
| 2.0.0.0 | 0x020000 (131072) | 0x02000000 (33554432) |
### 第 4 步: Git 集成gen_version_h.cmake
**文件**: `cmake/gen_version_h.cmake`
**执行于**: `cmake/listenai-cmake-config.cmake:132-145`
```cmake
# 尝试获取 git 提交哈希
find_package(Git QUIET)
if(GIT_FOUND AND EXISTS ${ARCS_SDK_BASE}/.git)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --abbrev=12 --always
WORKING_DIRECTORY ${ARCS_SDK_BASE}
OUTPUT_VARIABLE BUILD_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
# 从模板生成头文件
configure_file(${ARCS_SDK_BASE}/sdk_version.h.in ${OUT_FILE})
```
**Git describe 格式**:
- 有标签时: `v1.10.0-5-g8d2af741d`
- `v1.10.0`: 最近的标签
- `5`: 自标签以来的提交数
- `g8d2af741d`: 提交哈希12 个字符)
- 无标签时: `8d2af741d`(仅哈希)
### 第 5 步: 头文件生成arcs_sdk_version.h.in
**模板文件**: `sdk_version.h.in`
**生成文件**: `build/generated/include/sdk_version.h`
**模板内容**:
```c
#ifndef ARCS_SDK_VERSION_H
#define ARCS_SDK_VERSION_H
/* 版本号组件 */
#define SDK_VERSION_MAJOR @SDK_VERSION_MAJOR@
#define SDK_VERSION_MINOR @SDK_VERSION_MINOR@
#define SDK_PATCHLEVEL @SDK_PATCHLEVEL@
/* 版本字符串(用于显示) */
#define SDK_VERSION_STRING @SDK_VERSION_STRING@
/* 数值版本编码(用于版本比较) */
#define SDK_VERSION_CODE @SDK_VERSION_CODE@
#define SDK_VERSION_NUMBER @SDK_VERSION_NUMBER@
#define SDKVERSION @SDKVERSION@
/* 构建版本信息Git 提交哈希) */
#define BUILD_VERSION "@BUILD_VERSION@"
/* 版本比较宏 */
#define SDK_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
#endif /* ARCS_SDK_VERSION_H */
```
**生成的输出**(版本 1.10.0:
```c
#ifndef ARCS_SDK_VERSION_H
#define ARCS_SDK_VERSION_H
#define SDK_VERSION_MAJOR 1
#define SDK_VERSION_MINOR 10
#define SDK_PATCHLEVEL 0
#define SDK_VERSION_STRING "1.10.0"
#define SDK_VERSION_CODE 68096
#define SDK_VERSION_NUMBER 0x010A00
#define SDKVERSION 0x010A0000
#define BUILD_VERSION "8d2af741d"
#define SDK_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
#endif /* ARCS_SDK_VERSION_H */
```
### 第 6 步: 构建执行listenai-cmake-config.cmake
**文件**: `cmake/listenai-cmake-config.cmake:132-145`
```cmake
# 生成 SDK 版本文件
execute_process(
COMMAND ${CMAKE_COMMAND}
-DARCS_SDK_BASE=${ARCS_SDK_BASE}
-DOUT_FILE=${CMAKE_BINARY_DIR}/generated/include/sdk_version.h
-DSDK_VERSION_MAJOR=${SDK_VERSION_MAJOR}
-DSDK_VERSION_MINOR=${SDK_VERSION_MINOR}
-DSDK_PATCHLEVEL=${SDK_PATCHLEVEL}
-DSDK_VERSION_STRING=${SDK_VERSION_STRING}
-DSDK_VERSION_CODE=${SDK_VERSION_CODE}
-DSDK_VERSION_NUMBER=${SDK_VERSION_NUMBER}
-DSDKVERSION=${SDKVERSION}
-P ${ARCS_SDK_BASE}/cmake/gen_version_h.cmake
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
)
```
---
## 版本变量参考
### 完整变量列表
| 变量名 | 来源 | 类型 | 示例 | 描述 |
|--------------|--------|------|---------|-------------|
| `VERSION_MAJOR` | VERSION 文件 | 整数 | `1` | 主版本号 |
| `VERSION_MINOR` | VERSION 文件 | 整数 | `10` | 次版本号 |
| `PATCHLEVEL` | VERSION 文件 | 整数 | `0` | 补丁级别 |
| `VERSION_TWEAK` | VERSION 文件 | 整数 | `0` | 微调/构建号 |
| `EXTRAVERSION` | VERSION 文件 | 字符串 | `0``rc1` | 额外版本后缀 |
| `PROJECT_VERSION_MAJOR` | 解析后 | 整数 | `1` | 同 VERSION_MAJOR |
| `PROJECT_VERSION_MINOR` | 解析后 | 整数 | `10` | 同 VERSION_MINOR |
| `PROJECT_VERSION_PATCH` | 解析后 | 整数 | `0` | 同 PATCHLEVEL |
| `PROJECT_VERSION_TWEAK` | 解析后 | 整数 | `0` | 同 VERSION_TWEAK |
| `PROJECT_VERSION_EXTRA` | 解析后 | 字符串 | `0``rc1` | 同 EXTRAVERSION |
| `PROJECT_VERSION_WITHOUT_TWEAK` | 计算得出 | 字符串 | `1.10.0` | MAJOR.MINOR.PATCH |
| `PROJECT_VERSION` | 计算得出 | 字符串 | `1.10.0``1.10.0.1` | TWEAK 非零时包含 |
| `PROJECT_VERSION_STR` | 计算得出 | 字符串 | `1.10.0``1.10.0-rc1` | EXTRA 存在时包含 |
| `SDK_VERSION_MAJOR` | 导出 | 整数 | `1` | 用于 C 头文件 |
| `SDK_VERSION_MINOR` | 导出 | 整数 | `10` | 用于 C 头文件 |
| `SDK_PATCHLEVEL` | 导出 | 整数 | `0` | 用于 C 头文件 |
| `SDK_VERSION_STRING` | 导出 | 带引号字符串 | `"1.10.0"` | 用于 C 头文件(带引号) |
| `SDK_VERSION_NUMBER_INT` | 计算得出 | 整数 | `68096` | 十进制: (1<<16)+(10<<8)+0 |
| `SDK_VERSION_NUMBER` | 计算得出 | 十六进制字符串 | `0x010A00` | 十六进制编码24 位) |
| `SDKVERSION_INT` | 计算得出 | 整数 | `17432576` | 包含 TWEAK 的十进制 |
| `SDKVERSION` | 计算得出 | 十六进制字符串 | `0x010A0000` | 十六进制编码32 位) |
| `BUILD_VERSION` | Git | 字符串 | `8d2af741d` | Git 提交哈希 |
| `SDK_VERSION_CODE` | 别名 | 整数 | `68096` | 同 SDK_VERSION_NUMBER_INT |
### 变量使用场景
| 场景 | 使用的变量 |
|---------|---------------|
| CMake 构建消息 | `PROJECT_VERSION_STR` |
| C/C++ 版本检查 | `SDK_VERSION_NUMBER`, `SDKVERSION` |
| 显示给用户 | `SDK_VERSION_STRING`, `BUILD_VERSION` |
| 版本比较 | `SDK_VERSION_MAJOR/MINOR/PATCHLEVEL` |
| 宏计算 | `SDK_VERSION(a,b,c)` 宏 |
---
## 构建集成
### CMake 包含链
```
listenai-cmake-config.cmake (主配置)
├─> cmake/hex.cmake (十六进制工具)
├─> cmake/version.cmake (版本解析)
└─> cmake/gen_version_h.cmake (头文件生成)
```
### 构建时执行顺序
1. **配置阶段**:
- 读取 `VERSION` 文件
- 解析版本组件
- 设置所有版本变量
- 计算十六进制编码
2. **版本头文件生成**:
- 获取 Git 哈希(如果在 Git 仓库中)
- 生成 `sdk_version.h`
3. **编译阶段**:
- C/C++ 文件包含 `sdk_version.h`
- 编译时可使用版本宏
### 控制台输出示例
构建时会看到:
```
-- arcs_sdk: 1.10.0 (/path/to/arcs-sdk)
```
这来自 `cmake/version.cmake:65`:
```cmake
if(NOT NO_PRINT_VERSION)
message(STATUS "arcs_sdk: ${PROJECT_VERSION_STR} (${ARCS_SDK_BASE})")
endif()
```
---
## 版本检查机制
### C 代码版本检查
应用程序可以在编译时检查版本:
```c
#include "sdk_version.h"
#if SDK_VERSION_NUMBER < SDK_VERSION(1, 9, 0)
#error "此代码需要 SDK 1.9.0 或更高版本"
#endif
// 运行时检查
if (SDK_VERSION_NUMBER < SDK_VERSION(1, 10, 0)) {
printf("警告: SDK 版本 %s 低于推荐的 1.10.0\n",
SDK_VERSION_STRING);
}
```
---
## 使用示例
### 启动横幅boot_banner
**文件**: `startup/arcs/sysmain.c`
```c
#include "sdk_version.h"
#if CONFIG_SYSLOG_BANNER
__attribute__((weak)) void boot_banner(void)
{
printf("\n********Arcs SDK %s @ %s********\n",
SDK_VERSION_STRING, BUILD_VERSION);
printf("Running on hart-id: %ld\n", (unsigned long)__get_hart_id());
}
#endif
```
**输出示例**:
```
********Arcs SDK "1.10.0" @ 8d2af741d********
Running on hart-id: 0
```
### 版本号比较
```c
#include "sdk_version.h"
void check_sdk_version(void) {
// 方法 1: 使用宏比较
#if SDK_VERSION_NUMBER >= SDK_VERSION(1, 10, 0)
printf("SDK 版本满足要求\n");
#else
#error "需要 SDK 1.10.0 或更高版本"
#endif
// 方法 2: 运行时比较
if (SDK_VERSION_CODE >= SDK_VERSION(1, 10, 0)) {
printf("当前 SDK 版本: %s\n", SDK_VERSION_STRING);
printf("构建版本: %s\n", BUILD_VERSION);
}
}
```
---
## 修改版本号
### 发布新版本
1. 编辑 `VERSION` 文件:
```
VERSION_MAJOR = 1
VERSION_MINOR = 11
PATCHLEVEL = 0
VERSION_TWEAK = 0
EXTRAVERSION = 0
```
2. 重新配置构建:
```bash
cd build
cmake ..
```
3. 验证版本:
```bash
# 查看生成的头文件
cat build/generated/include/sdk_version.h
```
### 预发布版本
对于 rc1 版本:
```
VERSION_MAJOR = 1
VERSION_MINOR = 11
PATCHLEVEL = 0
VERSION_TWEAK = 0
EXTRAVERSION = rc1
```
生成的版本字符串: `"1.11.0-rc1"`
---
## 故障排查
### 常见问题
| 问题 | 原因 | 解决方案 |
|---------|-------|----------|
| 版本显示为 0.0.0 | VERSION 文件未找到或格式错误 | 检查 VERSION 文件路径和格式 |
| 十六进制版本为 0x0 | hex.cmake 未包含 | 验证在使用前 `include(hex.cmake)` |
| Git 哈希为空 | 不是 git 仓库或未安装 git | 正常行为BUILD_VERSION 将为 "unknown" |
| 头文件未重新生成 | CMake 缓存问题 | 删除构建目录并重新配置 |
### 调试版本变量
在 `version.cmake` 中添加以下内容用于调试:
```cmake
message(STATUS "--- 版本调试信息 ---")
message(STATUS "PROJECT_VERSION: ${PROJECT_VERSION}")
message(STATUS "SDK_VERSION_STRING: ${SDK_VERSION_STRING}")
message(STATUS "SDK_VERSION_NUMBER: ${SDK_VERSION_NUMBER}")
message(STATUS "SDKVERSION: ${SDKVERSION}")
message(STATUS "BUILD_VERSION: ${BUILD_VERSION}")
message(STATUS "-------------------------")
```
---
## 文件快速参考
| 文件 | 目的 | 何时修改 |
|------|---------|---------------|
| `VERSION` | 定义版本号 | 每次发布 |
| `cmake/version.cmake` | 解析和设置变量 | 添加新版本格式时 |
| `cmake/hex.cmake` | 十六进制转换工具 | 很少(稳定工具) |
| `cmake/gen_version_h.cmake` | 生成 C 头文件 | 更改 git 集成时 |
| `sdk_version.h.in` | C 头文件模板 | 添加新 C 宏时 |
| `cmake/listenai-cmake-config.cmake` | 集成版本管理 | 更改构建流程时 |
| `startup/arcs/sysmain.c` | 显示启动横幅 | 更改显示格式时 |
---
## 附录
### 相关文件
- 版本定义: [VERSION](VERSION)
- 版本解析: [cmake/version.cmake](cmake/version.cmake)
- 头文件模板: [sdk_version.h.in](sdk_version.h.in)
- 头文件生成: [cmake/gen_version_h.cmake](cmake/gen_version_h.cmake)
### 文档信息
- **文档版本**: 1.0
- **SDK 版本**: ARCS SDK v1.10.0
- **最后更新**: 2025-12-01

5
arcs-sdk/VERSION Normal file
View File

@@ -0,0 +1,5 @@
VERSION_MAJOR = 0
VERSION_MINOR = 1
PATCHLEVEL = 2
VERSION_TWEAK = 0
EXTRAVERSION = 0

15
arcs-sdk/auto-sync-build.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
# 此脚本用于修改跟目录的build.sh后
# 自动将build.sh复制到各个项目目录
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
directories=()
while IFS= read -r cmake_file; do
if grep -q "find_package(listenai-cmake" "$cmake_file"; then
project_dir=$(dirname "$cmake_file")
directories+=("$project_dir")
fi
done < <(find "${SCRIPT_DIR}" -type f -name "CMakeLists.txt")
for dir in "${directories[@]}"; do
cp $SCRIPT_DIR/build.sh $dir
done

View File

@@ -0,0 +1,248 @@
# 自定义板型开发指南
本文档提供创建自定义板型的完整指南,包括文件结构、接口规范、开发步骤和文件模板。
## 概述
创建自定义板型需要准备以下文件:
```
my_board/
├── CMakeLists.txt # 板型构建脚本(必需)
├── board.h # 板级接口声明(必需)
├── board.c # 板级初始化实现(必需)
├── pinmux.h # 引脚配置声明(必需)
├── pinmux.c # 引脚配置实现(必需)
└── Kconfig # 板型配置选项(必需)
```
## 开发步骤
### Step 1: 创建板型目录
```bash
# 创建板型目录
mkdir -p /path/to/my_boards/my_board
cd /path/to/my_boards/my_board
```
### Step 2: 创建 CMakeLists.txt
复制以下内容到 `CMakeLists.txt`
```cmake
# ARCS BOARD SUPPORT
#
# Copyright (c) 2025, LISTENAI
# SPDX-License-Identifier: Apache-2.0
# 创建板型库
listenai_library_named(module_boards)
# 添加源文件
listenai_library_sources(
pinmux.c
board.c
)
# 添加头文件路径
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
```
### Step 3: 创建 board.h 和 board.c
**方法一:复制 SDK 内置板型作为起点**
```bash
cp $ARCS_SDK_BASE/boards/arcs_evb/board.h .
cp $ARCS_SDK_BASE/boards/arcs_evb/board.c .
```
然后修改文件中的注释和 `board_get_name()` 返回值为您的板型名称。
**方法二:使用下方的文件模板手动创建**
参考本文档后面的"文件模板"章节。
### Step 4: 使用 Pinmux 工具生成引脚配置
**重要pinmux.h 和 pinmux.c 必须使用工具生成,不应手动编辑。**
#### 工具信息
- **工具地址**https://tool.listenai.com/ls-pinmux-tool/
- **功能**:根据芯片型号和外设配置,自动生成引脚复用代码
#### 使用步骤
1. 访问 Pinmux 配置工具
2. 选择目标芯片型号
3. 配置项目中使用的外设(如 UART0、SPI0、I2C0 等)
4. 为每个外设配置引脚映射
5. 生成代码
6. 将生成的 `pinmux.h``pinmux.c` 保存到板型目录
#### 设备驱动集成
生成的 pinmux 函数会被对应的设备驱动自动调用:
- `lisa_uart0_pinmux()` - 由 lisa_uart 驱动的 uart0 设备初始化时调用
- `lisa_spi0_pinmux()` - 由 lisa_spi 驱动的 spi0 设备初始化时调用
- 其他外设依此类推
**注意事项:**
- 不要手动编辑生成的文件
- 如需修改引脚配置,应在工具中修改后重新生成
### Step 5: 创建 Kconfig 文件(必需)
创建 `Kconfig` 文件,必须包含以下两个配置项:
```bash
# Board Configuration
#
# Copyright (c) 2025, LISTENAI
# SPDX-License-Identifier: Apache-2.0
config BOARD_NAME
string
default "my_board"
prompt "Board Name"
help
Board Name
config BOARD_MY_BOARD
bool
default y
help
Board is my_board
```
**重要提示:**
1.`my_board` 替换为您的实际板型名称
2. 第二个配置项名称格式为 `BOARD_<板型名称大写>`
3. 例如板型名为 `arcs_evb`,则配置项为 `BOARD_arcs_evb`
**Kconfig 说明:**
- **必需配置项 1**`BOARD_NAME` - 板型名称string 类型)
- **必需配置项 2**`BOARD_<BOARD_NAME_UPPER>` - 板型标识bool 类型,默认 y
- 构建系统会自动加载此文件并设置环境变量 `BOARD_KCONFIG_PATH`
- 外部板型和 SDK 内置板型享有相同的配置能力
### Step 6: 编译验证
```bash
cd $ARCS_SDK_BASE
./build.sh -S samples/helloworld \
-DBOARD=my_board \
-DBOARD_SEARCH_PATH=/path/to/my_boards
```
**重要提示:** 外部板型文件已通过编译变量指定构建路径,无需在工程中额外添加构建。
### Step 7: 检查构建日志
确认看到板型加载信息:
```
-- Target board: my_board
-- Board found (custom): my_board from /path/to/my_boards
-- Board loaded successfully: /path/to/my_boards/my_board
```
## 接口规范
### board.h / board.c - 标准接口
板型必须实现以下标准接口:
- `const char* board_get_name(void)` - 返回板型名称字符串
### pinmux.h / pinmux.c - 引脚复用配置
**重要说明:这两个文件由 Pinmux 配置工具生成,不应手动编辑。**
详见 Step 4 的说明。
## 文件模板
### board.h 模板
可参考boards/arcs_evb/board.h
```c
// ARCS BOARD SUPPORT
//
// Copyright (c) 2025, LISTENAI
// SPDX-License-Identifier: Apache-2.0
#ifndef _BOARD_MY_BOARD_H_
#define _BOARD_MY_BOARD_H_
/**
* @brief Get the board name
*
* @return const char* Board name string
*/
const char* board_get_name(void);
#endif // _BOARD_MY_BOARD_H_
```
### board.c 模板
可参考boards/arcs_evb/board.c
```c
// ARCS BOARD SUPPORT
//
// Copyright (c) 2025, LISTENAI
// SPDX-License-Identifier: Apache-2.0
#include "board.h"
const char* board_get_name(void)
{
return "my_board";
}
```
### Kconfig 模板
可参考boards/arcs_evb/Kconfig
```kconfig
# Board Configuration
#
# Copyright (c) 2025, LISTENAI
# SPDX-License-Identifier: Apache-2.0
config BOARD_NAME
string
default "my_board"
prompt "Board Name"
help
Board Name
config BOARD_MY_BOARD
bool
default y
help
Board is my_board
```
**注意:** 请将模板中的 `my_board``MY_BOARD` 替换为您的实际板型名称。
## 常见问题
### Q: 是否可以不创建 Kconfig 文件?
不可以。Kconfig 文件是必需的,必须包含 `BOARD_NAME``BOARD_<名称大写>` 两个配置项。
### Q: 可以手动编辑 pinmux.c 吗?
不建议。pinmux.h 和 pinmux.c 应该由 Pinmux 配置工具生成。如果需要修改引脚配置,应在工具中重新配置并生成新文件。
### Q: board.c 中还需要实现其他函数吗?
当前只需要实现 `board_get_name()` 函数。未来如果有更多板级初始化需求,可能会增加其他接口。

View File

@@ -0,0 +1,198 @@
# 板型使用指南
本文档说明如何在项目中使用板型支持系统。
## 板型支持系统简介
板型支持系统负责管理不同硬件板卡的配置和初始化。系统采用模块化设计将引脚复用pinmux配置与板级初始化逻辑分离便于维护和扩展。
### 主要特性
- **模块化设计**板级代码board.c/h与引脚配置pinmux.c/h分离
- **外部板型支持**:支持在 SDK 外部添加自定义板型,无需修改 SDK 源码
- **两级搜索机制**:优先搜索自定义路径,然后搜索 SDK 内置板型
- **简单易用**:通过 `build.sh` 命令行参数即可切换板型
- **配置管理**:通过 Kconfig 管理板型配置选项
## 快速开始
### 使用 SDK 内置板型
必须使用 `-DBOARD` 参数指定板型:
```bash
cd arcs-sdk
# 使用 arcs_mini 板型
./build.sh -S samples/helloworld -DBOARD=arcs_mini
# 使用 arcs_evb 板型
./build.sh -S samples/helloworld -DBOARD=arcs_evb
```
### 查看可用的 SDK 内置板型
```bash
ls boards/
```
查看各板型的详细说明,请参考对应板型目录下的 README.md 文件。
## 使用外部自定义板型
### 基本用法
使用 `-DBOARD``-DBOARD_SEARCH_PATH` 参数:
```bash
./build.sh -S samples/helloworld \
-DBOARD=my_custom_board \
-DBOARD_SEARCH_PATH=/path/to/my_boards
```
**说明:**
- `BOARD_SEARCH_PATH` **必须使用绝对路径**,例如 `/home/user/my_boards``~/my_boards`,避免相对路径带来的解析问题
- 例如:如果自定义板型目录为 `/path/to/my_boards/AAA_EVB`,则:
- `-DBOARD=AAA_EVB`
- `-DBOARD_SEARCH_PATH=/path/to/my_boards`
- **外部板型文件无需自行添加构建**
### 创建外部自定义板型
关于如何创建自定义板型的详细指南,请参考 [BOARD_TEMPLATE.md](BOARD_TEMPLATE.md)。
该指南包含:
- 完整的文件结构说明
- 接口规范和实现要求
- 详细的开发步骤
- 文件模板和示例
## 板型搜索机制
构建系统按以下优先级搜索板型:
1. **板型名称**(必需)
- 必须通过 `-DBOARD=<board_name>` 参数指定
- 不指定将导致构建失败
2. **外部自定义路径**(如果指定了 `BOARD_SEARCH_PATH`
- 搜索路径:`${BOARD_SEARCH_PATH}/${BOARD}/`
- **使用绝对路径**(如 `/home/user/boards``~/boards`),避免相对路径在不同构建环境下的解析差异
3. **SDK 内置路径**(备用)
- 搜索路径:`${ARCS_SDK_BASE}/boards/${BOARD}/`
4. **验证要求**
- 板型目录必须存在
- 板型目录下必须包含 `CMakeLists.txt` 文件
如果搜索失败,构建系统会给出详细的错误提示,说明搜索路径和解决方案。
## 板型文件结构
每个板型目录必须包含以下文件:
```
board_name/
├── CMakeLists.txt # 板型构建脚本(必需)
├── board.h # 板级接口声明(必需)
├── board.c # 板级初始化实现(必需)
├── pinmux.h # 引脚配置声明(必需)
├── pinmux.c # 引脚配置实现(必需)
├── Kconfig # 板型配置选项(必需)
└── README.md # 板型说明文档(推荐)
```
详细的文件说明和模板请参考 [BOARD_TEMPLATE.md](BOARD_TEMPLATE.md)。
## 故障排查
### BOARD 未指定
**错误信息:**
```
BOARD is not specified!
```
**解决方案:**
必须使用 `-DBOARD` 参数指定板型:
```bash
./build.sh -S samples/helloworld -DBOARD=arcs_mini
```
### 板型未找到
**错误信息:**
```
Board 'xxx' not found!
```
**解决方案:**
1. 检查板型名称拼写是否正确
2. 确认板型目录存在:
- SDK 内置板型:`ls boards/xxx`
- 自定义板型:`ls ${BOARD_SEARCH_PATH}/xxx`
3. 如使用自定义板型,检查 `BOARD_SEARCH_PATH` 路径是否正确
### 板型目标重复定义
**错误信息:**
```
add_library cannot create target "module_boards" because another target
with the same name already exists. The existing target is a static library
created in source directory
```
**原因:**
使用外部自定义板型时SDK已通过${BOARD_SEARCH_PATH}/${BOARD}将板型目录添加到构建系统中,无需外部构建额外添加,否则会出现重复定义目标的错误。
**解决方案:**
外部工程CMakeLists.txt中无需添加板型目录。
### 编译错误
**常见原因:**
1. 板型代码未实现必需的接口函数
2. 头文件路径配置错误
3. Kconfig 文件缺失或配置不正确
**解决方案:**
1. 确保实现了 `board_get_name()` 函数
2. 检查 CMakeLists.txt 中的头文件路径配置
3. 确保 Kconfig 文件包含必需的配置项
## 常见问题
### Q: 如何在不同板型之间快速切换?
只需修改 `build.sh``-DBOARD` 参数:
```bash
# 切换到 arcs_mini
./build.sh -S samples/helloworld -DBOARD=arcs_mini
# 切换到 arcs_evb
./build.sh -S samples/helloworld -DBOARD=arcs_evb
```
### Q: 可以为不同的项目使用不同的板型吗?
可以。每个项目的构建命令可以指定不同的板型:
```bash
# 项目 A 使用 arcs_mini
./build.sh -S samples/project_a -DBOARD=arcs_mini
# 项目 B 使用 arcs_evb
./build.sh -S samples/project_b -DBOARD=arcs_evb
```
### Q: 自定义板型是否会被覆盖?
不会。自定义板型存储在 SDK 外部SDK 更新不会影响自定义板型。
### Q: 如何创建新的板型?
请参考 [BOARD_TEMPLATE.md](BOARD_TEMPLATE.md),其中包含完整的开发指南和步骤说明。

View File

@@ -0,0 +1,99 @@
# ARCS BOARD SUPPORT
#
# Copyright (c) 2025, LISTENAI
# SPDX-License-Identifier: Apache-2.0
# 1. 动态获取可用板型列表
function(get_available_boards output_var)
set(boards_list "")
file(GLOB board_dirs RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/*")
foreach(item ${board_dirs})
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${item}")
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${item}/CMakeLists.txt")
list(APPEND boards_list "${item}")
endif()
endif()
endforeach()
set(${output_var} ${boards_list} PARENT_SCOPE)
endfunction()
# 2. 获取板型名称
if(NOT DEFINED BOARD)
# 动态生成板型列表
get_available_boards(AVAILABLE_BOARDS)
set(BOARD_LIST_STR "")
foreach(board ${AVAILABLE_BOARDS})
string(APPEND BOARD_LIST_STR " - ${board}\n")
endforeach()
message(FATAL_ERROR
"BOARD is not specified!\n"
"Please specify a board using -DBOARD=<board_name>\n"
"Available SDK boards:\n"
"${BOARD_LIST_STR}\n"
"Example usage:\n"
" ./build.sh -S samples/helloworld -DBOARD=arcs_evb\n"
" ./build.sh -S samples/helloworld -DBOARD=my_board -DBOARD_SEARCH_PATH=/path/to/boards")
endif()
message(STATUS "Target board: ${BOARD}")
# 3. 板型搜索(两级优先级)
set(BOARD_DIR "")
# 优先级1: 自定义板型路径 (如果 BOARD_SEARCH_PATH 已定义)
if(DEFINED BOARD_SEARCH_PATH)
if(EXISTS "${BOARD_SEARCH_PATH}/${BOARD}")
set(BOARD_DIR "${BOARD_SEARCH_PATH}/${BOARD}")
message(STATUS "Board found (custom): ${BOARD} from ${BOARD_SEARCH_PATH}")
else()
message(WARNING "BOARD_SEARCH_PATH specified but board '${BOARD}' not found in ${BOARD_SEARCH_PATH}")
endif()
endif()
# 优先级2: SDK 内置板型
if(NOT BOARD_DIR)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${BOARD}")
set(BOARD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${BOARD}")
message(STATUS "Board found (SDK): ${BOARD}")
endif()
endif()
# 4. 验证板型目录存在
if(NOT BOARD_DIR)
# 动态生成板型列表
get_available_boards(AVAILABLE_BOARDS)
set(BOARD_LIST_STR "")
foreach(board ${AVAILABLE_BOARDS})
string(APPEND BOARD_LIST_STR " - ${board}\n")
endforeach()
message(FATAL_ERROR
"Board '${BOARD}' not found!\n"
"Searched in:\n"
" - ${BOARD_SEARCH_PATH}/${BOARD} (if BOARD_SEARCH_PATH defined)\n"
" - ${CMAKE_CURRENT_SOURCE_DIR}/${BOARD}\n"
"Please check:\n"
" 1. BOARD variable is set correctly (-DBOARD=xxx)\n"
" 2. Board directory exists\n"
" 3. BOARD_SEARCH_PATH is correct (if using custom board)\n"
"Available SDK boards:\n"
"${BOARD_LIST_STR}\n"
"Example usage:\n"
" ./build.sh -S samples/helloworld -DBOARD=arcs_evb\n"
" ./build.sh -S samples/helloworld -DBOARD=my_board -DBOARD_SEARCH_PATH=/path/to/boards")
endif()
# 5. 检查板型 CMakeLists.txt 存在
if(NOT EXISTS "${BOARD_DIR}/CMakeLists.txt")
message(FATAL_ERROR
"Board directory found but missing CMakeLists.txt: ${BOARD_DIR}\n"
"Each board must contain a CMakeLists.txt file.\n"
"Please refer to boards/BOARD_TEMPLATE.md for board structure requirements.")
endif()
# 6. 加载板型
# 使用第二个参数指定二进制目录,避免外部板型路径冲突
add_subdirectory(${BOARD_DIR} ${CMAKE_CURRENT_BINARY_DIR}/${BOARD})
message(STATUS "Board loaded successfully: ${BOARD_DIR}")

6
arcs-sdk/boards/Kconfig Normal file
View File

@@ -0,0 +1,6 @@
# ARCS BOARD CONFIGURATION
#
# Copyright (c) 2025, LISTENAI
# SPDX-License-Identifier: Apache-2.0
osource "$BOARD_KCONFIG_PATH"

View File

@@ -0,0 +1,17 @@
# ARCS BOARD SUPPORT
#
# Copyright (c) 2025, LISTENAI
# SPDX-License-Identifier: Apache-2.0
# 创建板型库
listenai_library_named(module_boards)
# 添加源文件
listenai_library_sources(
pinmux.c
board.c
)
# 添加头文件路径
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,18 @@
# Board Configuration
#
# Copyright (c) 2025, LISTENAI
# SPDX-License-Identifier: Apache-2.0
config BOARD_NAME
string
default "arcs_evb"
prompt "Board Name"
help
Board Name
config BOARD_ARCS_EVB
bool
default y
help
Board is arcs_evb

View File

@@ -0,0 +1,165 @@
# ARCS EVB 评估板
## 板型概述
ARCS EVB 是一款功能丰富的评估板,集成了多种外设接口,适用于完整的产品原型开发和功能评估。
**板型标识:** `arcs_evb`
## 板型外观
![ARCS EVB 评估板](../../assets/arcs_evb_board.png)
*ARCS EVB 评估板外观图(标注序号说明见下表)*
## 硬件接口说明
下表详细说明了开发板上的各个接口和组件:
| 序号 | 接口/组件 | 说明 |
|------|----------|------|
| 1 | USB接口 | TypeC 接口,提供供电和充电功能 |
| 2 | 烧录接口 | TypeC 接口,提供日志输出和固件烧录功能。注意:需要先烧录 boot 固件 |
| 3 | 开关 | 控制整个开发板的主电源开关 |
| 4 | 统一为I/O接口 | 引出 30 个 IO 口以及多组电源 |
| 5 | 扬声器接口 | 用于连接开发板默认附带的扬声器,用户也可以替换或外接其他扬声器 |
| 6 | 麦克风 | 用于连接开发板默认配套的驻极体麦克风,方便用户更换或外引麦克风。 |
| 7 | 硬回采开关 | 该开关支持单麦克风硬回采、双麦克风软回采 |
| 8 | I/O电源指示灯 | 可编程控制的 LED 灯,使用 B09 引脚 |
| 9 | 电源LED | 指示开发板的供电状态,供电正常时 LED 点亮 |
| 10 | ADC按键 | ADC 按键,通过 GPADC 检测电压来确认其值 |
| 11 | RST按键 | Reset 按键,短按该按键会对开发板执行复位操作 |
| 12 | BOOT按键 | 长按该按键上电会进入芯片烧录模式 |
| 13 | TF卡槽 | 用于插入 TF 存储卡 |
| 14 | 屏幕连接器 | 用于连接开发板默认附带的显示屏。说明LCD 适配板一侧用于固定 LCD另一侧兼容不同的 QSPI 或 SPI LCD 屏幕。不同屏幕需要对应不同的适配板 |
| 15 | 摄像头DVP接口 | 摄像头 FPC 连接器,用于连接开发板默认附带的摄像头 |
## 硬件特性
### 主要参数
- **芯片平台**:基于 ARCS 架构
- **板型尺寸**:标准评估板尺寸
- **适用场景**:产品原型开发、功能评估、方案验证
### 外设支持
#### 串口通信UART
| 串口 | 引脚 | 功能 | 功能码 | 说明 |
|------|------|------|--------|------|
| UART0 | PAD_A[3] | CP_LOG_TX | 2 | 主串口 TXCP 日志输出) |
| UART0 | PAD_A[2] | CP_LOG_RX | 2 | 主串口 RXCP 日志输出) |
| UART1 | PAD_A[21] | AP_LOG_TX | 3 | 辅助串口 TXAP 日志输出) |
#### I2C 总线
| 总线 | 引脚 | 功能 | 功能码 | 说明 |
|------|------|------|--------|------|
| I2C0 | PAD_A[22] | SCL | 8 | 主 I2C 时钟线 |
| I2C0 | PAD_A[23] | SDA | 8 | 主 I2C 数据线 |
#### SPI 总线
| 总线 | 引脚 | 功能 | 功能码 | 说明 |
|------|------|------|--------|------|
| SPI1 | PAD_B[5] | CLK | 6 | 主 SPI 时钟线 |
| SPI1 | PAD_B[3] | MISO | 6 | 主 SPI 主入从出 |
| SPI1 | PAD_B[1] | MOSI | 6 | 主 SPI 主出从入 |
#### GPIO通用输入/输出)
| 引脚组 | 引脚 | 功能名称 | 功能码 | 说明 |
|--------|------|----------|--------|------|
| GPIO_A | PAD_A[1] | LCD_RST | 1 | LCD 复位控制 |
| GPIO_A | PAD_A[24] | TP_INT | 0 | 触摸屏中断输入 |
| GPIO_A | PAD_A[25] | TP_RST | 0 | 触摸屏复位控制 |
| GPIO_A | PAD_A[27] | PA_EN | 0 | 功放使能控制 |
| GPIO_B | PAD_B[0] | LCD_CD | 0 | LCD 命令/数据选择 |
| GPIO_B | PAD_B[7] | CAMERA_PWDN | 0 | 摄像头电源控制 |
| GPIO_B | PAD_B[8] | LCD_TE | 0 | LCD 撕裂效应信号 |
| GPIO_B | PAD_B[9] | LED | 0 | LED 指示灯控制 |
#### PWM脉宽调制
| 功能名称 | 引脚 | 功能码 | 说明 |
|----------|------|--------|------|
| LCD_PWM | PAD_A[0] | 12 | LCD 背光亮度调节 |
#### SDIO
| 接口 | 引脚范围 | 引脚数量 | 功能码 | 说明 |
|------|----------|----------|--------|------|
| SDIO | PAD_A[4-9] | 6 | 15 | SD 卡/eMMC 接口(数据/控制线) |
#### DVP数字视频接口
| 接口 | 引脚范围 | 引脚数量 | 功能码 | 说明 |
|------|----------|----------|--------|------|
| DVP | PAD_A[10-20, 26] | 12 | 16 | 摄像头接口(数据线) |
#### ADC模数转换
| 功能 | 通道引脚 | 功能码 | 说明 |
|------|----------|--------|------|
| ADC | PAD_B[6] | 3 | 模拟信号采集AON 域) |
### 引脚功能说明
下表为 LS26 芯片在 Arcs-EVB 开发板上的 GPIO 分配使用列表:
| 引脚/端口 | 主功能 | 复用功能 | 说明/连接外设 | 分类 |
|-----------|--------|----------|---------------|------|
| GPIOA_00 | LCD_PWM | CJTAG_TCK | LCM模组背光控制 | 显示 |
| GPIOA_01 | LCD RST | CJTAG_TMS | LCM模组复位 | 显示 |
| GPIOA_02 | UART0 RX | | LOAD&打印CP日志 | 烧录&日志 |
| GPIOA_03 | UART0 TX | | LOAD&打印CP日志 | 烧录&日志 |
| GPIOA_04 | SD DAT1 | | TF CARD | 存储 |
| GPIOA_05 | SD DAT0 | | TF CARD | 存储 |
| GPIOA_06 | SD CLK | | TF CARD | 存储 |
| GPIOA_07 | SD CMD | | TF CARD | 存储 |
| GPIOA_08 | SD DAT3 | | TF CARD | 存储 |
| GPIOA_09 | SD DAT2 | | TF CARD | 存储 |
| GPIOA_10 | vic_h_sync | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_11 | vic_v_sync | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_12 | vic_pixel_clk | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_13 | vic_pixel_data4 | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_14 | vic_pixel_data5 | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_15 | vic_pixel_data6 | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_16 | vic_pixel_data7 | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_17 | vic_pixel_data8 | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_18 | vic_pixel_data9 | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_19 | vic_pixel_data10 | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_20 | vic_pixel_data11 | | DVP摄像头 | 兼容SPI摄像头 |
| GPIOA_21 | UART1_TX | | 打印AP日志 | 调试 |
| GPIOA_22 | I2C0 SDA | | 摄像头和TP复用 | LCM模组 |
| GPIOA_23 | I2C0 SCL | | 摄像头和TP复用 | LCM模组 |
| GPIOA_24 | TP INT | | 触摸中断 | LCM模组 |
| GPIOA_25 | TP RST | | 触摸复位 | LCM模组 |
| GPIOA_26 | VIC_CLK_OUT | | MCLK (主时钟) | 摄像头 |
| GPIOA_27 | PA_EN | | 功放MUTE | 音频 |
| GPIOA_28 | MIC1 INP | | 硅麦 | 音频 |
| GPIOA_29 | MIC1 INN | | 硅麦 | 音频 |
| GPIOA_30 | MIC0 INP | | 硅麦 | 音频 |
| GPIOA_31 | MIC0 INN | | 硅麦 | 音频 |
| GPIOB_00 | LCD SPI MISO | D1 | LCM模组 (数据线1) | 显示 (SPI) |
| GPIOB_01 | LCD SPI MOSI | D0 | LCM模组 (数据线0) | 显示 (SPI) |
| GPIOB_02 | LCD SPI HOLD | D3 | LCM模组 (数据线3) | 显示 (SPI) |
| GPIOB_03 | LCD SPI CLK | CLK | LCM模组 (时钟线) | 显示 (SPI) |
| GPIOB_04 | LCD SPI WP | D2 | LCM模组 (数据线2) | 显示 (SPI) |
| GPIOB_05 | LCD_SPI CS | CS | LCM模组 (片选) | 显示 (SPI) |
| GPIOB_06 | KEY1 | | ADC按键 | 输入/控制 |
| GPIOB_07 | KEY2 | | 触摸按键 | 输入/控制 |
| GPIOB_08 | LCD_TE | | LCD TE中断 | 显示/中断 |
| GPIOB_09 | LED | | 单色指示灯 | GPIO |
| FLASH_CS_N | FLASH_CS_N | FLASH_CS_N | Boot Flash | 存储 (Flash) |
| FLASH_MISO | FLASH_MISO | FLASH_MISO | Boot Flash | 存储 (Flash) |
| FLASH_WP_N | FLASH_WP_N | FLASH_WP_N | Boot Flash | 存储 (Flash) |
| FLASH_HOLD_N | FLASH_HOLD_N | FLASH_HOLD_N | Boot Flash | 存储 (Flash) |
| FLASH_CLK | FLASH_CLK | FLASH_CLK | Boot Flash | 存储 (Flash) |
| FLASH_MOSI | FLASH_MOSI | FLASH_MOSI | Boot Flash | 存储 (Flash) |
| USB_DP | USB_DP | | USB口 | 通信 (USB) |
| USB_DM | USB_DM | | USB口 | 通信 (USB) |
| LIN_OUTP | LIN_OUTP | | 差分输出 正 | 音频 |
| LIN_OUTN | LIN_OUTN | | 差分输出 反 | 音频 |

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) 2025, LISTENAI
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file board.c
* @brief 板级初始化实现
*/
#include "board.h"
const char* board_get_name(void)
{
return CONFIG_BOARD_NAME;
}

View File

@@ -0,0 +1,26 @@
/**
* @file board.h
* @brief 板级接口定义
*
* 本文件定义了板级支持包必须实现的标准接口。
* 所有板型都应该提供这些接口,以确保系统能够正确初始化硬件。
*/
#pragma once
#include "pinmux.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 获取板型名称
*
* @return 指向板型名称字符串的指针(静态字符串,无需释放)
*/
const char* board_get_name(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,138 @@
/*
* Copyright (c) 2026, LISTENAI
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file pinmux.c
* @brief 引脚复用配置
*
* 本文件由 LISA Pinmux Tool 自动生成
* 生成工具: https://tool.listenai.com/ls-pinmux-tool/
*
* 说明:
* - 所有外设的 pinmux 函数都会生成,未配置的外设函数体为空
* - 所有函数使用 weak 属性修饰,可在用户代码中重写
* - 尽量避免手动修改该文件,建议统一使用工具生成
*/
#include "pinmux.h"
#include "IOMuxManager.h"
__attribute__((weak)) void lisa_adc_pinmux()
{
AON_IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 6, 3);
}
__attribute__((weak)) void lisa_capture_pinmux()
{
}
__attribute__((weak)) void lisa_dvp_pinmux()
{
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 10, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 11, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 12, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 13, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 14, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 15, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 16, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 17, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 18, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 19, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 20, 16);
// IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 26, 16);
}
__attribute__((weak)) void lisa_gpioa_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, LCD_RST_PIN, 1);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, TP_INT_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, TP_RST_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, PA_EN_PIN, 0);
}
__attribute__((weak)) void lisa_gpiob_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, LCD_TE_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, LED_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, CAMERA_PWDN_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, LCD_CD_PIN, 0);
}
__attribute__((weak)) void lisa_i2c0_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 22, 8);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 23, 8);
}
__attribute__((weak)) void lisa_i2c1_pinmux()
{
}
__attribute__((weak)) void lisa_i2s0_pinmux()
{
}
__attribute__((weak)) void lisa_i2s1_pinmux()
{
}
__attribute__((weak)) void lisa_jtag_pinmux()
{
}
__attribute__((weak)) void lisa_pwm_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, LCD_PWM_PIN, 12);
}
__attribute__((weak)) void lisa_qspi_lcd_pinmux()
{
}
__attribute__((weak)) void lisa_rgb_pinmux()
{
}
__attribute__((weak)) void lisa_sdio_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 4, 15);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 5, 15);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 6, 15);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 7, 15);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 8, 15);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 9, 15);
}
__attribute__((weak)) void lisa_spi0_pinmux()
{
}
__attribute__((weak)) void lisa_spi1_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 5, 6);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 3, 6);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 1, 6);
}
__attribute__((weak)) void lisa_spi2_pinmux()
{
}
__attribute__((weak)) void lisa_uart0_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CP_LOG_RX_PIN, 2);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CP_LOG_TX_PIN, 2);
}
__attribute__((weak)) void lisa_uart1_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, AP_LOG_TX_PIN, 3);
}
__attribute__((weak)) void lisa_uart2_pinmux()
{
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2026, LISTENAI
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file pinmux.h
* @brief 引脚复用配置头文件
*
* 本文件由 LISA Pinmux Tool 自动生成
* 生成工具: https://tool.listenai.com/ls-pinmux-tool/
*
* 说明:
* - 包含所有外设 pinmux 函数的声明
* - 包含引脚别名宏定义(如果有设置)
* - 尽量避免手动修改该文件,建议统一使用工具生成
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// Pin aliases
#define LCD_PWM_PIN 0
#define LCD_RST_PIN 1
#define CP_LOG_RX_PIN 2
#define CP_LOG_TX_PIN 3
#define AP_LOG_TX_PIN 21
#define TP_INT_PIN 24
#define TP_RST_PIN 25
#define PA_EN_PIN 27
#define LCD_TE_PIN 8
#define LED_PIN 9
#define CAMERA_PWDN_PIN 7
#define LCD_CD_PIN 0
void lisa_adc_pinmux();
void lisa_capture_pinmux();
void lisa_dvp_pinmux();
void lisa_gpioa_pinmux();
void lisa_gpiob_pinmux();
void lisa_i2c0_pinmux();
void lisa_i2c1_pinmux();
void lisa_i2s0_pinmux();
void lisa_i2s1_pinmux();
void lisa_jtag_pinmux();
void lisa_pwm_pinmux();
void lisa_qspi_lcd_pinmux();
void lisa_rgb_pinmux();
void lisa_sdio_pinmux();
void lisa_spi0_pinmux();
void lisa_spi1_pinmux();
void lisa_spi2_pinmux();
void lisa_uart0_pinmux();
void lisa_uart1_pinmux();
void lisa_uart2_pinmux();
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,17 @@
# ARCS BOARD SUPPORT
#
# Copyright (c) 2025, LISTENAI
# SPDX-License-Identifier: Apache-2.0
# 创建板型库
listenai_library_named(module_boards)
# 添加源文件
listenai_library_sources(
pinmux.c
board.c
)
# 添加头文件路径
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,18 @@
# Board Configuration
#
# Copyright (c) 2025, LISTENAI
# SPDX-License-Identifier: Apache-2.0
config BOARD_NAME
string
default "arcs_mini"
prompt "Board Name"
help
Board Name
config BOARD_ARCS_MINI
bool
default y
help
Board is arcs_mini

View File

@@ -0,0 +1,190 @@
# ARCS Mini 开发板
## 板型概述
ARCS Mini 是一款紧凑型开发板,适用于快速原型开发和嵌入式应用学习。
**板型标识:** `arcs_mini`
## 板型外观
![ARCS Mini 开发板](../../assets/arcs_mini_board.png)
*ARCS Mini 开发板外观图(标注序号说明见下表)*
## 硬件接口说明
下表详细说明了开发板上的各个接口和组件:
| 序号 | 接口/组件 | 说明 |
|------|----------|------|
| 1 | 预留烧录串口 | 引出可用于烧录 boot 固件和查看日志的引脚。注意boot 固件已在工厂预烧录,一般不需要使用此接口 |
| 2 | 屏幕SPI接口 | 屏幕连接器,用于连接开发板默认附带的显示屏 |
| 3 | 摄像头DVP接口 | 摄像头 FPC 连接器,用于连接开发板默认附带的摄像头 |
| 4 | I/O拓展接口 | 引出 6 个可编程 GPIO 和一组电源/GND可用于连接外部传感器或其他外部设备。<br>引脚定义PCBA 放置如图所示时,从下到上依次为 VCC(3.3V)、GND、A04、A05、A06、A07、A08、A09 |
| 5 | 主功能按键 | 用于开发板开关机等交互触发操作 |
| 6 | 充放电LED | 充电状态指示灯,充电时红色常亮 |
| 7 | USB接口 | TypeC 接口,提供供电、充电、固件烧录功能(需要已烧录 boot 固件) |
| 8 | 可编程LED | 支持通过编程控制的 LED使用 B01 引脚 |
| 9 | RST按钮 | Reset 按键,短按该按键会对开发板执行复位操作 |
| 10 | 麦克风接口 | 用于连接开发板默认附带的驻极体麦克风,用户也可以替换或外接其他麦克风 |
| 11 | 扬声器接口 | 用于连接开发板默认附带的扬声器,用户也可以替换或外接其他扬声器 |
| 12 | 锂电池接口 | 用于连接开发板默认附带的锂电池 |
## 硬件特性
### 主要参数
- **芯片平台**:基于 ARCS 架构
- **板型尺寸**:紧凑型设计
- **适用场景**:快速原型开发、教学演示、功能验证
### 外设支持
#### 串口通信UART
| 串口 | 引脚 | 功能 | 功能码 | 说明 |
|------|------|------|--------|------|
| UART0 | PAD_A[3] | AP_LOG_TX | 2 | 主串口 TXAP 日志输出) |
| UART0 | PAD_A[2] | AP_LOG_RX | 2 | 主串口 RXAP 日志输出) |
| UART2 | PAD_B[2] | CP_LOG_TX | 3 | 辅助串口 TXCP 日志输出) |
#### I2C 总线
| 总线 | 引脚 | 功能 | 功能码 | 说明 |
|------|------|------|--------|------|
| I2C0 | PAD_B[6] | SCL | 8 | 主 I2C 时钟线(摄像头) |
| I2C0 | PAD_B[7] | SDA | 8 | 主 I2C 数据线(摄像头) |
#### SPI 总线
| 总线 | 引脚 | 功能 | 功能码 | 说明 |
|------|------|------|--------|------|
| SPI0 | PAD_A[22] | CS | 5 | LCD SPI 片选 |
| SPI0 | PAD_A[24] | MOSI | 5 | LCD SPI 主出从入 |
| SPI0 | PAD_A[25] | CLK | 5 | LCD SPI 时钟线 |
#### GPIO通用输入/输出)
| 引脚组 | 引脚 | 功能名称 | 功能码 | 说明 |
|--------|------|----------|--------|------|
| GPIO_A | PAD_A[0] | CAMERA_RST | 1 | 摄像头复位控制 |
| GPIO_A | PAD_A[1] | PA_EN | 1 | 功放使能控制 |
| GPIO_A | PAD_A[4-9] | 可编程 GPIO | 0 | IO 扩展接口 |
| GPIO_A | PAD_A[23] | LCD_CD | 0 | LCD 命令/数据选择 |
| GPIO_A | PAD_A[27] | LCD_TE | 0 | LCD 撕裂效应信号 |
| GPIO_B | PAD_B[0] | USB_DET | 0 | USB 检测 |
| GPIO_B | PAD_B[1] | LED | 0 | LED 指示灯控制 |
| GPIO_B | PAD_B[3] | POWER_EN | 0 | 电源使能控制 |
| GPIO_B | PAD_B[4] | POWER_KEY | 0 | 电源按键输入 |
| GPIO_B | PAD_B[8] | CHARGE_DET | 0 | 充电检测 |
| GPIO_B | PAD_B[9] | LCD_RST | 0 | LCD 复位控制 |
#### PWM脉宽调制
| 功能名称 | 引脚 | 功能码 | 说明 |
|----------|------|--------|------|
| LCD_PWM | PAD_A[21] | 12 | LCD 背光亮度调节 |
#### SDIO
| 接口 | 引脚范围 | 引脚数量 | 功能码 | 说明 |
|------|----------|----------|--------|------|
| SDIO | PAD_A[4-9] | 6 | 15 | SD 卡/eMMC 接口(数据/控制线) |
#### DVP数字视频接口
| 接口 | 引脚范围 | 引脚数量 | 功能码 | 说明 |
|------|----------|----------|--------|------|
| DVP | PAD_A[10-20, 26] | 12 | 16 | 摄像头接口(数据线) |
#### ADC模数转换
| 功能 | 通道引脚 | 功能码 | 说明 |
|------|----------|--------|------|
| ADC | PAD_B[5] | 3 | 电池电压检测AON 域) |
## 引脚配置说明
所有外设的引脚复用配置定义在 [pinmux.h](pinmux.h) 和 [pinmux.c](pinmux.c) 中。这些文件由 LISA Pinmux Tool 自动生成,建议使用工具进行修改。
### 引脚别名
为方便使用,常用引脚已定义别名(参见 [pinmux.h:26-41](pinmux.h#L26-L41)
```c
CAMERA_RST_PIN // PAD_A[0]
PA_EN_PIN // PAD_A[1]
AP_LOG_RX_PIN // PAD_A[2]
AP_LOG_TX_PIN // PAD_A[3]
LCD_PWM_PIN // PAD_A[21]
LCD_CD_PIN // PAD_A[23]
LCD_TE_PIN // PAD_A[27]
CP_LOG_TX_PIN // PAD_B[2]
POWER_EN_PIN // PAD_B[3]
POWER_KEY_PIN // PAD_B[4]
BAT_ADC_PIN // PAD_B[5]
CHARGE_DET_PIN // PAD_B[8]
LCD_RST_PIN // PAD_B[9]
USB_DET_PIN // PAD_B[0]
LED_PIN // PAD_B[1]
```
### 引脚功能说明
下表为 LS26 芯片在 Arcs-Mini 开发板上的 GPIO 分配使用列表:
| 引脚/端口 | 主功能 | 说明/连接外设 | 分类 |
|-----------|--------|---------------|------|
| GPIOA_00 | CAM_RST | 摄像头复位 | 摄像头 |
| GPIOA_01 | PA_EN | 功放使能控制 | 音频 |
| GPIOA_02 | UART0 RX | 预留烧录串口 | 烧录&日志 |
| GPIOA_03 | UART0 TX | 预留烧录串口 | 烧录&日志 |
| GPIOA_04 | 可编程 GPIO | IO 扩展接口 | I/O 扩展 |
| GPIOA_05 | 可编程 GPIO | IO 扩展接口 | I/O 扩展 |
| GPIOA_06 | 可编程 GPIO | IO 扩展接口 | I/O 扩展 |
| GPIOA_07 | 可编程 GPIO | IO 扩展接口 | I/O 扩展 |
| GPIOA_08 | 可编程 GPIO | IO 扩展接口 | I/O 扩展 |
| GPIOA_09 | 可编程 GPIO | IO 扩展接口 | I/O 扩展 |
| GPIOA_10 | HSYNC | DVP 摄像头水平同步 | 摄像头 |
| GPIOA_11 | VSYNC | DVP 摄像头垂直同步 | 摄像头 |
| GPIOA_12 | DVP_CLK | DVP 摄像头像素时钟 | 摄像头 |
| GPIOA_13 | DVP_D4 | DVP 摄像头数据线 4 | 摄像头 |
| GPIOA_14 | DVP_D5 | DVP 摄像头数据线 5 | 摄像头 |
| GPIOA_15 | DVP_D6 | DVP 摄像头数据线 6 | 摄像头 |
| GPIOA_16 | DVP_D7 | DVP 摄像头数据线 7 | 摄像头 |
| GPIOA_17 | DVP_D8 | DVP 摄像头数据线 8 | 摄像头 |
| GPIOA_18 | DVP_D9 | DVP 摄像头数据线 9 | 摄像头 |
| GPIOA_19 | DVP_D10 | DVP 摄像头数据线 10 | 摄像头 |
| GPIOA_20 | DVP_D11 | DVP 摄像头数据线 11 | 摄像头 |
| GPIOA_21 | LCD_PWM | LCD 背光 PWM 控制 | 显示 |
| GPIOA_22 | LCD_SPI0_CS | LCD SPI 片选 | 显示 |
| GPIOA_23 | LCD_GPIO_WR | LCD 命令/数据选择 | 显示 |
| GPIOA_24 | LCD_SPI0_MOSI | LCD SPI 数据输出 | 显示 |
| GPIOA_25 | LCD_SPI0_CLK | LCD SPI 时钟 | 显示 |
| GPIOA_26 | DVP_MCLK | DVP 摄像头主时钟 | 摄像头 |
| GPIOA_27 | LCD_TE | LCD 撕裂效应信号 | 显示 |
| GPIOA_28 | MIC1 AEC_P | 硬回采正极 | 音频 |
| GPIOA_29 | MIC1 AEC_N | 硬回采负极 | 音频 |
| GPIOA_30 | MIC0 INP | 麦克风输入正极 | 音频 |
| GPIOA_31 | MIC0 INN | 麦克风输入负极 | 音频 |
| GPIOB_00 | USB_DET | USB 插入检测 | 通信 (USB) |
| GPIOB_01 | LED | 用户 LED | GPIO |
| GPIOB_02 | uart2_txd | 预留烧录串口 TX | 烧录&日志 |
| GPIOB_03 | POW_EN | 电源使能 | 电源 |
| GPIOB_04 | power_KEY | 主功能按键 | 输入/控制 |
| GPIOB_05 | ADC_Bat_Vol | 电池电量检测 | 电源 |
| GPIOB_06 | SDA0 | I2C0 数据线(摄像头) | 通信 (I2C) |
| GPIOB_07 | CLK0 | I2C0 时钟线(摄像头) | 通信 (I2C) |
| GPIOB_08 | CHARGE_DET | 充电状态检测 | 电源 |
| GPIOB_09 | LCD_RST | LCD 复位 | 显示 |
| FLASH_CS_N | FLASH_CS_N | Boot Flash 片选 | 存储 (Flash) |
| FLASH_MISO | FLASH_MISO | Boot Flash 数据输入 | 存储 (Flash) |
| FLASH_WP_N | FLASH_WP_N | Boot Flash 写保护 | 存储 (Flash) |
| FLASH_HOLD_N | FLASH_HOLD_N | Boot Flash 保持 | 存储 (Flash) |
| FLASH_CLK | FLASH_CLK | Boot Flash 时钟 | 存储 (Flash) |
| FLASH_MOSI | FLASH_MOSI | Boot Flash 数据输出 | 存储 (Flash) |
| USB_DP | USB_DP | USB 差分正极 | 通信 (USB) |
| USB_DM | USB_DM | USB 差分负极 | 通信 (USB) |
| MIC_BIAS | MIC_BIAS | 麦克风偏置 | 音频 |
| LIN_OUTP | LIN_OUTP | 差分输出正极 | 音频 |
| LIN_OUTN | LIN_OUTN | 差分输出负极 | 音频 |

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) 2025, LISTENAI
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file board.c
* @brief 板级初始化实现
*/
#include "board.h"
const char* board_get_name(void)
{
return "arcs_mini";
}

View File

@@ -0,0 +1,26 @@
/**
* @file board.h
* @brief 板级接口定义
*
* 本文件定义了板级支持包必须实现的标准接口。
* 所有板型都应该提供这些接口,以确保系统能够正确初始化硬件。
*/
#pragma once
#include "pinmux.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 获取板型名称
*
* @return 指向板型名称字符串的指针(静态字符串,无需释放)
*/
const char* board_get_name(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,134 @@
/*
* Copyright (c) 2026, LISTENAI
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file pinmux.c
* @brief 引脚复用配置
*
* 本文件由 LISA Pinmux Tool 自动生成
* 生成工具: https://tool.listenai.com/ls-pinmux-tool/
*
* 说明:
* - 所有外设的 pinmux 函数都会生成,未配置的外设函数体为空
* - 所有函数使用 weak 属性修饰,可在用户代码中重写
* - 尽量避免手动修改该文件,建议统一使用工具生成
*/
#include "pinmux.h"
#include "IOMuxManager.h"
__attribute__((weak)) void lisa_adc_pinmux()
{
AON_IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, BAT_ADC_PIN, 3);
}
__attribute__((weak)) void lisa_capture_pinmux()
{
}
__attribute__((weak)) void lisa_dvp_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_HSYNC_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_VSYNC_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_PCLK_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_D0_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_D1_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_D2_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_D3_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_D4_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_D5_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_D6_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_D7_PIN, 16);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAM_MCLK_PIN, 16);
}
__attribute__((weak)) void lisa_gpioa_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, CAMERA_RST_PIN, 1);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, PA_EN_PIN, 1);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, LCD_CD_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, LCD_TE_PIN, 0);
}
__attribute__((weak)) void lisa_gpiob_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, USB_DET_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, LED_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, POWER_EN_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, POWER_KEY_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, CHARGE_DET_PIN, 0);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, LCD_RST_PIN, 0);
}
__attribute__((weak)) void lisa_i2c0_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 6, 8);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, 7, 8);
}
__attribute__((weak)) void lisa_i2c1_pinmux()
{
}
__attribute__((weak)) void lisa_i2s0_pinmux()
{
}
__attribute__((weak)) void lisa_i2s1_pinmux()
{
}
__attribute__((weak)) void lisa_jtag_pinmux()
{
}
__attribute__((weak)) void lisa_pwm_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, LCD_PWM_PIN, 12);
}
__attribute__((weak)) void lisa_qspi_lcd_pinmux()
{
}
__attribute__((weak)) void lisa_rgb_pinmux()
{
}
__attribute__((weak)) void lisa_sdio_pinmux()
{
}
__attribute__((weak)) void lisa_spi0_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, LCD_CS_PIN, 5);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, LCD_SPI_DATA_PIN, 5);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, LCD_SPI_CLK_PIN, 5);
}
__attribute__((weak)) void lisa_spi1_pinmux()
{
}
__attribute__((weak)) void lisa_spi2_pinmux()
{
}
__attribute__((weak)) void lisa_uart0_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, AP_LOG_RX_PIN, 2);
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, AP_LOG_TX_PIN, 2);
}
__attribute__((weak)) void lisa_uart1_pinmux()
{
IOMuxManager_PinConfigure(CSK_IOMUX_PAD_B, CP_LOG_TX_PIN, 3);
}
__attribute__((weak)) void lisa_uart2_pinmux()
{
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2026, LISTENAI
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file pinmux.h
* @brief 引脚复用配置头文件
*
* 本文件由 LISA Pinmux Tool 自动生成
* 生成工具: https://tool.listenai.com/ls-pinmux-tool/
*
* 说明:
* - 包含所有外设 pinmux 函数的声明
* - 包含引脚别名宏定义(如果有设置)
* - 尽量避免手动修改该文件,建议统一使用工具生成
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// Pin aliases
#define CAMERA_RST_PIN 0
#define PA_EN_PIN 1
#define AP_LOG_RX_PIN 2
#define AP_LOG_TX_PIN 3
#define CAM_HSYNC_PIN 10
#define CAM_VSYNC_PIN 11
#define CAM_PCLK_PIN 12
#define CAM_D0_PIN 13
#define CAM_D1_PIN 14
#define CAM_D2_PIN 15
#define CAM_D3_PIN 16
#define CAM_D4_PIN 17
#define CAM_D5_PIN 18
#define CAM_D6_PIN 19
#define CAM_D7_PIN 20
#define LCD_PWM_PIN 21
#define LCD_CS_PIN 22
#define LCD_CD_PIN 23
#define LCD_SPI_DATA_PIN 24
#define LCD_SPI_CLK_PIN 25
#define CAM_MCLK_PIN 26
#define LCD_TE_PIN 27
#define USB_DET_PIN 0
#define LED_PIN 1
#define CP_LOG_TX_PIN 2
#define POWER_EN_PIN 3
#define POWER_KEY_PIN 4
#define BAT_ADC_PIN 5
#define CHARGE_DET_PIN 8
#define LCD_RST_PIN 9
void lisa_adc_pinmux();
void lisa_capture_pinmux();
void lisa_dvp_pinmux();
void lisa_gpioa_pinmux();
void lisa_gpiob_pinmux();
void lisa_i2c0_pinmux();
void lisa_i2c1_pinmux();
void lisa_i2s0_pinmux();
void lisa_i2s1_pinmux();
void lisa_jtag_pinmux();
void lisa_pwm_pinmux();
void lisa_qspi_lcd_pinmux();
void lisa_rgb_pinmux();
void lisa_sdio_pinmux();
void lisa_spi0_pinmux();
void lisa_spi1_pinmux();
void lisa_spi2_pinmux();
void lisa_uart0_pinmux();
void lisa_uart1_pinmux();
void lisa_uart2_pinmux();
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,505 @@
{
"project": {
"id": 1,
"name": "ARCS_76PIN_MINI",
"sdk": "v0.1.0",
"soc": "LS2664",
"package": "QFN76"
},
"pinConfig": {
"PA00": {
"alias": "CAMERA_RST",
"function": "gpioa_00",
"instance": "GPIOA",
"peripheral": "GPIO",
"signal": "GPIO_A_00"
},
"PA01": {
"alias": "PA_EN",
"function": "gpioa_01",
"instance": "GPIOA",
"peripheral": "GPIO",
"signal": "GPIO_A_01"
},
"PA02": {
"alias": "AP_LOG_RX",
"function": "uart0_rxd",
"instance": "UART0",
"peripheral": "UART",
"signal": "RX"
},
"PA03": {
"alias": "AP_LOG_TX",
"function": "uart0_txd",
"instance": "UART0",
"peripheral": "UART",
"signal": "TX"
},
"PA10": {
"function": "vic_h_sync",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_H_SYNC",
"alias": "CAM_HSYNC"
},
"PA11": {
"function": "vic_v_sync",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_V_SYNC",
"alias": "CAM_VSYNC"
},
"PA12": {
"function": "vic_pixel_clk",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_PIXEL_CLK",
"alias": "CAM_PCLK"
},
"PA13": {
"function": "vic_pixel_data4",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_PIXEL_DATA4",
"alias": "CAM_D0"
},
"PA14": {
"function": "vic_pixel_data5",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_PIXEL_DATA5",
"alias": "CAM_D1"
},
"PA15": {
"function": "vic_pixel_data6",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_PIXEL_DATA6",
"alias": "CAM_D2"
},
"PA16": {
"function": "vic_pixel_data7",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_PIXEL_DATA7",
"alias": "CAM_D3"
},
"PA17": {
"function": "vic_pixel_data8",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_PIXEL_DATA8",
"alias": "CAM_D4"
},
"PA18": {
"function": "vic_pixel_data9",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_PIXEL_DATA9",
"alias": "CAM_D5"
},
"PA19": {
"function": "vic_pixel_data10",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_PIXEL_DATA10",
"alias": "CAM_D6"
},
"PA20": {
"function": "vic_pixel_data11",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_PIXEL_DATA11",
"alias": "CAM_D7"
},
"PA21": {
"alias": "LCD_PWM",
"function": "gpt_pwm_1",
"instance": "CH1",
"peripheral": "PWM",
"signal": "OUT"
},
"PA22": {
"function": "spi0_cs_n",
"peripheral": "SPI",
"instance": "SPI0",
"signal": "CS",
"alias": "LCD_CS"
},
"PA23": {
"alias": "LCD_CD",
"function": "gpioa_23",
"instance": "GPIOA",
"peripheral": "GPIO",
"signal": "GPIO_A_23"
},
"PA24": {
"function": "spi0_mosi",
"peripheral": "SPI",
"instance": "SPI0",
"signal": "MOSI",
"alias": "LCD_SPI_DATA"
},
"PA25": {
"function": "spi0_clk",
"peripheral": "SPI",
"instance": "SPI0",
"signal": "CLK",
"alias": "LCD_SPI_CLK"
},
"PA26": {
"function": "vic_clk_out",
"peripheral": "DVP",
"instance": "DVP",
"signal": "VIC_CLK_OUT",
"alias": "CAM_MCLK"
},
"PA27": {
"alias": "LCD_TE",
"function": "gpioa_27",
"instance": "GPIOA",
"peripheral": "GPIO",
"signal": "GPIO_A_27"
},
"PB00": {
"alias": "USB_DET",
"function": "gpiob_00",
"instance": "GPIOB",
"peripheral": "GPIO",
"signal": "GPIO_B_00"
},
"PB01": {
"alias": "LED",
"function": "gpiob_01",
"instance": "GPIOB",
"peripheral": "GPIO",
"signal": "GPIO_B_01"
},
"PB02": {
"alias": "CP_LOG_TX",
"function": "uart1_txd",
"instance": "UART1",
"peripheral": "UART",
"signal": "TX"
},
"PB03": {
"alias": "POWER_EN",
"function": "gpiob_03",
"instance": "GPIOB",
"peripheral": "GPIO",
"signal": "GPIO_B_03"
},
"PB04": {
"alias": "POWER_KEY",
"function": "gpiob_04",
"instance": "GPIOB",
"peripheral": "GPIO",
"signal": "GPIO_B_04"
},
"PB05": {
"alias": "BAT_ADC",
"function": "adc_ch3",
"instance": "CH3",
"peripheral": "ADC",
"signal": "IN"
},
"PB06": {
"function": "i2c0_sda",
"instance": "I2C0",
"peripheral": "I2C",
"signal": "SDA"
},
"PB07": {
"function": "i2c0_scl",
"instance": "I2C0",
"peripheral": "I2C",
"signal": "SCL"
},
"PB08": {
"alias": "CHARGE_DET",
"function": "gpiob_08",
"instance": "GPIOB",
"peripheral": "GPIO",
"signal": "GPIO_B_08"
},
"PB09": {
"alias": "LCD_RST",
"function": "gpiob_09",
"instance": "GPIOB",
"peripheral": "GPIO",
"signal": "GPIO_B_09"
}
},
"peripheralConfig": {
"ADC": {
"CH0": {
"IN": null
},
"CH1": {
"IN": null
},
"CH2": {
"IN": null
},
"CH3": {
"IN": "PB05"
},
"CH4": {
"IN": null
},
"CH5": {
"IN": null
}
},
"CAPTURE": {
"CH0": {
"IN": null
},
"CH1": {
"IN": null
},
"CH2": {
"IN": null
},
"CH3": {
"IN": null
},
"CH4": {
"IN": null
},
"CH5": {
"IN": null
},
"CH6": {
"IN": null
},
"CH7": {
"IN": null
}
},
"DVP": {
"DVP": {
"VIC_H_SYNC": "PA10",
"VIC_V_SYNC": "PA11",
"VIC_PIXEL_CLK": "PA12",
"VIC_CLK_OUT": "PA26",
"VIC_PIXEL_DATA0": null,
"VIC_PIXEL_DATA1": null,
"VIC_PIXEL_DATA2": null,
"VIC_PIXEL_DATA3": null,
"VIC_PIXEL_DATA4": "PA13",
"VIC_PIXEL_DATA5": "PA14",
"VIC_PIXEL_DATA6": "PA15",
"VIC_PIXEL_DATA7": "PA16",
"VIC_PIXEL_DATA8": "PA17",
"VIC_PIXEL_DATA9": "PA18",
"VIC_PIXEL_DATA10": "PA19",
"VIC_PIXEL_DATA11": "PA20"
}
},
"GPIO": {
"GPIOA": {
"GPIO_A_00": "PA00",
"GPIO_A_01": "PA01",
"GPIO_A_02": null,
"GPIO_A_03": null,
"GPIO_A_04": null,
"GPIO_A_05": null,
"GPIO_A_06": null,
"GPIO_A_07": null,
"GPIO_A_08": null,
"GPIO_A_09": null,
"GPIO_A_10": null,
"GPIO_A_11": null,
"GPIO_A_12": null,
"GPIO_A_13": null,
"GPIO_A_14": null,
"GPIO_A_15": null,
"GPIO_A_16": null,
"GPIO_A_17": null,
"GPIO_A_18": null,
"GPIO_A_19": null,
"GPIO_A_20": null,
"GPIO_A_21": null,
"GPIO_A_22": null,
"GPIO_A_23": "PA23",
"GPIO_A_24": null,
"GPIO_A_25": null,
"GPIO_A_26": null,
"GPIO_A_27": "PA27",
"GPIO_A_28": null,
"GPIO_A_29": null,
"GPIO_A_30": null,
"GPIO_A_31": null
},
"GPIOB": {
"GPIO_B_00": "PB00",
"GPIO_B_01": "PB01",
"GPIO_B_02": null,
"GPIO_B_03": "PB03",
"GPIO_B_04": "PB04",
"GPIO_B_05": null,
"GPIO_B_06": null,
"GPIO_B_07": null,
"GPIO_B_08": "PB08",
"GPIO_B_09": "PB09"
}
},
"I2C": {
"I2C0": {
"SCL": "PB07",
"SDA": "PB06"
},
"I2C1": {
"SCL": null,
"SDA": null
}
},
"I2S": {
"I2S0": {
"BCLK": null,
"LRCK": null,
"SDOUT": null,
"SDIN": null
},
"I2S1": {
"BCLK": null,
"LRCK": null,
"SDOUT": null,
"SDIN": null
}
},
"JTAG": {
"JTAG": {
"TCK": null,
"TMS": null,
"TDI": null,
"TDO": null
}
},
"PWM": {
"CH0": {
"OUT": null
},
"CH1": {
"OUT": "PA21"
},
"CH2": {
"OUT": null
},
"CH3": {
"OUT": null
},
"CH4": {
"OUT": null
},
"CH5": {
"OUT": null
},
"CH6": {
"OUT": null
},
"CH7": {
"OUT": null
}
},
"QSPI_LCD": {
"QSPI_LCD": {
"CS": null,
"CLK": null,
"IO0": null,
"IO1": null,
"IO2": null,
"IO3": null
}
},
"RGB": {
"RGB": {
"HSYNC": null,
"VSYNC": null,
"PCLK": null,
"DE": null,
"DR0": null,
"DR1": null,
"DR2": null,
"DR3": null,
"DR4": null,
"DR5": null,
"DR6": null,
"DR7": null,
"DG0": null,
"DG1": null,
"DG2": null,
"DG3": null,
"DG4": null,
"DG5": null,
"DG6": null,
"DG7": null,
"DB0": null,
"DB1": null,
"DB2": null,
"DB3": null,
"DB4": null,
"DB5": null,
"DB6": null,
"DB7": null
}
},
"SDIO": {
"SDIO": {
"CMD": null,
"CLK": null,
"DAT0": null,
"DAT1": null,
"DAT2": null,
"DAT3": null
}
},
"SPI": {
"SPI0": {
"CLK": "PA25",
"CS": "PA22",
"MISO": null,
"MOSI": "PA24",
"WP": null,
"HOLD": null
},
"SPI1": {
"CLK": null,
"CS": null,
"MISO": null,
"MOSI": null,
"WP": null,
"HOLD": null
},
"SPI2": {
"CLK": null,
"CS": null,
"MISO": null,
"MOSI": null,
"WP": null,
"HOLD": null
}
},
"UART": {
"UART0": {
"TX": "PA03",
"RX": "PA02",
"CTS": null,
"RTS": null
},
"UART1": {
"TX": "PB02",
"RX": null,
"CTS": null,
"RTS": null
},
"UART2": {
"TX": null,
"RX": null,
"CTS": null,
"RTS": null
}
}
}
}

View File

@@ -0,0 +1,37 @@
.. _boards:
板型支持
========
板型支持系统负责管理不同硬件板卡的配置和初始化。本系统采用模块化设计将引脚复用pinmux配置与板级初始化逻辑分离便于维护和扩展。
系统特性
--------
- **模块化设计**:板级代码与引脚配置分离
- **外部板型支持**:支持在 SDK 外部添加自定义板型,无需修改 SDK 源码
- **两级搜索机制**:优先搜索自定义路径,然后搜索 SDK 内置板型
- **简单易用**:通过 ``build.sh`` 命令行参数即可切换板型
- **配置管理**:通过 Kconfig 管理板型配置选项
已支持板型列表
--------------
SDK 当前内置以下板型:
* **arcs_mini** - ARCS Mini 开发板,紧凑型设计,适用于快速原型开发
* **arcs_evb** - ARCS EVB 评估板,功能丰富,适用于产品原型开发和功能评估
详细的板型技术资料请参考各板型目录下的 README.md 文档。
详细文档
--------
.. toctree::
:maxdepth: 1
BOARD_USAGE.md
BOARD_TEMPLATE.md
arcs_mini/README.md
arcs_evb/README.md

252
arcs-sdk/build.sh Executable file
View File

@@ -0,0 +1,252 @@
#!/bin/bash
set -e
usage() {
echo "使用方式: $0 [选项]"
echo "选项:"
echo " -S, --Source <path> 指定项目源码路径 (默认为当前脚本所在目录)"
echo " -t, --target <target> 指定构建目标 (如 menuconfig)"
echo " -C, --Clean 清理构建目录"
echo " -B, --build 构建输出目录"
echo " -j<N>, --jobs <N> 指定并发构建任务数 (默认: 4)"
echo " -h, --help 显示此帮助信息"
echo " -r, --release 以 Release 模式构建 (移除 DEBUG_PATH 信息)"
echo " -w, --warnings-as-errors 将警告视为错误"
echo " -v, --verbose 显示详细的编译命令 (ninja -v)"
echo " -d, --debug 启用调试模式 (ninja -d explain + 错误诊断)"
echo " -G, --generator <type> 指定构建工具 (Ninja 或 Makefile, 默认: Ninja)"
echo " -D<var>=<value> 传递 CMake 变量 (可多次使用)"
echo ""
echo "示例:"
echo " $0 -S samples/helloworld -DBOARD=arcs_mini 指定板型构建"
echo " $0 -S samples/helloworld -DBOARD=arcs_evb 使用 EVB 板型"
echo " $0 -S samples/helloworld -t menuconfig -DBOARD=arcs_mini 运行 menuconfig"
echo " $0 -C -S samples/helloworld -DBOARD=arcs_mini 清理并重新构建"
echo " $0 -S samples/helloworld -j1 单线程构建(或 -j 1"
echo " $0 -S samples/helloworld -DBOARD=my_board -DBOARD_SEARCH_PATH=/path/to/boards 使用自定义板型"
exit 1
}
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_PATH="$SCRIPT_DIR"
TARGET=""
CLEAN=false
OUTPUT="build"
JOBS=4
WARNINGS_AS_ERRORS=false
RELEASE=false
VERBOSE=false
DEBUG=false
GENERATOR="Ninja"
ARCS_BASE_DIR_NAME="arcs-sdk"
ARCS_DEV_TOOLS_DIR_NAME="listenai-dev-tools"
ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME="gcc"
ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME="listenai-tools"
find_arcs_base() {
local current_dir=$(cd "$(dirname "$0")" && pwd)
local dir_name="$ARCS_BASE_DIR_NAME"
while [ "$current_dir" != "/" ]; do
if [ -d "$current_dir/$dir_name" ]; then
echo "Found ARCS_BASE: $current_dir/$dir_name"
export ARCS_BASE="$current_dir/$dir_name"
return 0
fi
current_dir=$(dirname "$current_dir")
done
echo "ARCS_BASE not found, Please add ARCS_BASE environment variable or set ARCS_BASE_DIR_NAME to the correct directory."
echo "Current Target ARCS_BASE directory name: $ARCS_BASE_DIR_NAME."
exit 1
}
find_dev_tools() {
local current_dir=$(cd "$(dirname "$0")" && pwd)
local dir_name="$ARCS_DEV_TOOLS_DIR_NAME"
echo "trying to find $dir_name in parent directories..."
while [ "$current_dir" != "/" ]; do
if [ -d "$current_dir/$dir_name" ]; then
echo "Found $dir_name: $current_dir/$dir_name"
if [ -d "$current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME" ]; then
echo "Found LISTENAI_TOOLS_PATH: $current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME"
export LISTENAI_TOOLS_PATH="$current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME"
fi
if [ -d "$current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME" ]; then
echo "Found NUCLEI_TOOLCHAIN_PATH: $current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME"
export NUCLEI_TOOLCHAIN_PATH="$current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME"
fi
return 0
fi
current_dir=$(dirname "$current_dir")
done
}
while [[ $# -gt 0 ]]; do
case $1 in
-S|--Source)
PROJECT_PATH="$2"
shift 2
;;
-B|--build)
OUTPUT="$2"
shift 2
;;
-t|--target)
TARGET="$2"
shift 2
;;
-j|--jobs)
JOBS="$2"
shift 2
;;
-j*)
JOBS="${1#-j}"
shift 1
;;
-C|--Clean)
CLEAN=true
shift 1
;;
-w|--warnings-as-errors)
WARNINGS_AS_ERRORS=true
shift 1
;;
-v|--verbose)
VERBOSE=true
shift 1
;;
-d|--debug)
DEBUG=true
VERBOSE=true # debug 模式自动启用 verbose
shift 1
;;
-G|--generator)
GENERATOR="$2"
shift 2
;;
-h|--help)
usage
;;
-r|--release)
RELEASE=true
shift 1
;;
-D*)
CMAKE_VARS+=("$1")
shift 1
;;
*)
echo "未知参数: $1"
usage
;;
esac
done
echo "Source: $PROJECT_PATH"
echo "Target: $TARGET"
echo "Clean : $CLEAN"
if [ -z "$LISTENAI_TOOLS_PATH" ] || [ -z "$NUCLEI_TOOLCHAIN_PATH" ]; then
find_dev_tools
fi
if [ -z "${LISTENAI_TOOLS_PATH}" ]; then
export LISTENAI_TOOLS_PATH="请添加 LISTENAI_TOOLS_PATH 环境变量,或在此行设置正确的路径"
echo "请添加 LISTENAI_TOOLS_PATH 环境变量或者修改脚本后, 注释脚本第 $LINENO";exit 1;
fi
if [ -z "${NUCLEI_TOOLCHAIN_PATH}" ]; then
export NUCLEI_TOOLCHAIN_PATH="请添加 NUCLEI_TOOLCHAIN_PATH 环境变量,或在此行设置正确的路径"
echo "请添加 NUCLEI_TOOLCHAIN_PATH 环境变量或者修改脚本后, 注释脚本第 $LINENO";exit 1;
fi
############### 下面代码不用修改 ##################
# 构建工具的位置
CMAKE_PROGRAM="$LISTENAI_TOOLS_PATH/cmake/bin/cmake"
NINJA_PROGRAM="$LISTENAI_TOOLS_PATH/ninja/ninja"
# 根据选择的构建工具设置生成器和构建程序
if [ "$GENERATOR" = "Makefile" ] || [ "$GENERATOR" = "Unix Makefiles" ]; then
CMAKE_GENERATOR="Unix Makefiles"
BUILD_PROGRAM="make"
echo "Using Makefile generator"
else
CMAKE_GENERATOR="Ninja"
BUILD_PROGRAM="$NINJA_PROGRAM"
echo "Using Ninja generator"
fi
# 配置环境变量 ARCS_BASE
if [ -z "$ARCS_BASE" ]; then
find_arcs_base
fi
if [ "$CLEAN" = true ]; then
rm -rf $OUTPUT
fi
# Initialize CMAKE_VARS array if it doesn't exist
declare -a CMAKE_VARS
# Add warnings-as-errors flag if enabled
if [ "$WARNINGS_AS_ERRORS" = true ]; then
CMAKE_VARS+=("-DCMAKE_C_FLAGS=-Werror")
CMAKE_VARS+=("-DCMAKE_CXX_FLAGS=-Werror")
echo "Treating warnings as errors"
fi
# Add release flags if enabled
if [ "$RELEASE" = true ]; then
CMAKE_VARS+=("-DENABLE_DEBUG_PATH=OFF")
echo "Release mode enabled (-DENABLE_DEBUG_PATH=OFF)"
fi
if [ "$CMAKE_GENERATOR" = "Ninja" ]; then
$CMAKE_PROGRAM -B "$OUTPUT" -G "$CMAKE_GENERATOR" -S "$PROJECT_PATH" \
-DCMAKE_MAKE_PROGRAM="$BUILD_PROGRAM" \
"${CMAKE_VARS[@]}"
else
$CMAKE_PROGRAM -B "$OUTPUT" -G "$CMAKE_GENERATOR" -S "$PROJECT_PATH" \
"${CMAKE_VARS[@]}"
fi
# Prepare ninja debug flags
NINJA_DEBUG_FLAGS=""
if [ "$DEBUG" = true ]; then
NINJA_DEBUG_FLAGS="-d explain"
echo "Debug mode enabled (ninja $NINJA_DEBUG_FLAGS)"
fi
# Build with optional verbose/debug flags
set +e # 临时允许命令失败
if [ "$VERBOSE" = true ]; then
echo "Verbose mode enabled (ninja -v)"
if [ -z "$TARGET" ]; then
$CMAKE_PROGRAM --build "$OUTPUT" -j${JOBS} -- -v $NINJA_DEBUG_FLAGS
BUILD_EXIT_CODE=$?
else
$CMAKE_PROGRAM --build "$OUTPUT" --target "$TARGET" -j${JOBS} -- -v $NINJA_DEBUG_FLAGS
BUILD_EXIT_CODE=$?
fi
else
if [ -z "$TARGET" ]; then
$CMAKE_PROGRAM --build "$OUTPUT" -j${JOBS} -- $NINJA_DEBUG_FLAGS
BUILD_EXIT_CODE=$?
else
$CMAKE_PROGRAM --build "$OUTPUT" --target "$TARGET" -j${JOBS} -- $NINJA_DEBUG_FLAGS
BUILD_EXIT_CODE=$?
fi
fi
set -e # 恢复严格模式
if [ "${BUILD_EXIT_CODE:-0}" -ne 0 ]; then
echo "Build failed (exit code: $BUILD_EXIT_CODE)" >&2
exit "$BUILD_EXIT_CODE"
fi

78
arcs-sdk/cmake/Kconfig Normal file
View File

@@ -0,0 +1,78 @@
menu "Common Compile and Link Options"
config DEBUG
bool "debug"
default y
choice
prompt "optimize level"
default COMPILE_OPTION_OPTIMIZE_LEVEL_OS
config COMPILE_OPTION_OPTIMIZE_LEVEL_O0
bool "O0"
config COMPILE_OPTION_OPTIMIZE_LEVEL_O1
bool "O1"
config COMPILE_OPTION_OPTIMIZE_LEVEL_O2
bool "O2"
config COMPILE_OPTION_OPTIMIZE_LEVEL_O3
bool "O3"
config COMPILE_OPTION_OPTIMIZE_LEVEL_OS
bool "OS"
endchoice
config LINK_OPTION_LISTENAI_LIBRARY_WHOLE_ARCHIVE
bool "whole arch link"
default y
help
Enable whole archive link option for listenai libraries (libm.a is automatically excluded)
config LINK_OPTION_LISTENAI_LIBRARY_GROUP
bool "group link"
default n
help
Enable group link option for listenai libraries
config LINK_OPTION_GC_SECTIONS
bool "gc sections"
default y
config COMPILE_OPTION_GENERATE_DEBUG_FILES
bool "generate debug files"
default y
help
Enable generate debug files like lst, relf, symbol table, etc
config COMPILE_OPTION_WARNING_AS_ERROR
bool "warning as error"
default n
config COMPILE_OPTION_WARNING_ALL
bool "warning all"
default n
config COMPILE_OPTION_WARNING_DISABLE
bool "warning disable"
default n
config FPU
bool "fpu"
default n
config PRINT_MEMORY_USAGE
bool "print memory usage"
default y
choice
prompt "link options specs"
default LINK_OPTION_NONE_SPECS
config LINK_OPTION_NOSYS_SPECS
bool "link options nosys specs"
config LINK_OPTION_NANO_SPECS
bool "link options nano specs"
config LINK_OPTION_NONE_SPECS
bool "link options none specs"
endchoice
endmenu

View File

@@ -0,0 +1,10 @@
set(CORE_FLAGS -mtune=nuclei-300-series -msave-restore)
if (CONFIG_FPU)
set(FPU_FLAGS -march=rv32imafc_zba_zbb_zbc_zbs -mabi=ilp32f)
else()
set(FPU_FLAGS -march=rv32imac_zba_zbb_zbc_zbs -mabi=ilp32)
endif()
add_compile_options(${CORE_FLAGS} ${FPU_FLAGS})
add_link_options(${CORE_FLAGS} ${FPU_FLAGS})

View File

@@ -0,0 +1,44 @@
if (NOT DEFINED NUCLEI_TOOLCHAIN_PATH)
if(DEFINED ENV{NUCLEI_TOOLCHAIN_PATH})
set(NUCLEI_TOOLCHAIN_PATH $ENV{NUCLEI_TOOLCHAIN_PATH})
else()
if (NOT DEFINED LISTENAI_TOOLS_PATH)
message(FATAL_ERROR "NUCLEI_TOOLCHAIN_PATH is not defined")
else()
set(NUCLEI_TOOLCHAIN_PATH ${LISTENAI_TOOLS_PATH}/nuclei-toolchain)
endif()
endif()
endif()
string(REPLACE "\\" "/" NUCLEI_TOOLCHAIN_PATH "${NUCLEI_TOOLCHAIN_PATH}")
set(TOOLCHAIN_PATH ${NUCLEI_TOOLCHAIN_PATH})
set(TOOLCHAIN_PREFIX riscv64-unknown-elf-)
set(TOOLCHAIN_SUFFIX )
if (WIN32)
set(TOOLCHAIN_SUFFIX .exe)
endif()
set(CROSS_COMPILE ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_PREFIX})
# Use ccache to speed up compilation if it is installed
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
endif()
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER ${CROSS_COMPILE}gcc${TOOLCHAIN_SUFFIX})
set(CMAKE_CXX_COMPILER ${CROSS_COMPILE}g++${TOOLCHAIN_SUFFIX})
set(CMAKE_LINKER ${CROSS_COMPILE}ld${TOOLCHAIN_SUFFIX})
set(CMAKE_OBJCOPY ${CROSS_COMPILE}objcopy${TOOLCHAIN_SUFFIX})
set(CMAKE_OBJDUMP ${CROSS_COMPILE}objdump${TOOLCHAIN_SUFFIX})
set(CMAKE_READELF ${CROSS_COMPILE}readelf${TOOLCHAIN_SUFFIX})
set(CMAKE_SIZE ${CROSS_COMPILE}size${TOOLCHAIN_SUFFIX})
set(CMAKE_NM ${CROSS_COMPILE}nm${TOOLCHAIN_SUFFIX})
if (NOT EXISTS ${CMAKE_C_COMPILER})
message(FATAL_ERROR "Compile not found: ${CMAKE_C_COMPILER}")
endif()
message(STATUS "Found toolchain path: ${NUCLEI_TOOLCHAIN_PATH}")

View File

@@ -0,0 +1,67 @@
if (CONFIG_DEBUG)
add_compile_options(-g)
endif()
if (CONFIG_COMPILE_OPTION_OPTIMIZE_LEVEL_O0)
add_compile_options(-O0)
endif()
if (CONFIG_COMPILE_OPTION_OPTIMIZE_LEVEL_O1)
add_compile_options(-O1)
endif()
if (CONFIG_COMPILE_OPTION_OPTIMIZE_LEVEL_O2)
add_compile_options(-O2)
endif()
if (CONFIG_COMPILE_OPTION_OPTIMIZE_LEVEL_O3)
add_compile_options(-O3)
endif()
if (CONFIG_COMPILE_OPTION_OPTIMIZE_LEVEL_OS)
add_compile_options(-Os)
endif()
if (CONFIG_COMPILE_OPTION_WARNING_AS_ERROR)
add_compile_options(-Werror)
endif()
if (CONFIG_COMPILE_OPTION_WARNING_ALL)
add_compile_options(-Wall)
endif()
if (CONFIG_COMPILE_OPTION_WARNING_DISABLE)
add_compile_options(-w)
endif()
option(ENABLE_DEBUG_PATH "Enable debug path info" ON)
add_compile_options(-MMD)
add_compile_options(-Wno-comment)
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-int-conversion>)
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-implicit-function-declaration>)
add_compile_options(-fno-common)
add_compile_options(-fno-builtin-printf -fno-builtin-puts)
add_compile_options(-fno-omit-frame-pointer -fno-optimize-sibling-calls)
add_compile_options(-ffunction-sections -fdata-sections -ffast-math)
add_compile_options(-fdiagnostics-color=always)
# add_compile_options(-mcmodel=medlow)
if (ENABLE_DEBUG_PATH)
message(STATUS "ENABLE_DEBUG_PATH is ON")
else()
message(STATUS "ENABLE_DEBUG_PATH is OFF")
# 使用-ffile-prefix-map来减少调试信息中的路径长度从而固定SRAM占用
get_filename_component(ROOT_PROJECT_DIR ${CMAKE_CURRENT_LIST_DIR}/../.. ABSOLUTE)
add_compile_options("-ffile-prefix-map=${ROOT_PROJECT_DIR}=.")
# 移除符号表中的build目录前缀
get_filename_component(BUILD_DIR ${CMAKE_BINARY_DIR} ABSOLUTE)
add_compile_options("-ffile-prefix-map=${BUILD_DIR}=")
# 同时也处理工具链目录的路径
if(DEFINED ENV{NUCLEI_TOOLCHAIN_PATH})
add_compile_options("-ffile-prefix-map=$ENV{NUCLEI_TOOLCHAIN_PATH}=/toolchain")
endif()
endif()

View File

@@ -0,0 +1,19 @@
if (CONFIG_LINK_OPTION_GC_SECTIONS)
add_link_options(-Wl,--gc-sections)
endif()
if (CONFIG_PRINT_MEMORY_USAGE)
add_link_options(-Wl,--print-memory-usage)
endif()
if (CONFIG_LINK_OPTION_NOSYS_SPECS)
add_link_options(--specs=nosys.specs)
endif()
if (CONFIG_LINK_OPTION_NANO_SPECS)
add_link_options(--specs=nano.specs)
endif()
add_link_options(-Wl,--no-warn-rwx-segments)
add_link_options(-nostartfiles)
add_link_options(-static)

View File

@@ -0,0 +1,271 @@
# ListenAI Cmake扩展说明文档
### listenai_library_named
简介: 定义一个名称为`_name`的静态库
参数:
+ _name 指定该静态库的名称
### listenai_library_sources
简介: 为当前的目标添加源文件
提示: 必须在使用`listenai_library_named`之后使用
参数:
+ source 源文件
+ ${ARGN} 可不參數
使用示例:
```
listenai_library_sources(test.c test1.c test2.c test3.c)
```
### listenai_library_sources_ifdef
简介: 当配置有效时,为当前的目标添加源文件
提示: 必须在使用`listenai_library_named`之后使用
参数:
+ feature_toggle 配置选项
+ ${ARGN} 可不參數
使用示例:
当`CONFIG_TEST`有效时,将添加源文件
```
listenai_library_sources_ifdef(CONFIG_TEST test.c test1.c test2.c test3.c)
```
### listenai_library_compile_options
简介: 为当前的目标提添加编译选项
提示: 必须在使用`listenai_library_named`之后使用
参数:
+ scope 作用范围, 可选择:PRIVATE, PUBLIC
+ option 编译选项
+ ${ARGN} 可不參數
### listenai_library_compile_options_ifdef
简介: 当配置项有效时, 为当前的目标提添加编译选项
提示: 必须在使用`listenai_library_named`之后使用
参数:
+ feature_toggle 配置项
+ scope 作用范围, 可选择:PRIVATE, PUBLIC
+ option 编译选项
+ ${ARGN} 可不參數
### listenai_append_cmake_library
简介: 将指定的目标添加到`LISTENAI_LIBS`属性中
参数:
+ library 待添加的目标
### listenai_add_subdirectory_ifdef
简介: 当配置有效时, 添加子目录
参数:
+ feature_toggle 配置项
+ dir 子目录
### listenai_target_sources_ifdef
简介: 当配置有效时, 为指定的目标添加源文件
参数:
+ feature_toggle 配置项
+ target 指定的目标
+ scope 作用范围,PRIVATE PUBLIC INTERFACE
+ item 源文件
+ ${ARGN} 可不參數
### listenai_target_compile_definitions_ifdef
简介: 当配置有效时, 为指定的目标添添加宏定义
参数:
+ feature_toggle 配置项
+ target 指定的目标
+ scope 作用范围 PRIVATE, PUBLIC, INTERFACE
+ item 宏定义
+ ${ARGV} 可变参数
### listenai_target_include_directories_ifdef
简介: 当配置有效时, 为指定的目标添加头文件路径
参数:
+ feature_toggle 配置项
+ target 指定的目标
+ scope 作用范围 PRIVATE, PUBLIC, INTERFACE
+ item 待被链接的目标
+ ${ARGV} 可变参数
### listenai_target_link_libraries_ifdef
简介: 当配置有效时, 为指定的目标连接其他目标
参数:
+ feature_toggle 配置项
+ target 指定的目标
+ item 待被链接的目标
+ ${ARGV} 可变参数
### listenai_add_compile_option_ifdef
简介: 当配置有效时, 添加全局的编译选项
参数:
+ feature_toggle 配置项
+ option 编译选项
+ ${ARGV} 可变参数
### listenai_target_compile_option_ifdef
简介: 当配置有效时, 为指定的目标添加编译选项
参数:
+ feature_toggle 配置项
+ target 指定的目标
+ scope 作用范围
+ option 编译选项
+ ${ARGV} 可变参数
### listenai_compile_options
简介: 为listenai_interface添加编译选项
参数:
+ ${ARGV} 可变参数
### listenai_compile_definitions
简介: 为listenai_interface添加宏定义
参数:
+ ${ARGV} 可变参数
### listenai_compile_definitions_ifdef
简介: 当配置有效时, 为listenai_interface添加宏定义
参数:
+ feature_toggle 配置项
+ ${ARGV} 可变参数
### listenai_link_libraries
简介: 将目标链接到listenai_interface中
参数:
+ item 被连接的目标
### listenai_include_directories
简介: 将头文件加入listenai_interface中
参数:
+ target_name 目标
### listenai_generate_bin
简介: 为目标生成bin文件
参数:
+ target_name 目标
### listenai_generate_hex
简介: 为目标生成hex文件
参数:
+ target_name 目标
### listenai_generate_lst
简介: 为目标生成lst文件
参数:
+ target_name 目标
### listenai_link_all_modules
简介: 将所有通过listenai cmake扩展定义的目标连接到指定的目标中
参数:
+ target_name 将要链接到的目标
注意: 此函数通常不需要手动调用,会在 `listenai_add_executable` 中自动延迟调用
### listenai_add_executable
添加可执行文件
此宏会自动为可执行文件添加bin,hex,lst文件输出
此宏会自动扫描LISTENAI_MODULES_DIR_LIST 列表中的目录, 并添加模块到构建系统中
**重要特性:**
- 使用延迟链接机制(需要 CMake 3.19+
- 用户可以在 `listenai_add_executable` 之前或之后调用 `add_subdirectory` 添加库
- 所有库会被自动包含在 `--whole-archive` 链接选项中,确保未被引用的符号不被丢弃
**CMake 版本要求:**
- 最低版本: CMake 3.19
- 低于此版本会报 FATAL_ERROR
### listenai_executable_set_link_file
为可执行文件设置链接脚本
### listenai_generate_boot_header
为可执行文件添加listenai boot header

0
arcs-sdk/cmake/empty.c Normal file
View File

View File

@@ -0,0 +1,415 @@
add_library(listenai_interface INTERFACE "")
# 简介: 定义一个名称为`_name`的静态库
# 参数:
# + _name 指定该静态库的名称
macro(listenai_library_named _name)
add_library(${_name} STATIC "")
set(LISTENAI_CURRENT_LIBRARY ${_name})
listenai_append_cmake_library(${_name})
target_link_libraries(${_name} PUBLIC listenai_interface)
endmacro()
macro(listenai_psram_library_named _name)
listenai_library_named(psram_${_name})
endmacro()
# 简介: 为当前的目标添加源文件
# 提示: 必须在使用`listenai_library_named`之后使用
# 参数:
# + source 源文件
# + ${ARGN} 可不參數
#
# 使用示例:
# ```
# listenai_library_sources(test.c test1.c test2.c test3.c)
# ```
function(listenai_library_sources source)
target_sources(${LISTENAI_CURRENT_LIBRARY} PRIVATE ${source} ${ARGN})
endfunction()
# 简介: 当配置有效时,为当前的目标添加源文件
# 提示: 必须在使用`listenai_library_named`之后使用
# 参数:
# + feature_toggle 配置选项
# + ${ARGN} 可不參數
#
# 使用示例:
# 当`CONFIG_TEST`有效时,将添加源文件
# ```
# listenai_library_sources_ifdef(CONFIG_TEST test.c test1.c test2.c test3.c)
# ```
#
function(listenai_library_sources_ifdef feature_toggle)
if(${${feature_toggle}})
listenai_library_sources(${ARGN})
endif()
endfunction()
# 简介: 为当前的目标提添加编译选项
# 提示: 必须在使用`listenai_library_named`之后使用
# 参数:
# + scope 作用范围, 可选择:PRIVATE, PUBLIC
# + option 编译选项
# + ${ARGN} 可不參數
function(listenai_library_compile_options scope option)
target_compile_options(${LISTENAI_CURRENT_LIBRARY} ${scope} ${option} ${ARGN})
endfunction()
# 简介: 当配置项有效时, 为当前的目标提添加编译选项
# 提示: 必须在使用`listenai_library_named`之后使用
# 参数:
# + feature_toggle 配置项
# + scope 作用范围, 可选择:PRIVATE, PUBLIC
# + option 编译选项
# + ${ARGN} 可不參數
function(listenai_library_compile_options_ifdef feature_toggle scope option)
if(${${feature_toggle}})
target_compile_options(${LISTENAI_CURRENT_LIBRARY} ${scope} ${option} ${ARGN})
endif()
endfunction()
# 简介: 将指定的目标添加到`LISTENAI_LIBS`属性中
# 参数:
# + library 待添加的目标
function(listenai_append_cmake_library library)
set_property(GLOBAL APPEND PROPERTY LISTENAI_LIBS ${library})
endfunction()
# 简介: 当配置有效时, 添加子目录
# 参数:
# + feature_toggle 配置项
# + dir 子目录
function(listenai_add_subdirectory_ifdef feature_toggle dir)
if(${${feature_toggle}})
add_subdirectory(${dir})
endif()
endfunction()
# 简介: 当配置有效时, 为指定的目标添加源文件
# 参数:
# + feature_toggle 配置项
# + target 指定的目标
# + scope 作用范围,PRIVATE PUBLIC INTERFACE
# + item 源文件
# + ${ARGN} 可不參數
function(listenai_target_sources_ifdef feature_toggle target scope item)
if(${${feature_toggle}})
target_sources(${target} ${scope} ${item} ${ARGN})
endif()
endfunction()
# 简介: 当配置有效时, 为指定的目标添添加宏定义
# 参数:
# + feature_toggle 配置项
# + target 指定的目标
# + scope 作用范围 PRIVATE, PUBLIC, INTERFACE
# + item 宏定义
# + ${ARGV} 可变参数
function(listenai_target_compile_definitions_ifdef feature_toggle target scope item)
if(${${feature_toggle}})
target_compile_definitions(${target} ${scope} ${item} ${ARGN})
endif()
endfunction()
# 简介: 当配置有效时, 为指定的目标添加头文件路径
# 参数:
# + feature_toggle 配置项
# + target 指定的目标
# + scope 作用范围 PRIVATE, PUBLIC, INTERFACE
# + item 待被链接的目标
# + ${ARGV} 可变参数
function(listenai_target_include_directories_ifdef feature_toggle target scope item)
if(${${feature_toggle}})
target_include_directories(${target} ${scope} ${item} ${ARGN})
endif()
endfunction()
# 简介: 当配置有效时, 为指定的目标连接其他目标
# 参数:
# + feature_toggle 配置项
# + target 指定的目标
# + item 待被链接的目标
# + ${ARGV} 可变参数
function(listenai_target_link_libraries_ifdef feature_toggle target item)
if(${${feature_toggle}})
target_link_libraries(${target} ${item} ${ARGN})
endif()
endfunction()
# 简介: 当配置有效时, 添加全局的编译选项
# 参数:
# + feature_toggle 配置项
# + option 编译选项
# + ${ARGV} 可变参数
function(listenai_add_compile_option_ifdef feature_toggle option)
if(${${feature_toggle}})
add_compile_options(${option})
endif()
endfunction()
# 简介: 当配置有效时, 为指定的目标添加编译选项
# 参数:
# + feature_toggle 配置项
# + target 指定的目标
# + scope 作用范围
# + option 编译选项
# + ${ARGV} 可变参数
function(listenai_target_compile_option_ifdef feature_toggle target scope option)
if(${feature_toggle})
target_compile_options(${target} ${scope} ${option} ${ARGV})
endif()
endfunction()
# 简介: 为listenai_interface添加编译选项
# 参数:
# + ${ARGV} 可变参数
function(listenai_compile_options)
target_compile_options(listenai_interface INTERFACE ${ARGV})
endfunction()
# 简介: 为listenai_interface添加宏定义
# 参数:
# + ${ARGV} 可变参数
function(listenai_compile_definitions)
target_compile_definitions(listenai_interface INTERFACE ${ARGV})
endfunction()
# 简介: 当配置有效时, 为listenai_interface添加宏定义
# 参数:
# + feature_toggle 配置项
# + ${ARGV} 可变参数
function(listenai_compile_definitions_ifdef feature_toggle)
if(${${feature_toggle}})
listenai_compile_definitions(${ARGN})
endif()
endfunction()
# 简介: 将目标链接到listenai_interface中
# 参数:
# + item 被连接的目标
function(listenai_link_libraries item)
target_link_libraries(listenai_interface INTERFACE ${item} ${ARGV})
foreach(arg ${ARGV})
set_property(GLOBAL APPEND PROPERTY LISTENAI_LIBS ${arg})
endforeach()
endfunction()
# 简介: 将头文件加入listenai_interface中
# 参数:
# + target_name 目标
function(listenai_include_directories)
foreach(arg ${ARGV})
if(IS_ABSOLUTE ${arg})
set(path ${arg})
else()
set(path ${CMAKE_CURRENT_SOURCE_DIR}/${arg})
endif()
target_include_directories(listenai_interface INTERFACE ${path})
endforeach()
endfunction()
# 简介: 为目标生成bin文件
# 参数:
# + target_name 目标
macro(listenai_generate_bin target_name)
add_custom_command(
TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "-- Genarating file: ${target_name}.bin"
COMMAND ${CMAKE_OBJCOPY} -S -O binary ${target_name} ${target_name}.bin
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
endmacro()
# 简介: 为目标生成hex文件
# 参数:
# + target_name 目标
macro(listenai_generate_hex target_name)
add_custom_command(
TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "-- Genarating file: ${target_name}.hex"
COMMAND ${CMAKE_OBJCOPY} -S -O ihex
${target_name}
${target_name}.hex
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
endmacro()
# 简介: 为目标生成调试信息包括反汇编、ELF头、符号表等
# 参数:
# + target_name 目标
macro(listenai_generate_debug_files target_name)
add_custom_command(
TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "-- Genarating file: ${target_name}.lst"
COMMAND ${CMAKE_OBJDUMP} -d -S ${target_name} > ${target_name}.lst
COMMAND ${CMAKE_READELF} -a ${target_name} > ${target_name}.relf
COMMAND ${CMAKE_NM} -CSsnl -f sysv ${target_name} > ${target_name}.symb
COMMAND ${CMAKE_SIZE} -B ${target_name}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
endmacro()
# 简介: 将所有通过listenai cmake扩展定义的目标连接到指定的目标中
# 参数:
# + target_name 将要链接到的目标
macro(listenai_link_all_modules target_name)
get_property(LISTENAI_LIBS_PROPERTY GLOBAL PROPERTY LISTENAI_LIBS)
list(REMOVE_DUPLICATES LISTENAI_LIBS_PROPERTY)
# 获取目标已手动链接的库,避免重复链接
get_target_property(_existing_libs ${target_name} LINK_LIBRARIES)
if(_existing_libs)
list(REMOVE_ITEM LISTENAI_LIBS_PROPERTY ${_existing_libs})
endif()
if (LISTENAI_LIBS_PROPERTY)
# 硬编码排除libm.a以避免符号冲突
# 2025.02版本工具链lib.m存在两个frexpl实现经芯来原厂沟通两个实现都可用
set(EXCLUDE_LIB_NAME "m")
set(WHOLE_ARCHIVE_LIBS)
set(NORMAL_LIBS)
# 分离需要全量链接的库和普通库
foreach(lib ${LISTENAI_LIBS_PROPERTY})
set(is_excluded FALSE)
# 检查库名是否匹配排除的库
if(lib MATCHES "lib${EXCLUDE_LIB_NAME}\\.(a|so)$" OR lib STREQUAL "${EXCLUDE_LIB_NAME}")
set(is_excluded TRUE)
endif()
if(is_excluded)
list(APPEND NORMAL_LIBS ${lib})
else()
list(APPEND WHOLE_ARCHIVE_LIBS ${lib})
endif()
endforeach()
set(LINK_START_CMD)
set(LINK_END_CMD)
if (DEFINED CONFIG_LINK_OPTION_LISTENAI_LIBRARY_GROUP)
list(APPEND LINK_START_CMD "-Wl,--start-group")
list(APPEND LINK_END_CMD "-Wl,--end-group")
endif()
# 根据whole-archive配置选择链接方式
if (DEFINED CONFIG_LINK_OPTION_LISTENAI_LIBRARY_WHOLE_ARCHIVE)
# 开启了whole-archive分别链接全量库和普通库
if (WHOLE_ARCHIVE_LIBS)
target_link_libraries(${target_name} PRIVATE
${LINK_START_CMD}
"-Wl,--whole-archive"
${WHOLE_ARCHIVE_LIBS}
"-Wl,--no-whole-archive"
${LINK_END_CMD})
endif()
if(NORMAL_LIBS)
target_link_libraries(${target_name} PRIVATE ${NORMAL_LIBS})
endif()
else()
# 没有开启whole-archive使用原来的逻辑
target_link_libraries(${target_name} PRIVATE ${LINK_START_CMD} ${LISTENAI_LIBS_PROPERTY} ${LINK_END_CMD})
endif()
endif()
endmacro()
# 添加可执行文件
# 此宏会自动为可执行文件添加bin,hex,lst文件输出
# 此宏会自动扫描LISTENAI_MODULES_DIR_LIST 列表中的目录, 并添加模块到构建系统中
macro(listenai_add_executable name)
set(LISTENAI_EXECUTABLE_NAME ${name})
add_executable(${name})
target_sources(${name} PRIVATE ${LISTENAI_CMAKE_PATH}/empty.c)
listenai_generate_bin(${name})
listenai_generate_hex(${name})
if(CONFIG_COMPILE_OPTION_GENERATE_DEBUG_FILES)
listenai_generate_debug_files(${name})
endif()
if(LISTENAI_ADD_BIN_HEADR)
listenai_generate_boot_header(${name})
endif()
# 立即扫描并添加模块,但延迟链接操作
# 这样可以确保所有 add_subdirectory 执行完后再链接,不受调用顺序影响
get_property(LISTENAI_MODULES_PROPERTY GLOBAL PROPERTY LISTENAI_MODULES)
foreach(module IN LISTS LISTENAI_MODULES_PROPERTY)
message(STATUS "Found module: ${module} ")
add_subdirectory(${module} ${CMAKE_BINARY_DIR}/modules/${module})
endforeach()
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.19")
# 延迟链接到配置阶段末尾,确保用户的 add_subdirectory 也执行完成
cmake_language(DEFER CALL listenai_link_all_modules ${name})
else()
# CMake 版本过低,无法使用 DEFER 机制
message(FATAL_ERROR "CMake 3.19 or higher is required, but current version is ${CMAKE_VERSION}")
endif()
set(LISTENAI_CURRENT_LIBRARY "_inner_app")
set(LISTENAI_EXECUTABLE_COMPLETED TRUE)
endmacro()
# 为可执行文件添加listenai boot header
macro(listenai_generate_boot_header target_name)
add_custom_target(
mkhdr ALL
COMMAND ${CMAKE_COMMAND} -E echo "-- Genarating ListenAI Boot Header for ${target_name}.bin"
COMMAND ${LISTENAI_TOOLS_MKHDR} ${target_name}.bin
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_dependencies(mkhdr ${target_name})
endmacro()
macro(listenai_set_linker_script linker_script)
get_property(LISTENAI_LINK_SCRIPTS_PROPERTY GLOBAL PROPERTY LISTENAI_LINK_SCRIPTS)
get_property(INCLUDE_DIRS TARGET listenai_interface PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
get_property(LISTENAI_IF_DEFINITIONS TARGET listenai_interface PROPERTY INTERFACE_COMPILE_DEFINITIONS)
set(INCLUDE_FLAGS "")
foreach(dir ${INCLUDE_DIRS})
list(APPEND INCLUDE_FLAGS "-I${dir}")
endforeach()
set(LISTENAI_IF_DEFINITIONS_FLAGS "")
foreach(flag ${LISTENAI_IF_DEFINITIONS})
list(APPEND LISTENAI_IF_DEFINITIONS_FLAGS "-D${flag}")
endforeach()
if(LISTENAI_LINK_SCRIPTS_PROPERTY)
add_custom_target(linker_script_prepare
COMMAND cat ${linker_script} ${LISTENAI_LINK_SCRIPTS_PROPERTY} > ${CMAKE_BINARY_DIR}/linker.ld.pre
DEPENDS ${linker_script} ${LISTENAI_LINK_SCRIPTS_PROPERTY}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
else()
add_custom_target(linker_script_prepare
COMMAND cp ${linker_script} ${CMAKE_BINARY_DIR}/linker.ld.pre
DEPENDS ${linker_script}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
endif()
add_custom_target(generate_linker_script
COMMAND ${CMAKE_C_COMPILER}
-E
-P
-x assembler-with-cpp
${INCLUDE_FLAGS}
${LISTENAI_IF_DEFINITIONS_FLAGS}
-include autoconf.h
${CMAKE_BINARY_DIR}/linker.ld.pre
-o ${CMAKE_BINARY_DIR}/linker.ld
DEPENDS linker_script_prepare
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_dependencies(${LISTENAI_EXECUTABLE_NAME} generate_linker_script)
target_link_options(${LISTENAI_EXECUTABLE_NAME} PRIVATE "-T${CMAKE_BINARY_DIR}/linker.ld")
endmacro()
macro(listenai_append_linker_script linker_script)
set_property(GLOBAL APPEND PROPERTY LISTENAI_LINK_SCRIPTS ${linker_script})
endmacro()

View File

@@ -0,0 +1,26 @@
# SPDX-License-Identifier: Apache-2.0
# ARCS SDK 版本头文件生成脚本
# 此脚本由主 CMakeLists.txt 通过 execute_process 调用
# 尝试获取 git 提交哈希
find_package(Git QUIET)
if(GIT_FOUND AND EXISTS ${ARCS_SDK_BASE}/.git)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --abbrev=12 --always
WORKING_DIRECTORY ${ARCS_SDK_BASE}
OUTPUT_VARIABLE BUILD_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
endif()
# 如果未获取到 git 信息,设置为默认值
if(NOT BUILD_VERSION)
set(BUILD_VERSION "unknown")
endif()
# 从模板生成头文件
configure_file(${ARCS_SDK_BASE}/sdk_version.h.in ${OUT_FILE} @ONLY)
message(STATUS "生成版本头文件: ${OUT_FILE}")
message(STATUS " BUILD_VERSION: ${BUILD_VERSION}")

56
arcs-sdk/cmake/hex.cmake Normal file
View File

@@ -0,0 +1,56 @@
# SPDX-License-Identifier: Apache-2.0
# from https://gist.github.com/korzo89/71a6de0f388f7cf8b349101b0134060c
function(from_hex HEX DEC)
string(SUBSTRING "${HEX}" 2 -1 HEX)
string(TOUPPER "${HEX}" HEX)
set(_res 0)
string(LENGTH "${HEX}" _strlen)
while(_strlen GREATER 0)
math(EXPR _res "${_res} * 16")
string(SUBSTRING "${HEX}" 0 1 NIBBLE)
string(SUBSTRING "${HEX}" 1 -1 HEX)
if(NIBBLE STREQUAL "A")
math(EXPR _res "${_res} + 10")
elseif(NIBBLE STREQUAL "B")
math(EXPR _res "${_res} + 11")
elseif(NIBBLE STREQUAL "C")
math(EXPR _res "${_res} + 12")
elseif(NIBBLE STREQUAL "D")
math(EXPR _res "${_res} + 13")
elseif(NIBBLE STREQUAL "E")
math(EXPR _res "${_res} + 14")
elseif(NIBBLE STREQUAL "F")
math(EXPR _res "${_res} + 15")
else()
math(EXPR _res "${_res} + ${NIBBLE}")
endif()
string(LENGTH "${HEX}" _strlen)
endwhile()
set(${DEC} ${_res} PARENT_SCOPE)
endfunction()
function(to_hex DEC HEX)
while(DEC GREATER 0)
math(EXPR _val "${DEC} % 16")
math(EXPR DEC "${DEC} / 16")
if(_val EQUAL 10)
set(_val "A")
elseif(_val EQUAL 11)
set(_val "B")
elseif(_val EQUAL 12)
set(_val "C")
elseif(_val EQUAL 13)
set(_val "D")
elseif(_val EQUAL 14)
set(_val "E")
elseif(_val EQUAL 15)
set(_val "F")
endif()
set(_res "${_val}${_res}")
endwhile()
set(${HEX} "0x${_res}" PARENT_SCOPE)
endfunction()

View File

@@ -0,0 +1,252 @@
macro(import_kconfig prefix kconfig_fragment)
cmake_parse_arguments(IMPORT_KCONFIG "" "TARGET" "" ${ARGN})
file(
STRINGS
${kconfig_fragment}
DOT_CONFIG_LIST
REGEX "^${prefix}"
ENCODING "UTF-8"
)
foreach (CONFIG ${DOT_CONFIG_LIST})
# maybe prefix is empty string
if(CONFIG MATCHES "^#")
continue()
endif()
# CONFIG could look like: CONFIG_NET_BUF=y
# Match the first part, the variable name
string(REGEX MATCH "[^=]+" CONF_VARIABLE_NAME ${CONFIG})
# Match the second part, variable value
string(REGEX MATCH "=(.+$)" CONF_VARIABLE_VALUE ${CONFIG})
# The variable name match we just did included the '=' symbol. To just get the
# part on the RHS we use match group 1
set(CONF_VARIABLE_VALUE ${CMAKE_MATCH_1})
if("${CONF_VARIABLE_VALUE}" MATCHES "^\"(.*)\"$") # Is surrounded by quotes
set(CONF_VARIABLE_VALUE ${CMAKE_MATCH_1})
endif()
set(${CONF_VARIABLE_NAME} ${CONF_VARIABLE_VALUE})
endforeach()
endmacro()
if (NOT DEFINED APPLICATION_SOURCE_DIR)
set(APPLICATION_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE PATH
"Application Source Directory"
)
endif()
option(LISTENAI_SDK_KCONFIG_PARSE "kconfig parse" ON)
set(LISTENAI_MODULES_KCONFIG_FILE ${CMAKE_BINARY_DIR}/module.Kconfig)
if (EXISTS ${LISTENAI_MODULES_KCONFIG_FILE})
file(REMOVE ${LISTENAI_MODULES_KCONFIG_FILE})
endif()
get_property(LISTENAI_MODULES_PROPERTY GLOBAL PROPERTY LISTENAI_MODULES)
set(ENV{LISTENAI_MODULES} ${CMAKE_BINARY_DIR}/module.Kconfig)
file(APPEND ${LISTENAI_MODULES_KCONFIG_FILE} "menu \"modules\"\n")
file(APPEND ${LISTENAI_MODULES_KCONFIG_FILE} "osource \"${LISTENAI_CMAKE_PATH}/Kconfig\"\n")
foreach(module IN LISTS LISTENAI_MODULES_PROPERTY)
list(FIND LISTENAI_KCONFIG_CUSTOM_PARSE_DIR_LIST ${module} idx)
if (idx EQUAL -1)
if (EXISTS ${module}/Kconfig)
file(APPEND ${LISTENAI_MODULES_KCONFIG_FILE} "osource \"${module}/Kconfig\"\n")
endif()
endif()
endforeach()
file(APPEND ${LISTENAI_MODULES_KCONFIG_FILE} "endmenu\n")
macro(listenai_kconfig_parse kconfig_root dot_config autoconf_h kconfig_list conf_merge prefix)
message(STATUS "Parse kconfig: ${${kconfig_root}}, prefix: ${${prefix}}")
set(ENV{CONFIG_} ${${prefix}})
execute_process(
COMMAND
${LISTENAI_TOOLS_KCONFIG}
-k ${${kconfig_root}}
-c ${${dot_config}}
-H ${${autoconf_h}}
-l ${${kconfig_list}}
-m ${${conf_merge}}
WORKING_DIRECTORY ${APPLICATION_SOURCE_DIR}
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
message(FATAL_ERROR "Kconfig parse failed, error code: ${result}")
endif()
import_kconfig("${${prefix}}" ${${dot_config}})
add_compile_options(-imacros${${autoconf_h}})
endmacro()
if (LISTENAI_SDK_KCONFIG_PARSE)
if (NOT EXISTS ${APPLICATION_SOURCE_DIR}/Kconfig)
set(KCONFIG_ROOT ${LISTENAI_SDK_BASE}/Kconfig)
else()
set(KCONFIG_ROOT ${APPLICATION_SOURCE_DIR}/Kconfig)
endif()
if (NOT DEFINED DOT_CONFIG)
set(DOT_CONFIG ${CMAKE_BINARY_DIR}/.config)
endif()
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated/include)
if (NOT DEFINED AUTOCONF_H)
set(AUTOCONF_H ${CMAKE_BINARY_DIR}/generated/include/autoconf.h)
endif()
if (NOT DEFINED KCONFIG_MERGE_LIST)
set(KCONFIG_MERGE_LIST "")
endif()
# 首先处理命令行CONFIG_*参数生成extra_kconfig_options.conf
unset(EXTRA_KCONFIG_OPTIONS)
get_cmake_property(cache_variable_names CACHE_VARIABLES)
foreach (name ${cache_variable_names})
if("${name}" MATCHES "^CONFIG_")
# When a cache variable starts with 'CONFIG_', it is assumed to be
# a Kconfig symbol assignment from the CMake command line.
set(EXTRA_KCONFIG_OPTIONS
"${EXTRA_KCONFIG_OPTIONS}\n${name}=${${name}}"
)
endif()
endforeach()
if(EXTRA_KCONFIG_OPTIONS)
set(EXTRA_KCONFIG_OPTIONS_FILE ${CMAKE_BINARY_DIR}/misc/generated/extra_kconfig_options.conf)
# 确保目录存在
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/misc/generated)
file(WRITE
${EXTRA_KCONFIG_OPTIONS_FILE}
${EXTRA_KCONFIG_OPTIONS}
)
endif()
if (NOT DEFINED KCONFIG_LIST)
set(KCONFIG_LIST ${CMAKE_BINARY_DIR}/kconfig.list)
endif()
if (EXISTS ${DOT_CONFIG})
list(APPEND KCONFIG_MERGE_LIST ${DOT_CONFIG})
endif()
if (NOT DEFINED CONFIG_DEFAULT)
set(CONFIG_DEFAULT ${APPLICATION_SOURCE_DIR}/prj.conf)
endif()
if (NOT IS_ABSOLUTE ${CONFIG_DEFAULT})
set(CONFIG_DEFAULT ${APPLICATION_SOURCE_DIR}/${CONFIG_DEFAULT})
endif()
if (EXISTS ${CONFIG_DEFAULT})
list(APPEND KCONFIG_MERGE_LIST ${CONFIG_DEFAULT})
endif()
if (EXISTS ${APPLICATION_SOURCE_DIR}/.config)
list(APPEND KCONFIG_MERGE_LIST ${APPLICATION_SOURCE_DIR}/.config)
endif()
if (EXISTS ${BOARD_DIR}/overlay.conf)
list(APPEND KCONFIG_MERGE_LIST ${BOARD_DIR}/overlay.conf)
message(STATUS "Added board overlay config: ${BOARD_DIR}/overlay.conf")
endif()
if (DEFINED CONFIG_FILES)
foreach(config_file IN LISTS CONFIG_FILES)
if (IS_ABSOLUTE ${config_file})
list(APPEND KCONFIG_MERGE_LIST ${config_file})
else()
list(APPEND KCONFIG_MERGE_LIST ${APPLICATION_SOURCE_DIR}/${config_file})
endif()
endforeach()
endif()
# 添加命令行CONFIG_*参数文件到合并列表的最后(最高优先级)
if(EXTRA_KCONFIG_OPTIONS_FILE AND EXISTS ${EXTRA_KCONFIG_OPTIONS_FILE})
list(APPEND KCONFIG_MERGE_LIST ${EXTRA_KCONFIG_OPTIONS_FILE})
message(STATUS "Added command-line CONFIG options file: ${EXTRA_KCONFIG_OPTIONS_FILE}")
endif()
string(JOIN " " KCONFIG_MERGE_LIST_STRING "${KCONFIG_MERGE_LIST}")
message(STATUS "Kconfig merge list: ${KCONFIG_MERGE_LIST_STRING}")
if (NOT DEFINED LISTENAI_KCONFIG_PREFIX)
set(LISTENAI_KCONFIG_PREFIX "CONFIG_")
endif()
# parse main kconfig
listenai_kconfig_parse(
KCONFIG_ROOT
DOT_CONFIG
AUTOCONF_H
KCONFIG_LIST
KCONFIG_MERGE_LIST_STRING
LISTENAI_KCONFIG_PREFIX
)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/kconfig/custom)
message(STATUS "Custom kconfig parse list: ${LISTENAI_KCONFIG_CUSTOM_PARSE_DIR_LIST}")
list(LENGTH LISTENAI_KCONFIG_CUSTOM_PARSE_PREFIX_LIST prefix_list_len)
foreach(item IN LISTS LISTENAI_KCONFIG_CUSTOM_PARSE_DIR_LIST)
get_filename_component(ITEM_DIRECTORY_NAME ${item} NAME)
string(TOLOWER ${ITEM_DIRECTORY_NAME} ITEM_DIRECTORY_NAME)
set(ITEM_KCONFIG_ROOT ${item}/Kconfig)
set(ITEM_DOTCONFIG ${CMAKE_BINARY_DIR}/kconfig/custom/${ITEM_DIRECTORY_NAME}.config)
set(ITEM_AUTOCONF_H ${CMAKE_BINARY_DIR}/generated/include/${ITEM_DIRECTORY_NAME}_autoconf.h)
set(ITEM_KCONFIG_LIST ${CMAKE_BINARY_DIR}/kconfig/custom/${ITEM_DIRECTORY_NAME}_kconfig.list)
list(FIND LISTENAI_KCONFIG_CUSTOM_PARSE_DIR_LIST ${item} idx)
if (${prefix_list_len} GREATER 0)
if (${idx} LESS ${prefix_list_len})
list(GET LISTENAI_KCONFIG_CUSTOM_PARSE_PREFIX_LIST ${idx} ITEM_KCONFIG_PREFIX)
else()
math(EXPR idx "${prefix_list_len} - 1")
list(GET LISTENAI_KCONFIG_CUSTOM_PARSE_PREFIX_LIST ${idx} ITEM_KCONFIG_PREFIX)
endif()
else()
set(ITEM_KCONFIG_PREFIX "CONFIG_")
endif()
if (${ITEM_KCONFIG_PREFIX} STREQUAL "\"\"")
set(ITEM_KCONFIG_PREFIX "")
endif()
listenai_kconfig_parse(
ITEM_KCONFIG_ROOT
ITEM_DOTCONFIG
ITEM_AUTOCONF_H
ITEM_KCONFIG_LIST
KCONFIG_MERGE_LIST_STRING
ITEM_KCONFIG_PREFIX
)
endforeach()
endif()
add_custom_target(menuconfig
COMMAND ${CMAKE_COMMAND} -E rm -rf ${CMAKE_BINARY_DIR}/.config.old
COMMAND ${CMAKE_COMMAND} -E env
LISTENAI_MODULES=${CMAKE_BINARY_DIR}/module.Kconfig
KCONFIG_CONFIG=${CMAKE_BINARY_DIR}/.config
${LISTENAI_TOOLS_MENUCONFIG}
WORKING_DIRECTORY ${APPLICATION_SOURCE_DIR}
COMMENT "running menuconfig"
USES_TERMINAL
)
# 命令行CONFIG_*参数处理已移动到kconfig_setup函数内部确保在合并列表构建时可用

View File

@@ -0,0 +1,145 @@
set(LISTENAI_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR})
if (NOT DEFINED LISTENAI_TOOLS_PATH)
if (NOT DEFINED ENV{LISTENAI_TOOLS_PATH})
message(FATAL_ERROR "LISTENAI_TOOLS_PATH is not defined")
else()
set(LISTENAI_TOOLS_PATH $ENV{LISTENAI_TOOLS_PATH})
endif()
endif()
if (NOT DEFINED ARCS_SDK_BASE)
if (NOT DEFINED ENV{ARCS_BASE})
message(FATAL_ERROR "ARCS_BASE is not defined")
else()
set(ARCS_SDK_BASE $ENV{ARCS_BASE})
endif()
endif()
string(REPLACE "\\" "/" LISTENAI_TOOLS_PATH "${LISTENAI_TOOLS_PATH}")
set(LISTENAI_TOOLS_MKHDR ${LISTENAI_TOOLS_PATH}/mkhdr/mkhdr)
set(LISTENAI_TOOLS_KCONFIG ${LISTENAI_TOOLS_PATH}/kconfig/kconfig)
set(LISTENAI_TOOLS_MENUCONFIG ${LISTENAI_TOOLS_PATH}/menuconfig/menuconfig)
option(LISTENAI_ADD_BIN_HEADR "kconfig no generate header of bin " ON)
string(REPLACE "\\" "/" LISTENAI_CMAKE_PATH "${LISTENAI_CMAKE_PATH}")
if (NOT DEFINED LISTENAI_MODULES_DIR_LIST)
message(STATUS "LISTENAI_MODULES_DIR_LIST is not defined")
set(LISTENAI_MODULES_DIR_LIST "")
endif()
if(NOT LISTENAI_MODULES_DIR_LIST MATCHES "${ARCS_SDK_BASE}")
list(APPEND LISTENAI_MODULES_DIR_LIST "${ARCS_SDK_BASE}")
endif()
message(STATUS "LISTENAI_MODULES_DIR_LIST: ${LISTENAI_MODULES_DIR_LIST}")
function(check_tool tool_var tool_name)
if (NOT DEFINED ${tool_var})
message(FATAL_ERROR "${tool_name} is not defined")
endif()
if (NOT EXISTS ${${tool_var}})
message(FATAL_ERROR "Tool ${${tool_var}} does not exist")
endif()
string(REPLACE "\\" "/" ${tool_var} "${${tool_var}}")
message(STATUS "Found ${tool_name}: ${${tool_var}}")
endfunction()
check_tool(LISTENAI_TOOLS_KCONFIG "ListenAI Kconfig tool")
check_tool(LISTENAI_TOOLS_MKHDR "ListenAI mkhdr tool")
include(${LISTENAI_CMAKE_PATH}/hex.cmake)
# 包含版本管理(在 kconfig 之前,因为 kconfig 可能依赖版本信息)
include(${LISTENAI_CMAKE_PATH}/version.cmake)
message(STATUS "Listenai module dir list: ${LISTENAI_MODULES_DIR_LIST}")
# 从指定的目录中查询模块,并将模块的路径存放到属性LISTENAI_MODULES中
function(listenai_find_modules dir)
set(cmake_lists_path "${dir}/CMakeLists.txt")
get_filename_component(DIRECTORY_NAME ${dir} NAME)
if(EXISTS "${cmake_lists_path}")
get_filename_component(module_abs_path "${dir}" ABSOLUTE)
set_property(GLOBAL APPEND PROPERTY LISTENAI_MODULES ${module_abs_path})
message(STATUS "Found module: ${module_abs_path}")
return()
endif()
file(GLOB CHILD_DIRS LIST_DIRECTORIES TRUE "${dir}/*")
foreach(child_dir ${CHILD_DIRS})
if(IS_DIRECTORY ${child_dir})
listenai_find_modules(${child_dir})
endif()
endforeach()
endfunction()
foreach(dir ${LISTENAI_MODULES_DIR_LIST})
listenai_find_modules(${dir})
endforeach()
# 板型搜索逻辑(在 Kconfig 解析之前)
# 设置 BOARD_KCONFIG_PATH 环境变量,供 boards/Kconfig 动态加载板型配置
set(BOARD_DIR "")
if(DEFINED BOARD)
# 优先级1: 自定义板型路径
if(DEFINED BOARD_SEARCH_PATH)
if(EXISTS "${BOARD_SEARCH_PATH}/${BOARD}")
set(BOARD_DIR "${BOARD_SEARCH_PATH}/${BOARD}")
endif()
endif()
# 优先级2: SDK 内置板型
if(NOT BOARD_DIR)
if(EXISTS "${ARCS_SDK_BASE}/boards/${BOARD}")
set(BOARD_DIR "${ARCS_SDK_BASE}/boards/${BOARD}")
endif()
endif()
endif()
# 设置板型 Kconfig 路径环境变量
# 如果找到板型,设置为板型的 Kconfig 文件路径
# 如果未找到,设置为不存在的路径,让 osource 静默跳过(避免 Kconfig 解析错误)
if(BOARD_DIR AND EXISTS "${BOARD_DIR}/Kconfig")
set(ENV{BOARD_KCONFIG_PATH} "${BOARD_DIR}/Kconfig")
else()
# 设置为一个明确不存在的文件路径,让 osource 静默跳过
set(ENV{BOARD_KCONFIG_PATH} "${ARCS_SDK_BASE}/boards/.kconfig.not.found")
endif()
include(${LISTENAI_CMAKE_PATH}/kconfig.cmake)
include(${LISTENAI_CMAKE_PATH}/extensions.cmake)
if (NOT DEFINED CHIP)
set(CHIP arcs)
endif()
include(${LISTENAI_CMAKE_PATH}/${CHIP}-chip.cmake)
include(${LISTENAI_CMAKE_PATH}/${CHIP}-toolchain.cmake)
include(${LISTENAI_CMAKE_PATH}/common_compile_options.cmake)
include(${LISTENAI_CMAKE_PATH}/common_link_options.cmake)
listenai_include_directories(${CMAKE_BINARY_DIR}/generated/include)
listenai_library_named(_inner_app)
listenai_library_sources(${LISTENAI_CMAKE_PATH}/empty.c)
# 生成 SDK 版本头文件
execute_process(
COMMAND ${CMAKE_COMMAND}
-DARCS_SDK_BASE=${ARCS_SDK_BASE}
-DOUT_FILE=${CMAKE_BINARY_DIR}/generated/include/sdk_version.h
-DSDK_VERSION_MAJOR=${SDK_VERSION_MAJOR}
-DSDK_VERSION_MINOR=${SDK_VERSION_MINOR}
-DSDK_PATCHLEVEL=${SDK_PATCHLEVEL}
-DSDK_VERSION_STRING=${SDK_VERSION_STRING}
-DSDK_VERSION_CODE=${SDK_VERSION_CODE}
-DSDK_VERSION_NUMBER=${SDK_VERSION_NUMBER}
-DSDKVERSION=${SDKVERSION}
-P ${ARCS_SDK_BASE}/cmake/gen_version_h.cmake
WORKING_DIRECTORY ${ARCS_SDK_BASE}
)

View File

@@ -0,0 +1,48 @@
import re
import argparse
def line_startswith_keys(line, keys):
for k in keys:
if line.startswith(k):
pattern = rf'{k}\((\w+)'
match = re.search(pattern, line)
return match.group(1) if match else None
return None
def comments_section_get_by_keys(file_path, keys):
current_comment = []
extensions_dic = {}
with open(file_path, 'r', encoding='UTF-8') as f:
for line in f:
line = line.strip()
if line.startswith('#'):
current_comment.append(line)
else:
name = line_startswith_keys(line, keys)
if name is not None:
extensions_dic[name] = current_comment.copy()
current_comment.clear()
return extensions_dic
def save_comments_to_file(filepath,comments_dic):
with open(filepath, 'w', encoding='utf-8') as f:
f.write(f'# ListenAI Cmake扩展说明文档\n')
for k,v in comments_dic.items():
f.write(f'### {k}\n')
for line in v:
line = line.replace('#','').strip()
f.write(f'{line}\n\n')
def main():
parser = argparse.ArgumentParser(description='Extract comments from CMake files.')
parser.add_argument('input_file', type=str, help='Path to the input CMake file')
parser.add_argument('output_file', type=str, help='Path to the output Markdown file')
args = parser.parse_args()
cmake_keys= ['macro', 'function']
dic = comments_section_get_by_keys(args.input_file, cmake_keys)
save_comments_to_file(args.output_file, dic)
if __name__=='__main__':
main()

View File

@@ -0,0 +1,73 @@
# SPDX-License-Identifier: Apache-2.0
# ARCS SDK 版本管理
# 参考: VERSION_SYSTEM_REFERENCE_CN.md
# 包含十六进制转换工具
include(${ARCS_SDK_BASE}/cmake/hex.cmake)
# 读取 VERSION 文件
file(READ ${ARCS_SDK_BASE}/VERSION ver)
# 使用正则表达式解析每个版本组件
string(REGEX MATCH "VERSION_MAJOR = ([0-9]*)" _ ${ver})
set(PROJECT_VERSION_MAJOR ${CMAKE_MATCH_1})
string(REGEX MATCH "VERSION_MINOR = ([0-9]*)" _ ${ver})
set(PROJECT_VERSION_MINOR ${CMAKE_MATCH_1})
string(REGEX MATCH "PATCHLEVEL = ([0-9]*)" _ ${ver})
set(PROJECT_VERSION_PATCH ${CMAKE_MATCH_1})
string(REGEX MATCH "VERSION_TWEAK = ([0-9]*)" _ ${ver})
set(PROJECT_VERSION_TWEAK ${CMAKE_MATCH_1})
string(REGEX MATCH "EXTRAVERSION = ([a-z0-9]*)" _ ${ver})
set(PROJECT_VERSION_EXTRA ${CMAKE_MATCH_1})
# 设置导出变量(用于 C 头文件)
set(SDK_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(SDK_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(SDK_PATCHLEVEL ${PROJECT_VERSION_PATCH})
# 构建版本字符串(不包含 TWEAK
set(PROJECT_VERSION_WITHOUT_TWEAK ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
# 构建完整版本TWEAK 非零时包含)
if(PROJECT_VERSION_TWEAK AND NOT PROJECT_VERSION_TWEAK EQUAL 0)
set(PROJECT_VERSION ${PROJECT_VERSION_WITHOUT_TWEAK}.${PROJECT_VERSION_TWEAK})
else()
set(PROJECT_VERSION ${PROJECT_VERSION_WITHOUT_TWEAK})
endif()
# 构建版本字符串(用于显示)
# 规则:优先显示 EXTRA其次显示 TWEAK最后显示基础版本
if(PROJECT_VERSION_EXTRA AND NOT PROJECT_VERSION_EXTRA STREQUAL "0")
# 有 EXTRA 后缀:显示 MAJOR.MINOR.PATCH-EXTRA不显示 TWEAK
set(PROJECT_VERSION_STR "${PROJECT_VERSION_WITHOUT_TWEAK}-${PROJECT_VERSION_EXTRA}")
set(SDK_VERSION_STRING "\"${PROJECT_VERSION_WITHOUT_TWEAK}-${PROJECT_VERSION_EXTRA}\"")
elseif(PROJECT_VERSION_TWEAK AND NOT PROJECT_VERSION_TWEAK EQUAL 0)
# 有 TWEAK显示 MAJOR.MINOR.PATCH.TWEAK
set(PROJECT_VERSION_STR "${PROJECT_VERSION}")
set(SDK_VERSION_STRING "\"${PROJECT_VERSION}\"")
else()
# 正式版本:显示 MAJOR.MINOR.PATCH
set(PROJECT_VERSION_STR "${PROJECT_VERSION_WITHOUT_TWEAK}")
set(SDK_VERSION_STRING "\"${PROJECT_VERSION_WITHOUT_TWEAK}\"")
endif()
# 计算数值版本(用于版本比较)
# SDK_VERSION_NUMBER: 24位编码 0xMMNNPP (MAJOR.MINOR.PATCH)
math(EXPR SDK_VERSION_NUMBER_INT "(${PROJECT_VERSION_MAJOR} << 16) + (${PROJECT_VERSION_MINOR} << 8) + (${PROJECT_VERSION_PATCH})")
to_hex(${SDK_VERSION_NUMBER_INT} SDK_VERSION_NUMBER)
# SDKVERSION: 32位编码 0xMMNNPPTT (MAJOR.MINOR.PATCH.TWEAK)
math(EXPR SDKVERSION_INT "(${PROJECT_VERSION_MAJOR} << 24) + (${PROJECT_VERSION_MINOR} << 16) + (${PROJECT_VERSION_PATCH} << 8) + (${PROJECT_VERSION_TWEAK})")
to_hex(${SDKVERSION_INT} SDKVERSION)
# SDK_VERSION_CODE: 与 SDK_VERSION_NUMBER_INT 相同(用于兼容)
set(SDK_VERSION_CODE ${SDK_VERSION_NUMBER_INT})
# 打印版本信息(可以通过 NO_PRINT_VERSION 禁用)
if(NOT NO_PRINT_VERSION)
message(STATUS "arcs_sdk: ${PROJECT_VERSION_STR} (${ARCS_SDK_BASE})")
endif()

View File

@@ -0,0 +1,26 @@
# Components subdirectories
add_subdirectory(acomp)
add_subdirectory(core_spinlock)
add_subdirectory(dirent)
add_subdirectory(exclib)
add_subdirectory(ipclsf)
add_subdirectory(irq_proxy)
add_subdirectory(lis_algo)
add_subdirectory(lite_adc)
add_subdirectory(lite_dac)
add_subdirectory(ringbuf)
add_subdirectory(simple_box)
add_subdirectory(tinyusb-appclass)
add_subdirectory(lisa_evt_pub)
add_subdirectory(lisa_sntp)
add_subdirectory(lisa_shell)
add_subdirectory(sys_heap)
add_subdirectory(lisa_os)
add_subdirectory(lisa_kv)
add_subdirectory(lisa_http)
add_subdirectory(lisa_websocket)
add_subdirectory(lisa_log)
add_subdirectory(lisa_wifi)
add_subdirectory(lisa_bluetooth)
add_subdirectory(work_queue)
add_subdirectory(app_player)

View File

@@ -0,0 +1,31 @@
# Components Configuration
# This file includes all component Kconfig files
menu "component"
rsource "acomp/Kconfig"
rsource "core_spinlock/Kconfig"
rsource "dirent/Kconfig"
rsource "exclib/Kconfig"
rsource "ipclsf/Kconfig"
rsource "irq_proxy/Kconfig"
rsource "lis_algo/Kconfig"
rsource "lite_adc/Kconfig"
rsource "lite_dac/Kconfig"
rsource "ringbuf/Kconfig"
rsource "simple_box/Kconfig"
rsource "tinyusb-appclass/Kconfig"
rsource "lisa_sntp/Kconfig"
rsource "lisa_evt_pub/Kconfig"
rsource "lisa_shell/Kconfig"
rsource "sys_heap/Kconfig"
rsource "lisa_os/Kconfig"
rsource "lisa_http/Kconfig"
rsource "lisa_websocket/Kconfig"
rsource "lisa_kv/Kconfig"
rsource "lisa_log/Kconfig"
rsource "lisa_wifi/Kconfig"
rsource "lisa_bluetooth/Kconfig"
rsource "work_queue/Kconfig"
rsource "app_player/Kconfig"
endmenu

View File

@@ -0,0 +1,22 @@
if (CONFIG_ACOMP)
set(TARGET_NAME psram_acomp-master)
listenai_library_named(${TARGET_NAME})
listenai_library_sources(acomp.c)
listenai_library_sources(acomp_sample.c)
add_subdirectory(ipc)
add_subdirectory(comm)
add_subdirectory(utils)
listenai_add_subdirectory_ifdef(CONFIG_ACOMP_WSP wsp)
listenai_add_subdirectory_ifdef(CONFIG_ACOMP_WAKEUP wakeup)
listenai_add_subdirectory_ifdef(CONFIG_ACOMP_LOGGER logger)
listenai_add_subdirectory_ifdef(CONFIG_ACOMP_FD fd)
listenai_include_directories(./)
target_include_directories(${TARGET_NAME} PUBLIC .)
target_include_directories(${TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inlucde)
endif()

View File

@@ -0,0 +1,39 @@
menu "algo components"
config ACOMP
bool "algo components"
default n
config ACOMP_MASTER
bool "algo components master"
default y
if ACOMP
config ACOMP_WSP
bool "algo component word spell"
default n
config ACOMP_WAKEUP
bool "algo component wakeup"
default n
config ACOMP_LOGGER
bool "algo component logger"
default n
config ACOMP_FD
bool "algo component face detection"
default n
rsource "wakeup/Kconfig"
rsource "fd/Kconfig"
endif
if ACOMP_LOGGER
rsource "logger/Kconfig"
endif
rsource "Kconfig.res"
endmenu

View File

@@ -0,0 +1,14 @@
if ACOMP_WSP
config ACOMP_WSP_RES_ENCODER_ADDRESS
hex "mlp encoder address"
default 0xd200000
config ACOMP_WSP_RES_ENCODER_LENGTH
int "mlp encoder length"
default 3546864
config ACOMP_WSP_RES_DECODER_ADDRESS
hex "mlp decoder address"
default 0xcd00000
config ACOMP_WSP_RES_DECODER_LENGTH
int "mlp decoder length"
default 419904
endif

View File

@@ -0,0 +1,9 @@
#include "ipc/acomp_ipc.h"
#include "wsp/acomp_wsp.h"
int acomp_init(void)
{
acomp_ipc_init();
}

View File

@@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
int acomp_init(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,122 @@
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include "acomp.h"
#include "wsp/acomp_wsp.h"
#include "FreeRTOS.h"
#define TAG "acomp_sample"
#include "lisa_log.h"
void wsp_event_handler(uint32_t event, void *event_data, uint32_t event_data_len, void *priv)
{
if (event & WSP_CB_EVENT_ENGINE_RLT) {
comp_wsp_result_t *result = (comp_wsp_result_t *)event_data;
LISA_LOGI(TAG, "wsp result:%d,%s", result->len, result->data);
} else if (event & WSP_CB_EVENT_ENGINE_VAD_BEGIN) {
LISA_LOGI(TAG, "wsp vad begin frame index:%d", *(uint32_t*)event_data);
} else if (event & WSP_CB_EVENT_ENGINE_VAD_END) {
LISA_LOGI(TAG, "wsp vad end frame index:%d", *(uint32_t*)event_data);
}
else if(event & WSP_CB_EVENT_STREAM_UPDATE){
acomp_stream_channel_t *chn = event_data;
LISA_LOGI(TAG, "wsp stream channel %d update", chn->idx);
}
else {
LISA_LOGW(TAG, "unknown wsp event:0X%X", event);
}
}
void test_acomp(void)
{
uint32_t count = 0;
int ret = 0;
acomp_stream_chn_create_desc_t desc = {
.cname = "wsp.stream.0",
.direction = ACOMP_STREAM_DIRECTION_R2M,
.index = 0,
.buffer_size = 320,
.num_descs = 128,
.kick_policy = 0,
};
LISA_LOGI(TAG, "acomp_init");
acomp_init();
LISA_LOGI(TAG, "acomp_wsp_init");
acomp_wsp_init();
LISA_LOGI(TAG, "acomp_wsp_add_callback");
acomp_wsp_add_callback(WSP_CB_EVENT_ENGINE_RLT | WSP_CB_EVENT_ENGINE_VAD_BEGIN | WSP_CB_EVENT_ENGINE_VAD_END | WSP_CB_EVENT_STREAM_UPDATE,
wsp_event_handler, NULL);
#if 0
do {
ret = acomp_wsp_prepare();
LISA_LOGI(TAG, "acomp_wsp_prepare ret:%d", ret);
ret = acomp_wsp_start();
LISA_LOGI(TAG, "acomp_wsp_start ret:%d", ret);
vTaskDelay(1000);
LISA_LOGI(TAG, "goto stop");
ret = acomp_wsp_stop();
LISA_LOGI(TAG, "acomp_wsp_stop ret:%d", ret);
ret = acomp_wsp_cleanup();
LISA_LOGI(TAG, "acomp_wsp_cleanup ret:%d", ret);
LISA_LOGI(TAG, "\n\n==========test_acomp run count:%d==========\n\n", count++);
vTaskDelay(3000);
} while (0);
#endif
do{
ret = acomp_wsp_stream_ch_enable(0,&desc);
LISA_LOGI(TAG, "acomp_wsp_stream_ch_enable %s ret:%d", desc.cname, ret);
ret = acomp_wsp_prepare();
LISA_LOGI(TAG, "acomp_wsp_prepare ret:%d", ret);
ret = acomp_wsp_start();
LISA_LOGI(TAG, "acomp_wsp_start ret:%d", ret);
uint64_t start_ms = pdTICKS_TO_MS(xTaskGetTickCount());
uint64_t current_ms;
int fd = open("/SD:/wsp_audio.pcm", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
LISA_LOGE(TAG, "Failed to open file /SD:/wsp_audio.pcm");
goto cleanup;
}
LISA_LOGI(TAG, "Audio file opened successfully, fd=%d", fd);
for(;;){
uint8_t* audio_buffer;
uint32_t len;
uint32_t desc_idx;
int written;
audio_buffer = acomp_wsp_stream_rx_buffer_get(0, &len, &desc_idx);
if(audio_buffer == NULL){
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
LISA_LOGI(TAG,"acomp_wsp stream %s get audio buffer:%p,len:%d,desc_idx:%d",desc.cname,audio_buffer,len,desc_idx);
LISA_LOGH(TAG,audio_buffer,64,"audio_buffer:");
written = write(fd, audio_buffer, len);
if (written != (int)len) {
LISA_LOGE(TAG, "Write failed: expected %d bytes, wrote %d bytes", len, written);
}
acomp_wsp_stream_rx_buffer_release(0,desc_idx,len,audio_buffer);
current_ms = pdTICKS_TO_MS(xTaskGetTickCount());
if(current_ms - start_ms > 3000){
break;
}
}
close(fd);
cleanup:
LISA_LOGI(TAG, "goto stop");
ret = acomp_wsp_stop();
LISA_LOGI(TAG, "acomp_wsp_stop ret:%d", ret);
ret = acomp_wsp_cleanup();
LISA_LOGI(TAG, "acomp_wsp_cleanup ret:%d", ret);
ret = acomp_wsp_stream_ch_disable(0);
LISA_LOGI(TAG, "acomp_wsp_stream_ch_disable ret:%d", ret);
}while(0);
}

View File

@@ -0,0 +1,17 @@
# Stream component sources
listenai_library_sources(
stream/acomp_stream.c
stream/acomp_stream_ipc.c
)
# Virtio component sources
listenai_library_sources(
virtio/virtqueue.c
virtio/virtio_port.c
)
# Include directories
listenai_include_directories(
./stream
./virtio
)

View File

@@ -0,0 +1,6 @@
FILE(GLOB SRCS
acomp_stream.c
)
listenai_library_sources(${SRCS})
target_include_directories(${TARGET_NAME} PUBLIC ./stream)

View File

@@ -0,0 +1,459 @@
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include "acomp_stream.h"
#include "../acomp.h"
#include "../virtio/virtqueue.h"
#include "../virtio/virtio_ring.h"
#include "sysheap.h"
#define TAG "acomp_stream"
#include "lisa_log.h"
/* Private function declarations */
static acomp_stream_channel_t *acomp_stream_channel_create(acomp_stream_t *stream, acomp_stream_channel_desc_t *desc);
static int acomp_stream_channel_destroy(acomp_stream_channel_t *channel);
static void *(acomp_stream_tx_buffer_alloc)(acomp_stream_channel_t *channel, uint32_t *len, uint16_t *desc_idx);
static int(acomp_stream_tx_buffer_submit)(acomp_stream_channel_t *channel, void *buffer, uint32_t len,
uint16_t desc_idx);
static void *(acomp_stream_rx_buffer_get)(acomp_stream_channel_t *channel, uint32_t *len, uint16_t *desc_idx);
static int(acomp_stream_rx_buffer_release)(acomp_stream_channel_t *channel, void *buffer, uint32_t len,
uint16_t desc_idx);
static int acomp_stream_kick(acomp_stream_channel_t *channel);
static int acomp_stream_enable_cb(acomp_stream_channel_t *channel);
static void acomp_stream_disable_cb(acomp_stream_channel_t *channel);
static uint32_t acomp_stream_get_buffer_len(acomp_stream_channel_t *channel, uint16_t desc_idx);
/**
* Calculate number of buffers that can fit in given memory
* @param mem_size: Total shared memory size in bytes
* @param buffer_size: Size of each buffer in bytes
* @param align: Alignment requirement (typically 32 or 64)
* @return: Number of buffers (must be power of 2), 0 if memory too small
*
* Memory layout: [vring metadata][buffer0][buffer1]...[bufferN-1]
* vring metadata includes: descriptor table, available ring, used ring
* Each buffer is aligned to 'align' boundary
*/
uint16_t acomp_stream_calc_buffer_num(uint32_t mem_size, uint32_t buffer_size, uint32_t align)
{
if (mem_size < 128 || buffer_size == 0 || align == 0) {
return 0;
}
/* Align buffer_size up to alignment boundary */
uint32_t aligned_buffer_size = (buffer_size + align - 1) & ~(align - 1);
/* Try powers of 2 from largest to smallest */
uint16_t num = 1;
/* Find the largest power of 2 that might fit */
while (num < 8192 && (num * aligned_buffer_size) < mem_size) {
num <<= 1;
}
/* Try smaller values until we find one that fits */
while (num >= 1) {
int32_t vring_metadata_size = vring_size(num, align);
uint32_t total_buffer_size = num * aligned_buffer_size;
uint32_t total_required = vring_metadata_size + total_buffer_size;
if (total_required <= mem_size) {
CLOGD("[%s] mem_size=%u, buffer_size=%u->%u (aligned): num_buffers=%u (vring=%d, buffers=%u, total=%u)\n",
__FUNCTION__, mem_size, buffer_size, aligned_buffer_size, num, vring_metadata_size, total_buffer_size,
total_required);
return num;
}
num >>= 1; /* Try half the size */
}
return 0;
}
/**
* Calculate required memory size for given number of buffers
* @param num_descs: Number of descriptors (must be power of 2)
* @param buffer_size: Size of each buffer in bytes
* @param align: Alignment requirement (typically 32 or 64)
* @return: Total memory size required in bytes, 0 on error
*
* This is the inverse of acomp_stream_calc_buffer_num()
* Memory layout: [vring metadata][buffer0][buffer1]...[bufferN-1]
* Each buffer is aligned to 'align' boundary
*/
uint32_t acomp_stream_calc_mem_size(uint32_t num_descs, uint32_t buffer_size, uint32_t align)
{
if (num_descs == 0 || buffer_size == 0 || align == 0) {
CLOGE("[%s] Invalid parameters: num_descs=%u, buffer_size=%u, align=%u\n", __FUNCTION__, num_descs, buffer_size,
align);
return 0;
}
/* Validate num_descs is power of 2 */
if ((num_descs & (num_descs - 1)) != 0) {
CLOGE("[%s] num_descs=%u must be power of 2\n", __FUNCTION__, num_descs);
return 0;
}
/* Calculate vring metadata size */
int32_t vring_metadata_size = vring_size(num_descs, align);
if (vring_metadata_size < 0) {
CLOGE("[%s] Invalid vring_size result: %d\n", __FUNCTION__, vring_metadata_size);
return 0;
}
/* Align buffer_size up to alignment boundary to ensure each buffer is aligned */
uint32_t aligned_buffer_size = (buffer_size + align - 1) & ~(align - 1);
/* Calculate total required memory */
uint32_t total_buffer_size = num_descs * aligned_buffer_size;
uint32_t total_required = vring_metadata_size + total_buffer_size;
return total_required;
}
/**
* Initialize acomp_stream component
* @param stream: acomp_stream instance to initialize
* @return: ACOMP_STREAM_SUCCESS on success, error code otherwise
*/
acomp_stream_t *acomp_stream_create(uint32_t dev_index)
{
acomp_stream_t *stream = psram_malloc(sizeof(acomp_stream_t));
if (!stream) {
CLOGE("[%s] Invalid parameter: stream is NULL\n", __FUNCTION__);
return NULL;
}
memset(stream, 0, sizeof(acomp_stream_t));
/* Initialize operations */
stream->ops.channel_create = acomp_stream_channel_create;
stream->ops.channel_destroy = acomp_stream_channel_destroy;
/* TX/RX buffer operations */
stream->ops.tx_buffer_alloc = acomp_stream_tx_buffer_alloc;
stream->ops.tx_buffer_submit = acomp_stream_tx_buffer_submit;
stream->ops.rx_buffer_get = acomp_stream_rx_buffer_get;
stream->ops.rx_buffer_release = acomp_stream_rx_buffer_release;
/* Control operations */
stream->ops.kick = acomp_stream_kick;
stream->ops.get_buffer_len = acomp_stream_get_buffer_len;
stream->channel_count = 0;
stream->dev_index = dev_index; /* Store dev pointer in REMOTE mode */
CLOGD("[%s] acomp_stream create successfully\n", __FUNCTION__);
return stream;
}
/**
* Deinitialize acomp_stream component
* @param stream: acomp_stream instance to deinitialize
* @return: ACOMP_STREAM_SUCCESS on success, error code otherwise
*/
int acomp_stream_destroy(acomp_stream_t *stream)
{
if (!stream) {
return ACOMP_STREAM_ERROR_INVALID_PARAM;
}
/* Destroy all active channels */
for (uint32_t i = 0; i < ACOMP_STREAM_MAX_CHANNEL; i++) {
if (stream->ch[i] != NULL && stream->ch[i]->vq != NULL) {
acomp_stream_channel_destroy(stream->ch[i]);
stream->ch[i] = NULL;
}
}
psram_free(stream);
CLOGD("[%s] acomp_stream destroy\n", __FUNCTION__);
return ACOMP_STREAM_SUCCESS;
}
/**
* Create a new channel
* @param stream: Stream instance to create channel in
* @param desc: Channel description
* @return: Pointer to created channel, NULL on failure
*/
static acomp_stream_channel_t *acomp_stream_channel_create(acomp_stream_t *stream, acomp_stream_channel_desc_t *desc)
{
acomp_stream_channel_t *channel;
if (!stream || !desc || !desc->cname) {
CLOGE("[%s] Invalid parameter: stream, desc or name is NULL\n", __FUNCTION__);
return NULL;
}
/* Validate ring parameters */
if (desc->ring.num_descs == 0 || (desc->ring.num_descs & (desc->ring.num_descs - 1)) != 0) {
CLOGE("[%s] Invalid ring size: %d (must be power of 2)\n", __FUNCTION__, desc->ring.num_descs);
return NULL;
}
if (!desc->ring.phy_addr) {
CLOGE("[%s] Invalid ring physical address\n", __FUNCTION__);
return NULL;
}
/* Validate buffer_size (recommended for zero-copy transfer) */
if (desc->buffer_size == 0) {
CLOGW("[%s] Warning: buffer_size not provided, zero-copy transfer won't be available\n", __FUNCTION__);
/* Allow creation without pre-filling for backward compatibility */
}
channel = psram_malloc(sizeof(acomp_stream_channel_t));
if (!channel) {
CLOGE("[%s] No available channel slots\n", __FUNCTION__);
return NULL;
}
/* Initialize channel */
memset(channel, 0, sizeof(acomp_stream_channel_t));
snprintf(channel->name, sizeof(channel->name), "%s", desc->cname);
channel->direction = desc->direction;
channel->idx = desc->vq_id;
channel->kick_policy = desc->kick_policy;
channel->user_priv = desc->user_priv;
channel->priv = stream;
/* Create virtqueue */
int32_t ret =
virtqueue_create(desc->vq_id, desc->cname, &desc->ring, desc->callback_fc, desc->notify_fc, &channel->vq);
if (ret != VQUEUE_SUCCESS) {
CLOGE("[%s] Failed to create virtqueue: %d\n", __FUNCTION__, ret);
memset(channel, 0, sizeof(acomp_stream_channel_t));
return NULL;
}
/* Set channel as virtqueue's private data for callback */
channel->vq->priv = channel;
#ifdef ACOMP_STREAM_ROLE_MASTER
if (desc->direction == ACOMP_STREAM_DIRECTION_R2M)
#else
if (desc->direction == ACOMP_STREAM_DIRECTION_M2R)
#endif
{
/* Initialize virtqueue ring */
vq_ring_init(channel->vq);
if (desc->buffer_size > 0) {
/* Calculate buffer pool start address:
* buffer_pool = shared_mem_base + vring_metadata_size
*/
int32_t vring_metadata_size = vring_size(desc->ring.num_descs, desc->ring.align);
uint8_t *buffer_pool = (uint8_t *)desc->ring.phy_addr + vring_metadata_size;
/* Align buffer_size to ensure each buffer is properly aligned */
uint32_t aligned_buffer_size = (desc->buffer_size + desc->ring.align - 1) & ~(desc->ring.align - 1);
/* Fill all descriptors with available buffers */
for (uint16_t i = 0; i < desc->ring.num_descs; i++) {
void *buffer = buffer_pool + (i * aligned_buffer_size);
ret = virtqueue_fill_avail_buffers(channel->vq, buffer, desc->buffer_size);
if (ret != VQUEUE_SUCCESS) {
CLOGE("[%s] Failed to pre-fill virtqueue buffer %d: %d\n", __FUNCTION__, i, ret);
virtqueue_free(channel->vq);
psram_free(channel);
return NULL;
}
}
}
}
stream->channel_count++;
CLOGI("[%s]Acomp stream channel %p,%p,'%s' created successfully ,vring phy addr=%p, num_descs=%d, buffer_size=%u, "
"direction=%s, role=%s\n",
__FUNCTION__, channel, channel->vq, desc->cname, desc->ring.phy_addr, desc->ring.num_descs, desc->buffer_size,
desc->direction == ACOMP_STREAM_DIRECTION_M2R ? "M2R" : "R2M",
#ifdef ACOMP_STREAM_ROLE_MASTER
"Master"
#else
"Remote"
#endif
);
return channel;
}
/**
* Destroy a channel
* @param channel: Channel to destroy
* @return: ACOMP_STREAM_SUCCESS on success, error code otherwise
*/
static int acomp_stream_channel_destroy(acomp_stream_channel_t *channel)
{
if (!channel) {
CLOGE("[%s] Invalid parameter: channel is NULL\n", __FUNCTION__);
return ACOMP_STREAM_ERROR_INVALID_PARAM;
}
CLOGD("[%s] Destroying channel '%s' (idx=%d)\n", __FUNCTION__, channel->name, channel->idx);
/* Get parent stream for updating channel count (before memset) */
acomp_stream_t *stream = (acomp_stream_t *)channel->priv;
/* Disable virtqueue callback if exists */
if (channel->vq) {
/* Free virtqueue */
virtqueue_free(channel->vq);
channel->vq = NULL;
}
/* Update parent stream channel count */
if (stream && stream->channel_count > 0) {
stream->channel_count--;
}
/* Free channel memory */
psram_free(channel);
CLOGD("[%s] Channel destroyed successfully\n", __FUNCTION__);
return ACOMP_STREAM_SUCCESS;
}
static void *(acomp_stream_tx_buffer_alloc)(acomp_stream_channel_t *channel, uint32_t *len, uint16_t *desc_idx)
{
void *p_buf = NULL;
if (!channel || !channel->vq) {
CLOGE("[%s] Invalid parameters\n", __FUNCTION__);
return NULL;
}
#if defined(ACOMP_STREAM_ROLE_MASTER)
if (channel->direction == ACOMP_STREAM_DIRECTION_M2R)
#else
if (channel->direction == ACOMP_STREAM_DIRECTION_R2M)
#endif
{
p_buf = virtqueue_get_available_buffer(channel->vq, desc_idx, len);
}
/* invalidate cache before use buffer */
if (p_buf != NULL) {
env_cache_invalidate(p_buf, *len);
}
return p_buf;
}
static int(acomp_stream_tx_buffer_submit)(acomp_stream_channel_t *channel, void *buffer, uint32_t len,
uint16_t desc_idx)
{
int ret = ACOMP_STREAM_ERROR_WRONG_DIRECTION;
if (!channel || !channel->vq || !buffer || !len) {
return ACOMP_STREAM_ERROR_INVALID_PARAM;
}
#if defined(ACOMP_STREAM_ROLE_MASTER)
if (channel->direction == ACOMP_STREAM_DIRECTION_M2R)
#else
if (channel->direction == ACOMP_STREAM_DIRECTION_R2M)
#endif
{
/* flush cache before submit buffer */
env_cache_flush(buffer, len);
ret = virtqueue_add_consumed_buffer(channel->vq, desc_idx, len);
/* Kick based on policy */
channel->kick_count++;
if ((channel->kick_policy != 0) && (channel->kick_count >= channel->kick_policy)) {
virtqueue_kick(channel->vq);
channel->kick_count = 0;
}
}
return ret;
}
static void *(acomp_stream_rx_buffer_get)(acomp_stream_channel_t *channel, uint32_t *len, uint16_t *desc_idx)
{
void *p_buf = NULL;
if (!channel || !channel->vq) {
CLOGE("[%s] Invalid parameters\n", __FUNCTION__);
return NULL;
}
#if defined(ACOMP_STREAM_ROLE_MASTER)
if (channel->direction == ACOMP_STREAM_DIRECTION_R2M)
#else
if (channel->direction == ACOMP_STREAM_DIRECTION_M2R)
#endif
{
p_buf = virtqueue_get_buffer(channel->vq, len, desc_idx);
}
/* invalidate cache before use buffer */
if (p_buf != NULL) {
env_cache_invalidate(p_buf, *len);
}
return p_buf;
}
static int(acomp_stream_rx_buffer_release)(acomp_stream_channel_t *channel, void *buffer, uint32_t len,
uint16_t desc_idx)
{
int ret = 0;
if (!channel || !channel->vq || !buffer) {
return ACOMP_STREAM_ERROR_INVALID_PARAM;
}
/* flush cache before submit buffer */
env_cache_flush(buffer, len);
#if defined(ACOMP_STREAM_ROLE_MASTER)
if (channel->direction == ACOMP_STREAM_DIRECTION_R2M)
#else
if (channel->direction == ACOMP_STREAM_DIRECTION_M2R)
#endif
{
ret = virtqueue_add_buffer(channel->vq, desc_idx);
}
return ret;
}
/**
* Manually kick the virtqueue to notify peer
* @param channel: Channel to kick
* @return: ACOMP_STREAM_SUCCESS on success, error code otherwise
*/
static int acomp_stream_kick(acomp_stream_channel_t *channel)
{
if (!channel || !channel->vq) {
return ACOMP_STREAM_ERROR_INVALID_PARAM;
}
virtqueue_kick(channel->vq);
return ACOMP_STREAM_SUCCESS;
}
/**
* Get buffer length by descriptor index
* @param channel: Channel
* @param desc_idx: Descriptor index
* @return: Buffer length, 0 on error
*/
static uint32_t acomp_stream_get_buffer_len(acomp_stream_channel_t *channel, uint16_t desc_idx)
{
if (!channel || !channel->vq) {
return 0;
}
return virtqueue_get_buffer_length(channel->vq, desc_idx);
}

View File

@@ -0,0 +1,112 @@
#ifndef __ACOMP_STREAM_H__
#define __ACOMP_STREAM_H__
#include <stdint.h>
#include <stdbool.h>
#include "../virtio/virtqueue.h"
/*
* IMPORTANT: Compile-time role definition
*
* Define ACOMP_STREAM_ROLE_MASTER to indicate current program runs on Master core
* - If defined: Master role (device side for M2R, driver side for R2M)
* - If NOT defined: Remote role (driver side for M2R, device side for R2M)
*
* This macro determines automatic buffer pre-filling behavior:
* - Driver side (RX): Pre-fills available ring with empty buffers
* - Device side (TX): Gets buffers from available ring to write data
*
* Example usage in build system:
* Master build: -DACOMP_STREAM_ROLE_MASTER
* Remote build: (no flag needed, defaults to Remote)
*/
#if defined(CONFIG_ACOMP_REMOTE)
#define ACOMP_STREAM_ROLE_REMOTE
#else
#define ACOMP_STREAM_ROLE_MASTER
#endif
#if defined (ACOMP_STREAM_ROLE_MASTER)
#include "../ipc/acomp_ipc.h"
#endif
#define ACOMP_STREAM_NAME_MAX_LEN (16)
#define ACOMP_STREAM_MAX_CHANNEL (4)
/* Kick policy definitions */
#define ACOMP_STREAM_KICK_IMMEDIATE (0) /* Kick immediately after each buffer */
#define ACOMP_STREAM_KICK_BATCH (1) /* Kick after multiple buffers */
#define ACOMP_STREAM_KICK_MANUAL (2) /* Manual kick by upper layer */
struct acomp_stream;
typedef enum{
ACOMP_STREAM_DIRECTION_M2R, /* master to remote */
ACOMP_STREAM_DIRECTION_R2M, /* remote to master */
}acomp_stream_direction_e;
typedef struct{
char name[ACOMP_STREAM_NAME_MAX_LEN];
acomp_stream_direction_e direction;
uint32_t idx;
struct virtqueue* vq; /* Associated virtqueue */
uint32_t kick_policy; /* Kick policy */
uint32_t kick_count; /* Kick count */
void* user_priv; /* User private data */
void* priv;
}acomp_stream_channel_t;
typedef struct{
const char* cname; /* Channel name for debugging */
acomp_stream_direction_e direction; /* Channel direction */
uint16_t vq_id; /* VirtQueue ID */
struct vring_alloc_info ring; /* VirtQueue ring memory info */
uint32_t buffer_size; /* Size of each buffer (required for R2M channels) */
vq_callback *callback_fc; /* Optional: callback when data available */
vq_notify *notify_fc; /* Optional: notify function to peer */
uint32_t kick_policy; /* Kick policy: 0:manual/ >0: buffer count to kick */
void* user_priv; /* User private data */
}acomp_stream_channel_desc_t;
typedef struct{
/* Channel management */
acomp_stream_channel_t *(*channel_create)(struct acomp_stream* stream, acomp_stream_channel_desc_t *desc);
int (*channel_destroy)(acomp_stream_channel_t* channel);
/* Zero-copy buffer operations (symmetric for TX and RX) */
void *(*tx_buffer_alloc)(acomp_stream_channel_t* channel, uint32_t* len, uint16_t* desc_idx);
int (*tx_buffer_submit)(acomp_stream_channel_t* channel, void *buffer, uint32_t len, uint16_t desc_idx);
void *(*rx_buffer_get)(acomp_stream_channel_t* channel, uint32_t* len, uint16_t* desc_idx);
int (*rx_buffer_release)(acomp_stream_channel_t* channel, void *buffer, uint32_t len, uint16_t desc_idx);
/* Control operations */
int (*kick)(acomp_stream_channel_t* channel);
uint32_t (*get_buffer_len)(acomp_stream_channel_t* channel, uint16_t desc_idx);
}acomp_stream_ops_t;
typedef struct acomp_stream{
acomp_stream_channel_t *ch[ACOMP_STREAM_MAX_CHANNEL];
acomp_stream_ops_t ops;
uint32_t channel_count; /* Number of active channels */
uint32_t dev_index;
}acomp_stream_t;
/* Error codes */
#define ACOMP_STREAM_SUCCESS (0)
#define ACOMP_STREAM_ERROR_INVALID_PARAM (-1)
#define ACOMP_STREAM_ERROR_NO_MEMORY (-2)
#define ACOMP_STREAM_ERROR_CHANNEL_FULL (-3)
#define ACOMP_STREAM_ERROR_WRONG_DIRECTION (-4)
#define ACOMP_STREAM_ERROR_NO_BUFFER (-5)
#define ACOMP_STREAM_ERROR_CREATE_FAILED (-6)
/* Function declarations */
acomp_stream_t* acomp_stream_create(uint32_t dev_index);
int acomp_stream_destroy(acomp_stream_t* stream);
uint16_t acomp_stream_calc_buffer_num(uint32_t mem_size, uint32_t buffer_size, uint32_t align);
uint32_t acomp_stream_calc_mem_size(uint32_t num_descs, uint32_t buffer_size, uint32_t align);
#endif

View File

@@ -0,0 +1,116 @@
#include <stdio.h>
#include <string.h>
#include "acomp_stream_ipc.h"
#include "utils/acomp_err.h"
#define TAG "stream_ipc"
#include "lisa_log.h"
#define CACHE_LINE_SIZE (32)
static void stream_notify(struct virtqueue *vqa){
struct acomp_device* dev;
acomp_stream_t *stream;
acomp_stream_channel_t* channel;
acomp_ipc_stream_update_t *ipc_desc;
int ret;
channel = vqa->priv;
stream = channel->priv;
ipc_desc = psram_malloc_align(CACHE_LINE_SIZE, sizeof(acomp_ipc_stream_update_t));
if(ipc_desc == NULL){
LISA_LOGE(TAG,"[%s %d] psram_malloc_align failed",__FUNCTION__,__LINE__);
return;
}
ipc_desc->index = channel->idx;
ret = acomp_ipc_build_frame_send_sync( stream->dev_index,
ACOMP_CONTEXT_IPC_GLB_CONTROL| IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_STREAM_UPDATE,
0,
ipc_desc,
sizeof(acomp_ipc_stream_update_t));
psram_free(ipc_desc);
// LISA_LOGI(TAG,"[%s %d]ret:%d",__FUNCTION__,__LINE__,ret);
}
acomp_stream_channel_t* acomp_stream_ipc_channel_create(acomp_stream_t* stream,uint32_t chn,uint32_t dev_index,acomp_stream_chn_create_desc_t *desc){
uint32_t mem_size;
uint8_t* mem_ptr;
int ret;
acomp_ipc_stream_create_desc_t *ipc_desc;
acomp_stream_channel_desc_t chn_desc = {
.cname = desc->cname,
.direction = desc->direction,
.vq_id = desc->index,
.ring = {
.phy_addr = NULL,
.align = 32,
.num_descs = desc->num_descs,
.pad = 32,
},
.buffer_size = desc->buffer_size,
.notify_fc = stream_notify,
.callback_fc = NULL,
.kick_policy = desc->kick_policy,
.user_priv = NULL,
};
mem_size = acomp_stream_calc_mem_size (chn_desc.ring.num_descs, chn_desc.buffer_size, chn_desc.ring.align);
if (mem_size == 0) {
LISA_LOGE(TAG, "Failed to calculate stream memory size");
return NULL;
}
mem_ptr = psram_malloc_align(chn_desc.ring.align, mem_size);
if (mem_ptr == NULL) {
LISA_LOGE(TAG, "Failed to allocate stream memory");
return NULL;
}
memset(mem_ptr, 0, mem_size);
HAL_FlushInvalidateDCache_by_Addr(mem_ptr, mem_size);
chn_desc.ring.phy_addr = mem_ptr;
acomp_stream_channel_t *channel = stream->ops.channel_create(stream, &chn_desc);
if(!channel){
LISA_LOGE(TAG, "Failed to create channel %d", chn);
return NULL;
}
ipc_desc = psram_malloc_align(chn_desc.ring.align, sizeof(acomp_ipc_stream_create_desc_t));
if (ipc_desc == NULL) {
LISA_LOGE(TAG, "Failed to allocate ipc stream create memory");
stream->ops.channel_destroy(channel);
return NULL;
}
snprintf(ipc_desc->name, sizeof(ipc_desc->name), "%s", chn_desc.cname);
ipc_desc->direction = chn_desc.direction == ACOMP_STREAM_DIRECTION_M2R ? 0 : 1;
ipc_desc->index = chn_desc.vq_id;
ipc_desc->phy_addr = mem_ptr;
ipc_desc->mem_size = mem_size;
ipc_desc->align = chn_desc.ring.align;
ipc_desc->buffer_size = chn_desc.buffer_size;
ipc_desc->kick_policy = chn_desc.kick_policy;
ret = acomp_ipc_build_frame_send_sync(dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_STREAM_CREATE, 0, ipc_desc, sizeof(acomp_ipc_stream_create_desc_t));
if(ret != ACOMP_ERR_OK){
stream->ops.channel_destroy(channel);
return NULL;
}
psram_free(ipc_desc);
return channel;
}
int acomp_stream_ipc_channel_destroy(uint32_t chn){
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include "comm/stream/acomp_stream.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct{
const char *cname;
acomp_stream_direction_e direction;
uint32_t index;
uint32_t buffer_size;
uint32_t num_descs;
uint32_t kick_policy; /* Kick policy: 0:manual/ >0: buffer count to kick */
}acomp_stream_chn_create_desc_t;
acomp_stream_channel_t* acomp_stream_ipc_channel_create(acomp_stream_t* stream,uint32_t chn,uint32_t dev_index,acomp_stream_chn_create_desc_t *desc);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,859 @@
/*
* Copyright (c) 2014, Mentor Graphics Corporation
* Copyright (c) 2015 Xilinx, Inc.
* Copyright (c) 2016 Freescale Semiconductor, Inc.
* Copyright 2016-2025 NXP
* Copyright 2021 ACRIOS Systems s.r.o.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**************************************************************************
* FILE NAME
*
* rpmsg_env_freertos.c
*
*
* DESCRIPTION
*
* This file is FreeRTOS Implementation of env layer for OpenAMP.
*
*
**************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "virtqueue.h"
#include "event_groups.h"
#include <stdlib.h>
#include <string.h>
static int32_t env_init_counter = 0;
static SemaphoreHandle_t env_sema = ((void *)0);
#ifndef __COVERAGESCANNER__
static EventGroupHandle_t event_group = ((void *)0);
#else
EventGroupHandle_t event_group = ((void *)0);
#endif
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
LOCK_STATIC_CONTEXT env_sem_static_context;
StaticEventGroup_t event_group_static_context;
#endif
/* RL_ENV_MAX_MUTEX_COUNT is an arbitrary count greater than 'count'
if the inital count is 1, this function behaves as a mutex
if it is greater than 1, it acts as a "resource allocator" with
the maximum of 'count' resources available.
Currently, only the first use-case is applicable/applied in RPMsg-Lite.
*/
#define RL_ENV_MAX_MUTEX_COUNT (10)
/* Max supported ISR counts */
#define ISR_COUNT (32U)
/*!
* Structure to keep track of registered ISR's.
*/
struct isr_info
{
void *data;
};
static struct isr_info isr_table[ISR_COUNT];
#if defined(RL_USE_ENVIRONMENT_CONTEXT) && (RL_USE_ENVIRONMENT_CONTEXT == 1)
#error "This RPMsg-Lite port requires RL_USE_ENVIRONMENT_CONTEXT set to 0"
#endif
#if defined(AARCH64)
extern uint64_t ullPortInterruptNesting;
static int32_t os_in_isr(void)
{
return (ullPortInterruptNesting > 0);
}
#endif
/*!
* env_in_isr
*
* @returns - true, if currently in ISR
*
*/
static int32_t env_in_isr(void)
{
#if defined(AARCH64)
return os_in_isr();
#else
return platform_in_isr();
#endif
}
#ifndef __COVERAGESCANNER__
/*!
* env_wait_for_link_up
*
* Wait until the link_state parameter of the rpmsg_lite_instance is set.
* Utilize events to avoid busy loop implementation.
*
*/
uint32_t env_wait_for_link_up(volatile uint32_t *link_state, uint32_t link_id, uint32_t timeout_ms)
{
(void)xEventGroupClearBits(event_group, (EventBits_t)(1UL << link_id));
if (*link_state != 1U)
{
EventBits_t uxBits;
uxBits = xEventGroupWaitBits(event_group, (EventBits_t)(1UL << link_id), pdFALSE, pdTRUE,
((portMAX_DELAY == timeout_ms) ? portMAX_DELAY : timeout_ms / portTICK_PERIOD_MS));
if (uxBits == (EventBits_t)(1UL << link_id))
{
return 1U;
}
else
{
/* timeout */
return 0U;
}
}
else
{
return 1U;
}
}
/*!
* env_tx_callback
*
* Set event to notify task waiting in env_wait_for_link_up().
*
*/
void env_tx_callback(uint32_t link_id)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (env_in_isr() != 0)
{
(void)xEventGroupSetBitsFromISR(event_group, (EventBits_t)(1UL << link_id), &xHigherPriorityTaskWoken);
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
}
else
{
(void)xEventGroupSetBits(event_group, (EventBits_t)(1UL << link_id));
}
}
#endif /* __COVERAGESCANNER__*/
/*!
* env_init
*
* Initializes OS/BM environment.
*
*/
int32_t env_init(void)
{
int32_t retval;
vTaskSuspendAll(); /* stop scheduler */
/* verify 'env_init_counter' */
RL_ASSERT(env_init_counter >= 0);
if (env_init_counter < 0)
{
/* coco begin validated: (env_init_counter < 0) condition will never met unless RAM is corrupted */
(void)xTaskResumeAll(); /* re-enable scheduler */
return -1;
/* coco end */
}
env_init_counter++;
/* multiple call of 'env_init' - return ok */
if (env_init_counter == 1)
{
/* first call */
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
env_sema = (SemaphoreHandle_t)xSemaphoreCreateBinaryStatic(&env_sem_static_context);
event_group = (EventGroupHandle_t)xEventGroupCreateStatic(&event_group_static_context);
#else
env_sema = (SemaphoreHandle_t)xSemaphoreCreateBinary();
event_group = (EventGroupHandle_t)xEventGroupCreate();
#endif
#if (configUSE_16_BIT_TICKS == 1)
(void)xEventGroupClearBits(event_group, 0xFFu);
#else
(void)xEventGroupClearBits(event_group, 0xFFFFFFu);
#endif
(void)memset(isr_table, 0, sizeof(isr_table));
(void)xTaskResumeAll();
retval = platform_init();
(void)xSemaphoreGive(env_sema);
return retval;
}
else
{
(void)xTaskResumeAll();
/* Get the semaphore and then return it,
* this allows for platform_init() to block
* if needed and other tasks to wait for the
* blocking to be done.
* This is in ENV layer as this is ENV specific.*/
(void)xSemaphoreTake(env_sema, portMAX_DELAY);
(void)xSemaphoreGive(env_sema);
return 0;
}
}
/*!
* env_deinit
*
* Uninitializes OS/BM environment.
*
* @returns - execution status
*/
int32_t env_deinit(void)
{
int32_t retval;
vTaskSuspendAll(); /* stop scheduler */
/* verify 'env_init_counter' */
RL_ASSERT(env_init_counter > 0);
if (env_init_counter <= 0)
{
(void)xTaskResumeAll(); /* re-enable scheduler */
return -1;
}
/* counter on zero - call platform deinit */
env_init_counter--;
/* multiple call of 'env_deinit' - return ok */
if (env_init_counter <= 0)
{
/* last call */
(void)memset(isr_table, 0, sizeof(isr_table));
retval = platform_deinit();
vEventGroupDelete(event_group);
event_group = ((void *)0);
vSemaphoreDelete(env_sema);
env_sema = ((void *)0);
(void)xTaskResumeAll();
return retval;
}
else
{
(void)xTaskResumeAll();
return 0;
}
}
#if !(defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1))
/*!
* env_allocate_memory - implementation
*
* @param size
*/
void *env_allocate_memory(uint32_t size)
{
return (pvPortMalloc(size));
}
/*!
* env_free_memory - implementation
*
* @param ptr
*/
void env_free_memory(void *ptr)
{
if (ptr != ((void *)0))
{
vPortFree(ptr);
}
}
#endif
/*!
*
* env_memset - implementation
*
* @param ptr
* @param value
* @param size
*/
void env_memset(void *ptr, int32_t value, uint32_t size)
{
/* Explicitly convert value to unsigned char range to ensure consistent behavior */
(void)memset(ptr, (unsigned char)(value & 0xFF), size);
}
/*!
*
* env_memcpy - implementation
*
* @param dst
* @param src
* @param len
*/
void env_memcpy(void *dst, void const *src, uint32_t len)
{
(void)memcpy(dst, src, len);
}
/*!
*
* env_strcmp - implementation
*
* @param dst
* @param src
*/
int32_t env_strcmp(const char *dst, const char *src)
{
return (strcmp(dst, src));
}
/*!
*
* env_strncpy - implementation
*
* @param dest
* @param src
* @param len
*/
void env_strncpy(char *dest, const char *src, uint32_t len)
{
(void)strncpy(dest, src, len);
}
/*!
*
* env_strncmp - implementation
*
* @param dest
* @param src
* @param len
*/
int32_t env_strncmp(char *dest, const char *src, uint32_t len)
{
return (strncmp(dest, src, len));
}
/*!
*
* env_mb - implementation
*
*/
void env_mb(void)
{
// MEM_BARRIER();
}
/*!
* env_rmb - implementation
*/
void env_rmb(void)
{
// MEM_BARRIER();
}
/*!
* env_wmb - implementation
*/
void env_wmb(void)
{
// MEM_BARRIER();
}
/*!
* env_map_vatopa - implementation
*
* @param address
*/
uint32_t env_map_vatopa(void *address)
{
// return platform_vatopa(address);
return (uint32_t)address;
}
/*!
* env_map_patova - implementation
*
* @param address
*/
void *env_map_patova(uint32_t address)
{
// return platform_patova(address);
return (void*)address;
}
/*!
* env_create_mutex
*
* Creates a mutex with the given initial count.
*
*/
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
int32_t env_create_mutex(void **lock, int32_t count, void *context)
#else
int32_t env_create_mutex(void **lock, int32_t count)
#endif
{
if (count > RL_ENV_MAX_MUTEX_COUNT)
{
return -1;
}
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
*lock = (void *)xSemaphoreCreateCountingStatic((UBaseType_t)RL_ENV_MAX_MUTEX_COUNT, (UBaseType_t)count,
(StaticSemaphore_t *)context);
#else
*lock = (void *)xSemaphoreCreateCounting((UBaseType_t)RL_ENV_MAX_MUTEX_COUNT, (UBaseType_t)count);
#endif
if (*lock != ((void *)0))
{
return 0;
}
else
{
return -1;
}
}
/*!
* env_delete_mutex
*
* Deletes the given lock
*
*/
void env_delete_mutex(void *lock)
{
vSemaphoreDelete(lock);
}
/*!
* env_lock_mutex
*
* Tries to acquire the lock, if lock is not available then call to
* this function will suspend.
*/
void env_lock_mutex(void *lock)
{
SemaphoreHandle_t xSemaphore = (SemaphoreHandle_t)lock;
if (env_in_isr() == 0)
{
(void)xSemaphoreTake(xSemaphore, portMAX_DELAY);
}
}
/*!
* env_unlock_mutex
*
* Releases the given lock.
*/
void env_unlock_mutex(void *lock)
{
SemaphoreHandle_t xSemaphore = (SemaphoreHandle_t)lock;
if (env_in_isr() == 0)
{
(void)xSemaphoreGive(xSemaphore);
}
}
/*!
* env_create_sync_lock
*
* Creates a synchronization lock primitive. It is used
* when signal has to be sent from the interrupt context to main
* thread context.
*/
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
int32_t env_create_sync_lock(void **lock, int32_t state, void *context)
{
return env_create_mutex(lock, state, context); /* state=1 .. initially free */
}
#else
int32_t env_create_sync_lock(void **lock, int32_t state)
{
return env_create_mutex(lock, state); /* state=1 .. initially free */
}
#endif
/*!
* env_delete_sync_lock
*
* Deletes the given lock
*
*/
void env_delete_sync_lock(void *lock)
{
if (lock != ((void *)0))
{
env_delete_mutex(lock);
}
}
#ifndef __COVERAGESCANNER__
/*!
* env_acquire_sync_lock
*
* Tries to acquire the lock, if lock is not available then call to
* this function waits for lock to become available.
*/
void env_acquire_sync_lock(void *lock)
{
BaseType_t xTaskWokenByReceive = pdFALSE;
SemaphoreHandle_t xSemaphore = (SemaphoreHandle_t)lock;
if (env_in_isr() != 0)
{
(void)xSemaphoreTakeFromISR(xSemaphore, &xTaskWokenByReceive);
portEND_SWITCHING_ISR(xTaskWokenByReceive);
}
else
{
(void)xSemaphoreTake(xSemaphore, portMAX_DELAY);
}
}
/*!
* env_release_sync_lock
*
* Releases the given lock.
*/
void env_release_sync_lock(void *lock)
{
BaseType_t xTaskWokenByReceive = pdFALSE;
SemaphoreHandle_t xSemaphore = (SemaphoreHandle_t)lock;
if (env_in_isr() != 0)
{
(void)xSemaphoreGiveFromISR(xSemaphore, &xTaskWokenByReceive);
portEND_SWITCHING_ISR(xTaskWokenByReceive);
}
else
{
(void)xSemaphoreGive(xSemaphore);
}
}
#endif /* __COVERAGESCANNER__ */
/*!
* env_sleep_msec
*
* Suspends the calling thread for given time , in msecs.
*/
void env_sleep_msec(uint32_t num_msec)
{
vTaskDelay(num_msec / portTICK_PERIOD_MS);
}
/*!
* env_register_isr
*
* Registers interrupt handler data for the given interrupt vector.
*
* @param vector_id - virtual interrupt vector number
* @param data - interrupt handler data (virtqueue)
*/
void env_register_isr(uint32_t vector_id, void *data)
{
RL_ASSERT(vector_id < ISR_COUNT);
if (vector_id < ISR_COUNT)
{
isr_table[vector_id].data = data;
}
}
/*!
* env_unregister_isr
*
* Unregisters interrupt handler data for the given interrupt vector.
*
* @param vector_id - virtual interrupt vector number
*/
void env_unregister_isr(uint32_t vector_id)
{
RL_ASSERT(vector_id < ISR_COUNT);
if (vector_id < ISR_COUNT)
{
isr_table[vector_id].data = ((void *)0);
}
}
/*!
* env_enable_interrupt
*
* Enables the given interrupt
*
* @param vector_id - virtual interrupt vector number
*/
void env_enable_interrupt(uint32_t vector_id)
{
(void)platform_interrupt_enable(vector_id);
}
/*!
* env_disable_interrupt
*
* Disables the given interrupt
*
* @param vector_id - virtual interrupt vector number
*/
void env_disable_interrupt(uint32_t vector_id)
{
(void)platform_interrupt_disable(vector_id);
}
/*!
* env_map_memory
*
* Enables memory mapping for given memory region.
*
* @param pa - physical address of memory
* @param va - logical address of memory
* @param size - memory size
* param flags - flags for cache/uncached and access type
*/
void env_map_memory(uint32_t pa, uint32_t va, uint32_t size, uint32_t flags)
{
platform_map_mem_region(va, pa, size, flags);
}
/*!
* env_disable_cache
*
* Disables system caches.
*
*/
void env_disable_cache(void)
{
HAL_FlushInvalidateDCache();
HAL_DisableDCache();
}
void env_cache_flush(void *data, uint32_t len)
{
#if defined(RL_USE_DCACHE) && (RL_USE_DCACHE == 1)
HAL_FlushDCache_by_Addr(data, len);
#endif
}
void env_cache_invalidate(void *data, uint32_t len)
{
#if defined(RL_USE_DCACHE) && (RL_USE_DCACHE == 1)
HAL_InvalidateDCache_by_Addr(data, len);
#endif
}
/*!
*
* env_get_timestamp
*
* Returns a 64 bit time stamp.
*
*
*/
uint64_t env_get_timestamp(void)
{
if (env_in_isr() != 0)
{
return (uint64_t)xTaskGetTickCountFromISR();
}
else
{
return (uint64_t)xTaskGetTickCount();
}
}
/*========================================================= */
/* Util data / functions */
void env_isr(uint32_t vector)
{
struct isr_info *info;
RL_ASSERT(vector < ISR_COUNT);
if (vector < ISR_COUNT)
{
info = &isr_table[vector];
virtqueue_notification((struct virtqueue *)info->data);
}
}
/*
* env_create_queue
*
* Creates a message queue.
*
* @param queue - pointer to created queue
* @param length - maximum number of elements in the queue
* @param element_size - queue element size in bytes
* @param queue_static_storage - pointer to queue static storage buffer
* @param queue_static_context - pointer to queue static context
*
* @return - status of function execution
*/
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
int32_t env_create_queue(void **queue,
int32_t length,
int32_t element_size,
uint8_t *queue_static_storage,
rpmsg_static_queue_ctxt *queue_static_context)
{
if (length < 0 || element_size < 0)
{
/* Length and size should not be negative */
*queue = NULL;
return -1;
}
*queue = (void *)xQueueCreateStatic((UBaseType_t)length, (UBaseType_t)element_size, queue_static_storage,
queue_static_context);
#else
int32_t env_create_queue(void **queue, int32_t length, int32_t element_size)
{
if (length < 0 || element_size < 0)
{
/* Length and size should not be negative */
*queue = NULL;
return -1;
}
*queue = xQueueCreate((UBaseType_t)length, (UBaseType_t)element_size);
#endif
if (*queue != ((void *)0))
{
return 0;
}
else
{
return -1;
}
}
/*!
* env_delete_queue
*
* Deletes the message queue.
*
* @param queue - queue to delete
*/
void env_delete_queue(void *queue)
{
vQueueDelete(queue);
}
#ifndef __COVERAGESCANNER__
/*!
* env_put_queue
*
* Put an element in a queue.
*
* @param queue - queue to put element in
* @param msg - pointer to the message to be put into the queue
* @param timeout_ms - timeout in ms
*
* @return - status of function execution
*/
int32_t env_put_queue(void *queue, void *msg, uintptr_t timeout_ms)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (env_in_isr() != 0)
{
if (xQueueSendFromISR(queue, msg, &xHigherPriorityTaskWoken) == pdPASS)
{
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
return 1;
}
}
else
{
if (xQueueSend(queue, msg, ((portMAX_DELAY == timeout_ms) ? portMAX_DELAY : timeout_ms / portTICK_PERIOD_MS)) ==
pdPASS)
{
return 1;
}
}
return 0;
}
/*!
* env_get_queue
*
* Get an element out of a queue.
*
* @param queue - queue to get element from
* @param msg - pointer to a memory to save the message
* @param timeout_ms - timeout in ms
*
* @return - status of function execution
*/
int32_t env_get_queue(void *queue, void *msg, uintptr_t timeout_ms)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (env_in_isr() != 0)
{
if (xQueueReceiveFromISR(queue, msg, &xHigherPriorityTaskWoken) == pdPASS)
{
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
return 1;
}
}
else
{
if (xQueueReceive(queue, msg,
((portMAX_DELAY == timeout_ms) ? portMAX_DELAY : timeout_ms / portTICK_PERIOD_MS)) == pdPASS)
{
return 1;
}
}
return 0;
}
#endif /* __COVERAGESCANNER__ */
/*!
* env_get_current_queue_size
*
* Get current queue size.
*
* @param queue - queue pointer
*
* @return - Number of queued items in the queue
*/
int32_t env_get_current_queue_size(void *queue)
{
UBaseType_t messages = 0;
if (env_in_isr() != 0)
{
messages = uxQueueMessagesWaitingFromISR(queue);
}
else
{
messages = uxQueueMessagesWaiting(queue);
}
return (messages > INT32_MAX) ? INT32_MAX : (int32_t)messages;
}

View File

@@ -0,0 +1,172 @@
/*-
* Copyright Rusty Russell IBM Corporation 2007.
* Copyright 2019,2022 NXP
* This header is BSD licensed so anyone can use the definitions to implement
* compatible drivers/servers.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of IBM nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef VIRTIO_RING_H
#define VIRTIO_RING_H
/* This marks a buffer as continuing via the next field. */
#define VRING_DESC_F_NEXT 1U
/* This marks a buffer as write-only (otherwise read-only). */
#define VRING_DESC_F_WRITE 2U
/* This means the buffer contains a list of buffer descriptors. */
#define VRING_DESC_F_INDIRECT 4U
/* The Host uses this in used->flags to advise the Guest: don't kick me
* when you add a buffer. It's unreliable, so it's simply an
* optimization. Guest will still kick if it's out of buffers. */
#define VRING_USED_F_NO_NOTIFY 1U
/* The Guest uses this in avail->flags to advise the Host: don't
* interrupt me when you consume a buffer. It's unreliable, so it's
* simply an optimization. */
#define VRING_AVAIL_F_NO_INTERRUPT 1U
/* VirtIO ring descriptors: 16 bytes.
* These can chain together via "next". */
struct vring_desc
{
/* Address (guest-physical). */
uint64_t addr;
/* Length. */
uint32_t len;
/* The flags as indicated above. */
uint16_t flags;
/* We chain unused descriptors via this, too. */
uint16_t next;
};
struct vring_avail
{
uint16_t flags;
uint16_t idx;
uint16_t ring[1];
};
/* uint32_t is used here for ids for padding reasons. */
struct vring_used_elem
{
/* Index of start of used descriptor chain. */
uint32_t id;
/* Total length of the descriptor chain which was written to. */
uint32_t len;
};
struct vring_used
{
uint16_t flags;
uint16_t idx;
struct vring_used_elem ring[1];
};
struct vring
{
uint32_t num;
struct vring_desc *desc;
struct vring_avail *avail;
struct vring_used *used;
};
/* The standard layout for the ring is a continuous chunk of memory which
* looks like this. We assume num is a power of 2.
*
* struct vring {
* # The actual descriptors (16 bytes each)
* struct vring_desc desc[num];
*
* # A ring of available descriptor heads with free-running index.
* __u16 avail_flags;
* __u16 avail_idx;
* __u16 available[num];
* __u16 used_event_idx;
*
* # Padding to the next align boundary.
* char pad[];
*
* # A ring of used descriptor heads with free-running index.
* __u16 used_flags;
* __u16 used_idx;
* struct vring_used_elem used[num];
* __u16 avail_event_idx;
* };
*
* NOTE: for VirtIO PCI, align is 4096.
*/
/*
* We publish the used event index at the end of the available ring, and vice
* versa. They are at the end for backwards compatibility.
*/
#define vring_used_event(vr) ((vr)->avail->ring[(vr)->num])
#define vring_avail_event(vr) ((vr)->used->ring[(vr)->num].id)
static inline int32_t vring_size(uint32_t num, uint32_t align)
{
uint32_t size;
size = num * sizeof(struct vring_desc);
size += sizeof(struct vring_avail) + (num * sizeof(uint16_t)) + sizeof(uint16_t);
size = (size + align - 1UL) & ~(align - 1UL);
size += sizeof(struct vring_used) + (num * sizeof(struct vring_used_elem)) + sizeof(uint16_t);
return ((int32_t)size);
}
static inline void vring_init(struct vring *vr, uint32_t num, uint8_t *p, uint32_t align)
{
vr->num = num;
vr->desc = (struct vring_desc *)(void *)p;
vr->avail = (struct vring_avail *)(void *)(p + num * sizeof(struct vring_desc));
vr->used = (struct vring_used *)(((uintptr_t)&vr->avail->ring[num] + align - 1UL) & ~(align - 1UL));
}
/*
* The following is used with VIRTIO_RING_F_EVENT_IDX.
*
* Assuming a given event_idx value from the other size, if we have
* just incremented index from old to new_idx, should we trigger an
* event?
*/
static inline int32_t vring_need_event(uint16_t event_idx, uint16_t new_idx, uint16_t old)
{
/* coco begin validated: This function does not need to be tested because it is not used in rpmsg_lite
* implementation (only called from unused part of vq_ring_must_notify_host() ). */
if (((uint16_t)new_idx - (uint16_t)event_idx - (uint16_t)1U) < ((uint16_t)new_idx - (uint16_t)old))
{
return 1;
}
else
{
return 0;
}
}
/* coco end */
#endif /* VIRTIO_RING_H */

View File

@@ -0,0 +1,831 @@
/*-
* Copyright (c) 2011, Bryan Venteicher <bryanv@FreeBSD.org>
* Copyright (c) 2016 Freescale Semiconductor, Inc.
* Copyright 2016-2024 NXP
* All rights reserved.
*
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice unmodified, this list of conditions, and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "virtqueue.h"
/* Prototype for internal functions. */
static void vq_ring_update_avail(struct virtqueue *vq, uint16_t desc_idx);
static void vq_ring_update_used(struct virtqueue *vq, uint16_t head_idx, uint32_t len);
static uint16_t vq_ring_add_buffer(
struct virtqueue *vq, struct vring_desc *desc, uint16_t head_idx, void *buffer, uint32_t length);
static int32_t vq_ring_enable_interrupt(struct virtqueue *vq, uint16_t ndesc);
static int32_t vq_ring_must_notify_host(struct virtqueue *vq);
static void vq_ring_notify_host(struct virtqueue *vq);
static uint16_t virtqueue_nused(struct virtqueue *vq);
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
/*!
* virtqueue_create_static - Creates new VirtIO queue - static version
*
* @param id - VirtIO queue ID , must be unique
* @param name - Name of VirtIO queue
* @param ring - Pointer to vring_alloc_info control block
* @param callback - Pointer to callback function, invoked
* when message is available on VirtIO queue
* @param notify - Pointer to notify function, used to notify
* other side that there is job available for it
* @param v_queue - Created VirtIO queue.
* @param vq_ctxt - Statically allocated virtqueue context
*
* @return - Function status
*/
int32_t virtqueue_create_static(uint16_t id,
const char *name,
struct vring_alloc_info *ring,
void (*callback_fc)(struct virtqueue *vq),
void (*notify_fc)(struct virtqueue *vq),
struct virtqueue **v_queue,
struct vq_static_context *vq_ctxt)
{
struct virtqueue *vq = VQ_NULL;
volatile int32_t status = VQUEUE_SUCCESS;
uint32_t vq_size = 0U;
VQ_PARAM_CHK(vq_ctxt == VQ_NULL, status, ERROR_VQUEUE_INVLD_PARAM);
VQ_PARAM_CHK(ring == VQ_NULL, status, ERROR_VQUEUE_INVLD_PARAM);
VQ_PARAM_CHK(ring->num_descs == 0U, status, ERROR_VQUEUE_INVLD_PARAM);
VQ_PARAM_CHK(ring->num_descs & (ring->num_descs - 1U), status, ERROR_VRING_ALIGN);
VQ_PARAM_CHK(ring->align > INT32_MAX, status, ERROR_VQUEUE_INVLD_PARAM);
if (status == VQUEUE_SUCCESS)
{
vq_size = sizeof(struct virtqueue);
vq = &vq_ctxt->vq;
env_memset(vq, 0x00, vq_size);
env_strncpy(vq->vq_name, name, VIRTQUEUE_MAX_NAME_SZ);
vq->vq_queue_index = id;
vq->vq_alignment = (int32_t)(ring->align);
vq->vq_nentries = ring->num_descs;
vq->callback_fc = callback_fc;
vq->notify_fc = notify_fc;
// indirect addition is not supported
vq->vq_ring_size = vring_size(ring->num_descs, ring->align);
vq->vq_ring_mem = (void *)ring->phy_addr;
vring_init(&vq->vq_ring, vq->vq_nentries, vq->vq_ring_mem, (uint32_t)vq->vq_alignment);
/* Cache flush initialized virt queue ring pointers */
VQUEUE_FLUSH(vq->vq_ring.avail, sizeof(struct vring_avail));
VQUEUE_FLUSH(vq->vq_ring.used, sizeof(struct vring_used));
*v_queue = vq;
}
return (status);
}
#else
/*!
* virtqueue_create - Creates new VirtIO queue
*
* @param id - VirtIO queue ID , must be unique
* @param name - Name of VirtIO queue
* @param ring - Pointer to vring_alloc_info control block
* @param callback - Pointer to callback function, invoked
* when message is available on VirtIO queue
* @param notify - Pointer to notify function, used to notify
* other side that there is job available for it
* @param v_queue - Created VirtIO queue.
*
* @return - Function status
*/
int32_t virtqueue_create(uint16_t id,
const char *name,
struct vring_alloc_info *ring,
void (*callback_fc)(struct virtqueue *vq),
void (*notify_fc)(struct virtqueue *vq),
struct virtqueue **v_queue)
{
struct virtqueue *vq = VQ_NULL;
volatile int32_t status = VQUEUE_SUCCESS;
uint32_t vq_size = 0U;
VQ_PARAM_CHK(ring == VQ_NULL, status, ERROR_VQUEUE_INVLD_PARAM);
VQ_PARAM_CHK(ring->num_descs == 0U, status, ERROR_VQUEUE_INVLD_PARAM);
VQ_PARAM_CHK(ring->num_descs & (ring->num_descs - 1U), status, ERROR_VRING_ALIGN);
VQ_PARAM_CHK(ring->align > INT32_MAX, status, ERROR_VQUEUE_INVLD_PARAM);
if (status == VQUEUE_SUCCESS)
{
vq_size = sizeof(struct virtqueue);
vq = (struct virtqueue *)env_allocate_memory(vq_size);
if (vq == VQ_NULL)
{
return (ERROR_NO_MEM);
}
env_memset(vq, 0x00, vq_size);
env_strncpy(vq->vq_name, name, VIRTQUEUE_MAX_NAME_SZ);
vq->vq_queue_index = id;
vq->vq_alignment = (int32_t)(ring->align);
vq->vq_nentries = ring->num_descs;
vq->callback_fc = callback_fc;
vq->notify_fc = notify_fc;
// indirect addition is not supported
vq->vq_ring_size = vring_size(ring->num_descs, ring->align);
vq->vq_ring_mem = (void *)ring->phy_addr;
vring_init(&vq->vq_ring, vq->vq_nentries, vq->vq_ring_mem, (uint32_t)vq->vq_alignment);
/* Cache flush initialized virt queue ring pointers */
VQUEUE_FLUSH(vq->vq_ring.avail, sizeof(struct vring_avail));
VQUEUE_FLUSH(vq->vq_ring.used, sizeof(struct vring_used));
*v_queue = vq;
}
return (status);
}
#endif /* RL_USE_STATIC_API */
/*!
* virtqueue_add_buffer() - Enqueues new buffer in vring for consumption
* by other side.
*
* @param vq - Pointer to VirtIO queue control block.
* @param head_idx - Index of buffer to be added to the avail ring
*
* @return - Function status
*/
int32_t virtqueue_add_buffer(struct virtqueue *vq, uint16_t head_idx)
{
volatile int32_t status = VQUEUE_SUCCESS;
VQ_PARAM_CHK(vq == VQ_NULL, status, ERROR_VQUEUE_INVLD_PARAM);
VQUEUE_BUSY(vq, avail_write);
if (status == VQUEUE_SUCCESS)
{
VQ_RING_ASSERT_VALID_IDX(vq, head_idx);
/*
* Update vring_avail control block fields so that other
* side can get buffer using it.
*/
vq_ring_update_avail(vq, head_idx);
}
VQUEUE_IDLE(vq, avail_write);
return (status);
}
/*!
* virtqueue_fill_avail_buffers - Enqueues single buffer in vring, updates avail
*
* @param vq - Pointer to VirtIO queue control block
* @param buffer - Address of buffer
* @param len - Length of buffer
*
* @return - Function status
*/
int32_t virtqueue_fill_avail_buffers(struct virtqueue *vq, void *buffer, uint32_t len)
{
struct vring_desc *dp;
uint16_t head_idx;
volatile int32_t status = VQUEUE_SUCCESS;
VQ_PARAM_CHK(vq == VQ_NULL, status, ERROR_VQUEUE_INVLD_PARAM);
VQUEUE_BUSY(vq, avail_write);
if (status == VQUEUE_SUCCESS)
{
head_idx = vq->vq_desc_head_idx;
dp = &vq->vq_ring.desc[head_idx];
#if defined(RL_USE_ENVIRONMENT_CONTEXT) && (RL_USE_ENVIRONMENT_CONTEXT == 1)
dp->addr = env_map_vatopa(vq->env, buffer);
#else
dp->addr = env_map_vatopa(buffer);
#endif
dp->len = len;
dp->flags = VRING_DESC_F_WRITE;
VQUEUE_FLUSH(&vq->vq_ring.desc[head_idx], sizeof(vq->vq_ring.desc[head_idx]));
vq->vq_desc_head_idx++;
vq_ring_update_avail(vq, head_idx);
}
VQUEUE_IDLE(vq, avail_write);
return (status);
}
/*!
* virtqueue_get_buffer - Returns used buffers from VirtIO queue
*
* @param vq - Pointer to VirtIO queue control block
* @param len - Length of consumed buffer
* @param idx - Index to buffer descriptor pool
*
* @return - Pointer to used buffer
*/
void *virtqueue_get_buffer(struct virtqueue *vq, uint32_t *len, uint16_t *idx)
{
struct vring_used_elem *uep;
uint16_t used_idx, desc_idx;
/* Invalidate used->idx before it is read */
VQUEUE_INVALIDATE(&vq->vq_ring.used->idx, sizeof(vq->vq_ring.used->idx));
if ((vq == VQ_NULL) || (vq->vq_used_cons_idx == vq->vq_ring.used->idx))
{
return (VQ_NULL);
}
VQUEUE_BUSY(vq, used_read);
used_idx = (uint16_t)(vq->vq_used_cons_idx & ((uint16_t)(vq->vq_nentries - 1U)));
uep = &vq->vq_ring.used->ring[used_idx];
env_rmb();
/* Invalidate used->ring before it is read */
VQUEUE_INVALIDATE(&vq->vq_ring.used->ring[used_idx], sizeof(vq->vq_ring.used->ring[used_idx]));
desc_idx = (uint16_t)uep->id;
if (len != VQ_NULL)
{
*len = uep->len;
}
if (idx != VQ_NULL)
{
*idx = desc_idx;
}
vq->vq_used_cons_idx++;
VQUEUE_IDLE(vq, used_read);
#if defined(RL_USE_ENVIRONMENT_CONTEXT) && (RL_USE_ENVIRONMENT_CONTEXT == 1)
return env_map_patova(vq->env, ((uint32_t)(vq->vq_ring.desc[desc_idx].addr)));
#else
return env_map_patova((uint32_t)(vq->vq_ring.desc[desc_idx].addr));
#endif
}
/*!
* virtqueue_get_buffer_length - Returns size of a buffer
*
* @param vq - Pointer to VirtIO queue control block
* @param idx - Index to buffer descriptor pool
*
* @return - Buffer length
*/
uint32_t virtqueue_get_buffer_length(struct virtqueue *vq, uint16_t idx)
{
/* Invalidate used->ring before it is read */
VQUEUE_INVALIDATE(&vq->vq_ring.desc[idx].len, sizeof(vq->vq_ring.desc[idx].len));
return vq->vq_ring.desc[idx].len;
}
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
/*!
* virtqueue_free - Frees VirtIO queue resources - static version
*
* @param vq - Pointer to VirtIO queue control block
*
*/
void virtqueue_free_static(struct virtqueue *vq)
{
if (vq != VQ_NULL)
{
if (vq->vq_ring_mem != VQ_NULL)
{
vq->vq_ring_size = 0;
vq->vq_ring_mem = VQ_NULL;
}
}
}
#else
/*!
* virtqueue_free - Frees VirtIO queue resources
*
* @param vq - Pointer to VirtIO queue control block
*
*/
void virtqueue_free(struct virtqueue *vq)
{
if (vq != VQ_NULL)
{
if (vq->vq_ring_mem != VQ_NULL)
{
vq->vq_ring_size = 0;
vq->vq_ring_mem = VQ_NULL;
}
env_free_memory(vq);
}
}
#endif /* RL_USE_STATIC_API */
/*!
* virtqueue_get_available_buffer - Returns buffer available for use in the
* VirtIO queue
*
* @param vq - Pointer to VirtIO queue control block
* @param avail_idx - Pointer to index used in vring desc table
* @param len - Length of buffer
*
* @return - Pointer to available buffer
*/
void *virtqueue_get_available_buffer(struct virtqueue *vq, uint16_t *avail_idx, uint32_t *len)
{
uint16_t head_idx = 0;
void *buffer;
/* Invalidate avail->idx before it is read */
VQUEUE_INVALIDATE(&vq->vq_ring.avail->idx, sizeof(vq->vq_ring.avail->idx));
if (vq->vq_available_idx == vq->vq_ring.avail->idx)
{
return (VQ_NULL);
}
VQUEUE_BUSY(vq, avail_read);
head_idx = (uint16_t)(vq->vq_available_idx++ & ((uint16_t)(vq->vq_nentries - 1U)));
/* Invalidate avail->ring before it is read */
VQUEUE_INVALIDATE(&vq->vq_ring.avail->ring[head_idx], sizeof(vq->vq_ring.avail->ring[head_idx]));
*avail_idx = vq->vq_ring.avail->ring[head_idx];
env_rmb();
#if defined(RL_USE_ENVIRONMENT_CONTEXT) && (RL_USE_ENVIRONMENT_CONTEXT == 1)
buffer = env_map_patova(vq->env, ((uint32_t)(vq->vq_ring.desc[*avail_idx].addr)));
#else
buffer = env_map_patova((uint32_t)(vq->vq_ring.desc[*avail_idx].addr));
#endif
*len = vq->vq_ring.desc[*avail_idx].len;
VQUEUE_IDLE(vq, avail_read);
return (buffer);
}
/*!
* virtqueue_add_consumed_buffer - Returns consumed buffer back to VirtIO queue
*
* @param vq - Pointer to VirtIO queue control block
* @param head_idx - Index of vring desc containing used buffer
* @param len - Length of buffer
*
* @return - Function status
*/
int32_t virtqueue_add_consumed_buffer(struct virtqueue *vq, uint16_t head_idx, uint32_t len)
{
if (head_idx > vq->vq_nentries)
{
return (ERROR_VRING_NO_BUFF);
}
VQUEUE_BUSY(vq, used_write);
vq_ring_update_used(vq, head_idx, len);
VQUEUE_IDLE(vq, used_write);
return (VQUEUE_SUCCESS);
}
/*!
* virtqueue_fill_used_buffers - Fill used buffer ring
*
* @param vq - Pointer to VirtIO queue control block
* @param buffer - Buffer to add
* @param len - Length of buffer
*
* @return - Function status
*/
int32_t virtqueue_fill_used_buffers(struct virtqueue *vq, void *buffer, uint32_t len)
{
uint16_t head_idx;
uint16_t idx;
VQUEUE_BUSY(vq, used_write);
head_idx = vq->vq_desc_head_idx;
VQ_RING_ASSERT_VALID_IDX(vq, head_idx);
/* Enqueue buffer onto the ring. */
idx = vq_ring_add_buffer(vq, vq->vq_ring.desc, head_idx, buffer, len);
vq->vq_desc_head_idx = idx;
vq_ring_update_used(vq, head_idx, len);
VQUEUE_IDLE(vq, used_write);
return (VQUEUE_SUCCESS);
}
/*!
* virtqueue_enable_cb - Enables callback generation
*
* @param vq - Pointer to VirtIO queue control block
*
* @return - Function status
*/
int32_t virtqueue_enable_cb(struct virtqueue *vq)
{
/* coco begin validated: This virtqueue function does not need to be tested because it is not used in rpmsg_lite
* implementation */
return (vq_ring_enable_interrupt(vq, 0));
}
/* coco end */
/*!
* virtqueue_enable_cb - Disables callback generation
*
* @param vq - Pointer to VirtIO queue control block
*
*/
void virtqueue_disable_cb(struct virtqueue *vq)
{
VQUEUE_BUSY(vq, avail_write);
if ((vq->vq_flags & VIRTQUEUE_FLAG_EVENT_IDX) != 0UL)
{
/* coco begin validated: This part does not need to be tested because VIRTQUEUE_FLAG_EVENT_IDX is not being
* utilized in rpmsg_lite implementation */
vring_used_event(&vq->vq_ring) = vq->vq_used_cons_idx - vq->vq_nentries - 1U;
VQUEUE_FLUSH(&vring_used_event(&vq->vq_ring), sizeof(vring_used_event(&vq->vq_ring)));
}
/* coco end */
else
{
vq->vq_ring.avail->flags |= (uint16_t)VRING_AVAIL_F_NO_INTERRUPT;
VQUEUE_FLUSH(&vq->vq_ring.avail->flags, sizeof(vq->vq_ring.avail->flags));
}
VQUEUE_IDLE(vq, avail_write);
}
/*!
* virtqueue_kick - Notifies other side that there is buffer available for it.
*
* @param vq - Pointer to VirtIO queue control block
*/
void virtqueue_kick(struct virtqueue *vq)
{
VQUEUE_BUSY(vq, avail_write);
/* Ensure updated avail->idx is visible to host. */
env_mb();
if (0 != vq_ring_must_notify_host(vq))
{
vq_ring_notify_host(vq);
}
vq->vq_queued_cnt = 0;
VQUEUE_IDLE(vq, avail_write);
}
/*!
* virtqueue_dump Dumps important virtqueue fields , use for debugging purposes
*
* @param vq - Pointer to VirtIO queue control block
*/
void virtqueue_dump(struct virtqueue *vq)
{
/* coco begin validated: This virtqueue function does not need to be tested because it is not used in rpmsg_lite
* implementation */
if (vq == VQ_NULL)
{
return;
}
/* Invalidate avail and used before read */
VQUEUE_INVALIDATE(vq->vq_ring.avail, sizeof(struct vring_avail));
VQUEUE_INVALIDATE(vq->vq_ring.used, sizeof(struct vring_used));
env_print(
"VQ: %s - size=%d; used=%d; queued=%d; "
"desc_head_idx=%d; avail.idx=%d; used_cons_idx=%d; "
"used.idx=%d; avail.flags=0x%x; used.flags=0x%x\r\n",
vq->vq_name, vq->vq_nentries, virtqueue_nused(vq), vq->vq_queued_cnt, vq->vq_desc_head_idx,
vq->vq_ring.avail->idx, vq->vq_used_cons_idx, vq->vq_ring.used->idx, vq->vq_ring.avail->flags,
vq->vq_ring.used->flags);
}
/* coco end */
/*!
* virtqueue_get_desc_size - Returns vring descriptor size
*
* @param vq - Pointer to VirtIO queue control block
*
* @return - Descriptor length
*/
uint32_t virtqueue_get_desc_size(struct virtqueue *vq)
{
/* coco begin validated: This virtqueue function does not need to be tested because it is not used in rpmsg_lite
* implementation */
uint16_t head_idx;
uint16_t avail_idx;
uint32_t len;
/* Invalidate avail->idx before read */
VQUEUE_INVALIDATE(&vq->vq_ring.avail->idx, sizeof(vq->vq_ring.avail->idx));
if (vq->vq_available_idx == vq->vq_ring.avail->idx)
{
return 0;
}
head_idx = (uint16_t)(vq->vq_available_idx & ((uint16_t)(vq->vq_nentries - 1U)));
/* Invalidate avail->ring before read */
VQUEUE_INVALIDATE(&vq->vq_ring.avail->ring[head_idx], sizeof(vq->vq_ring.avail->ring[head_idx]));
avail_idx = vq->vq_ring.avail->ring[head_idx];
/* Invalidate len before read */
VQUEUE_INVALIDATE(&vq->vq_ring.desc[avail_idx].len, sizeof(vq->vq_ring.desc[avail_idx].len));
len = vq->vq_ring.desc[avail_idx].len;
return (len);
}
/* coco end */
/**************************************************************************
* Helper Functions *
**************************************************************************/
/*!
*
* vq_ring_add_buffer
*
*/
static uint16_t vq_ring_add_buffer(
struct virtqueue *vq, struct vring_desc *desc, uint16_t head_idx, void *buffer, uint32_t length)
{
struct vring_desc *dp;
if (buffer == VQ_NULL)
{
return head_idx; /* coco validated: line never reached, vq_ring_add_buffer() is called from
rpmsg_lite_master_init() only and the buffer parameter not being null check is done before
passing the parameter */
}
VQASSERT(vq, head_idx != VQ_RING_DESC_CHAIN_END, "premature end of free desc chain");
dp = &desc[head_idx];
#if defined(RL_USE_ENVIRONMENT_CONTEXT) && (RL_USE_ENVIRONMENT_CONTEXT == 1)
dp->addr = env_map_vatopa(vq->env, buffer);
#else
dp->addr = env_map_vatopa(buffer);
#endif
dp->len = length;
dp->flags = VRING_DESC_F_WRITE;
/* Flush desc after write */
VQUEUE_FLUSH(&desc[head_idx], sizeof(desc[head_idx]));
return (head_idx + 1U);
}
/*!
*
* vq_ring_init
*
*/
void vq_ring_init(struct virtqueue *vq)
{
struct vring *vr;
uint32_t i, size;
size = (uint32_t)(vq->vq_nentries);
vr = &vq->vq_ring;
for (i = 0U; i < size - 1U; i++)
{
vr->desc[i].next = (uint16_t)(i + 1U);
}
vr->desc[i].next = (uint16_t)VQ_RING_DESC_CHAIN_END;
}
/*!
*
* vq_ring_update_avail
*
*/
static void vq_ring_update_avail(struct virtqueue *vq, uint16_t desc_idx)
{
uint16_t avail_idx;
/*
* Place the head of the descriptor chain into the next slot and make
* it usable to the host. The chain is made available now rather than
* deferring to virtqueue_notify() in the hopes that if the host is
* currently running on another CPU, we can keep it processing the new
* descriptor.
*/
/* Invalidate avail->idx before read */
VQUEUE_INVALIDATE(&vq->vq_ring.avail->idx, sizeof(vq->vq_ring.avail->idx));
avail_idx = (uint16_t)(vq->vq_ring.avail->idx & ((uint16_t)(vq->vq_nentries - 1U)));
vq->vq_ring.avail->ring[avail_idx] = desc_idx;
/* Flush avail->ring after write */
VQUEUE_FLUSH(&vq->vq_ring.avail->ring[avail_idx], sizeof(vq->vq_ring.avail->ring[avail_idx]));
env_wmb();
vq->vq_ring.avail->idx++;
/* Flush idx after write */
VQUEUE_FLUSH(&vq->vq_ring.avail->idx, sizeof(vq->vq_ring.avail->idx));
/* Keep pending count until virtqueue_notify(). */
vq->vq_queued_cnt++;
}
/*!
*
* vq_ring_update_used
*
*/
static void vq_ring_update_used(struct virtqueue *vq, uint16_t head_idx, uint32_t len)
{
uint16_t used_idx;
struct vring_used_elem *used_desc = VQ_NULL;
/*
* Place the head of the descriptor chain into the next slot and make
* it usable to the host. The chain is made available now rather than
* deferring to virtqueue_notify() in the hopes that if the host is
* currently running on another CPU, we can keep it processing the new
* descriptor.
*/
/* Invalidate used->idx before read */
VQUEUE_INVALIDATE(&vq->vq_ring.used->idx, sizeof(vq->vq_ring.used->idx));
used_idx = vq->vq_ring.used->idx & (vq->vq_nentries - 1U);
used_desc = &(vq->vq_ring.used->ring[used_idx]);
used_desc->id = head_idx;
used_desc->len = len;
/* Flush used->ring after write */
VQUEUE_FLUSH(&(vq->vq_ring.used->ring[used_idx]), sizeof(vq->vq_ring.used->ring[used_idx]));
env_wmb();
vq->vq_ring.used->idx++;
/* Flush used->idx after write */
VQUEUE_FLUSH(&vq->vq_ring.used->idx, sizeof(vq->vq_ring.used->idx));
}
/*!
*
* vq_ring_enable_interrupt
*
*/
static int32_t vq_ring_enable_interrupt(struct virtqueue *vq, uint16_t ndesc)
{
/* coco begin validated: This virtqueue function does not need to be tested because it is not used in rpmsg_lite
* implementation */
/*
* Enable interrupts, making sure we get the latest index of
* what's already been consumed.
*/
if ((vq->vq_flags & VIRTQUEUE_FLAG_EVENT_IDX) != 0UL)
{
vring_used_event(&vq->vq_ring) = vq->vq_used_cons_idx + ndesc;
VQUEUE_FLUSH(&vring_used_event(&vq->vq_ring), sizeof(vring_used_event(&vq->vq_ring)));
}
else
{
vq->vq_ring.avail->flags &= ~(uint16_t)VRING_AVAIL_F_NO_INTERRUPT;
VQUEUE_FLUSH(&vq->vq_ring.avail->flags, sizeof(vq->vq_ring.avail->flags));
}
env_mb();
/*
* Enough items may have already been consumed to meet our threshold
* since we last checked. Let our caller know so it processes the new
* entries.
*/
if (virtqueue_nused(vq) > ndesc)
{
return (1);
}
return (0);
}
/* coco end */
/*!
*
* virtqueue_interrupt
*
*/
void virtqueue_notification(struct virtqueue *vq)
{
if (vq != VQ_NULL)
{
if (vq->callback_fc != VQ_NULL)
{
vq->callback_fc(vq);
}
}
}
/*!
*
* vq_ring_must_notify_host
*
*/
static int32_t vq_ring_must_notify_host(struct virtqueue *vq)
{
uint16_t new_idx, prev_idx;
uint16_t event_idx;
if ((vq->vq_flags & VIRTQUEUE_FLAG_EVENT_IDX) != 0UL)
{
/* coco begin validated: This part does not need to be tested because VIRTQUEUE_FLAG_EVENT_IDX is not being
* utilized in rpmsg_lite implementation */
/* Invalidate avail->idx before read */
VQUEUE_INVALIDATE(&vq->vq_ring.avail->idx, sizeof(vq->vq_ring.avail->idx));
new_idx = vq->vq_ring.avail->idx;
prev_idx = new_idx - vq->vq_queued_cnt;
VQUEUE_INVALIDATE(&vring_avail_event(&vq->vq_ring), sizeof(vring_avail_event(&vq->vq_ring)));
event_idx = (uint16_t)vring_avail_event(&vq->vq_ring);
return ((vring_need_event(event_idx, new_idx, prev_idx) != 0) ? 1 : 0);
}
/* coco end */
/* Invalidate flags before read */
VQUEUE_INVALIDATE(&vq->vq_ring.used->flags, sizeof(vq->vq_ring.used->flags));
return (((vq->vq_ring.used->flags & ((uint16_t)VRING_USED_F_NO_NOTIFY)) == 0U) ? 1 : 0);
}
/*!
*
* vq_ring_notify_host
*
*/
static void vq_ring_notify_host(struct virtqueue *vq)
{
if (vq->notify_fc != VQ_NULL)
{
vq->notify_fc(vq);
}
}
/*!
*
* virtqueue_nused
*
*/
static uint16_t virtqueue_nused(struct virtqueue *vq)
{
/* coco begin validated: This virtqueue function does not need to be tested because it is not used in rpmsg_lite
* implementation */
uint16_t used_idx, nused;
/* Invalidate used-idx before read */
VQUEUE_INVALIDATE(&vq->vq_ring.used->idx, sizeof(vq->vq_ring.used->idx));
used_idx = vq->vq_ring.used->idx;
nused = (uint16_t)(used_idx - vq->vq_used_cons_idx);
VQASSERT(vq, nused <= vq->vq_nentries, "used more than available");
return (nused);
}
/* coco end */

View File

@@ -0,0 +1,267 @@
#ifndef VIRTQUEUE_H_
#define VIRTQUEUE_H_
/*-
* Copyright (c) 2011, Bryan Venteicher <bryanv@FreeBSD.org>
* Copyright (c) 2016 Freescale Semiconductor, Inc.
* Copyright 2016-2019 NXP
* Copyright (C) 2025 LISTENAI, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice unmodified, this list of conditions, and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <stdbool.h>
#include <stdint.h>
typedef uint8_t boolean;
#include "virtio_ring.h"
#include "utils/list.h"
/*Error Codes*/
#define VQ_ERROR_BASE (-3000)
#define ERROR_VRING_FULL (VQ_ERROR_BASE - 1)
#define ERROR_INVLD_DESC_IDX (VQ_ERROR_BASE - 2)
#define ERROR_EMPTY_RING (VQ_ERROR_BASE - 3)
#define ERROR_NO_MEM (VQ_ERROR_BASE - 4)
#define ERROR_VRING_MAX_DESC (VQ_ERROR_BASE - 5)
#define ERROR_VRING_ALIGN (VQ_ERROR_BASE - 6)
#define ERROR_VRING_NO_BUFF (VQ_ERROR_BASE - 7)
#define ERROR_VQUEUE_INVLD_PARAM (VQ_ERROR_BASE - 8)
#define VQUEUE_SUCCESS (0)
#define VQUEUE_DEBUG (false)
#define RL_USE_DCACHE (1)
/* This is temporary macro to replace C NULL support.
* At the moment all the RTL specific functions are present in env.
* */
#define VQ_NULL ((void *)0)
/* The maximum virtqueue size is 2^15. Use that value as the end of
* descriptor chain terminator since it will never be a valid index
* in the descriptor table. This is used to verify we are correctly
* handling vq_free_cnt.
*/
#define VQ_RING_DESC_CHAIN_END (32768)
#define VIRTQUEUE_FLAG_INDIRECT (0x0001U)
#define VIRTQUEUE_FLAG_EVENT_IDX (0x0002U)
#define VIRTQUEUE_MAX_NAME_SZ (32) /* mind the alignment */
/* Support for indirect buffer descriptors. */
#define VIRTIO_RING_F_INDIRECT_DESC (1 << 28)
/* Support to suppress interrupt until specific index is reached. */
#define VIRTIO_RING_F_EVENT_IDX (1 << 29)
#if defined(RL_USE_DCACHE) && (RL_USE_DCACHE == 1)
#define VQUEUE_FLUSH(x, s) env_cache_flush(x, s)
#define VQUEUE_INVALIDATE(x, s) env_cache_invalidate(x, s)
#else
#define VQUEUE_FLUSH(x, s)
#define VQUEUE_INVALIDATE(x, s)
#endif /* RL_USE_DCACHE */
/*
* Hint on how long the next interrupt should be postponed. This is
* only used when the EVENT_IDX feature is negotiated.
*/
typedef enum
{
VQ_POSTPONE_SHORT,
VQ_POSTPONE_LONG,
VQ_POSTPONE_EMPTIED /* Until all available desc are used. */
} vq_postpone_t;
/* local virtqueue representation, not in shared memory */
struct virtqueue
{
/* 32bit aligned { */
char vq_name[VIRTQUEUE_MAX_NAME_SZ];
uint32_t vq_flags;
int32_t vq_alignment;
int32_t vq_ring_size;
void *vq_ring_mem;
void (*callback_fc)(struct virtqueue *vq);
void (*notify_fc)(struct virtqueue *vq);
int32_t vq_max_indirect_size;
int32_t vq_indirect_mem_size;
struct vring vq_ring;
/* } 32bit aligned */
/* 16bit aligned { */
uint16_t vq_queue_index;
uint16_t vq_nentries;
uint16_t vq_free_cnt;
uint16_t vq_queued_cnt;
/*
* Head of the free chain in the descriptor table. If
* there are no free descriptors, this will be set to
* VQ_RING_DESC_CHAIN_END.
*/
uint16_t vq_desc_head_idx;
/*
* Last consumed descriptor in the used table,
* trails vq_ring.used->idx.
*/
uint16_t vq_used_cons_idx;
/*
* Last consumed descriptor in the available table -
* used by the consumer side.
*/
uint16_t vq_available_idx;
/* } 16bit aligned */
boolean avail_read; /* 8bit wide */
boolean avail_write; /* 8bit wide */
boolean used_read; /* 8bit wide */
boolean used_write; /* 8bit wide */
uint16_t padd; /* aligned to 32bits after this: */
void *priv; /* private pointer, upper layer instance pointer */
#if defined(RL_USE_ENVIRONMENT_CONTEXT) && (RL_USE_ENVIRONMENT_CONTEXT == 1)
void *env; /* private pointer to environment layer internal context */
#endif
};
/* struct to hold vring specific information */
struct vring_alloc_info
{
void *phy_addr;
uint32_t align;
uint16_t num_descs;
uint16_t pad;
};
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
struct vq_static_context
{
struct virtqueue vq;
};
#endif
typedef void vq_callback(struct virtqueue *vq);
typedef void vq_notify(struct virtqueue *vq);
#if (VQUEUE_DEBUG == true)
#define VQASSERT_BOOL(_vq, _exp, _msg) \
do \
{ \
if (!(_exp)) \
{ \
env_print("%s: %s - "(_msg), __func__, (_vq)->vq_name); \
while (1) \
{ \
}; \
} \
} while (0)
#define VQASSERT(_vq, _exp, _msg) VQASSERT_BOOL(_vq, (_exp) != 0, _msg)
#define VQ_RING_ASSERT_VALID_IDX(_vq, _idx) VQASSERT((_vq), (_idx) < (_vq)->vq_nentries, "invalid ring index")
#define VQ_PARAM_CHK(condition, status_var, status_err) \
if ((status_var == 0) && (condition)) \
{ \
status_var = status_err; \
}
#define VQUEUE_BUSY(vq, dir) \
if ((vq)->dir == false) \
{ \
(vq)->dir = true; \
} \
else \
{ \
VQASSERT(vq, (vq)->dir == false, "VirtQueue already in use") \
}
#define VQUEUE_IDLE(vq, dir) ((vq)->dir = false)
#else
#define KASSERT(cond, str)
#define VQASSERT(_vq, _exp, _msg)
#define VQ_RING_ASSERT_VALID_IDX(_vq, _idx)
#define VQ_PARAM_CHK(condition, status_var, status_err)
#define VQUEUE_BUSY(vq, dir)
#define VQUEUE_IDLE(vq, dir)
#endif
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
int32_t virtqueue_create_static(uint16_t id,
const char *name,
struct vring_alloc_info *ring,
void (*callback_fc)(struct virtqueue *vq),
void (*notify_fc)(struct virtqueue *vq),
struct virtqueue **v_queue,
struct vq_static_context *vq_ctxt);
#else
int32_t virtqueue_create(uint16_t id,
const char *name,
struct vring_alloc_info *ring,
void (*callback_fc)(struct virtqueue *vq),
void (*notify_fc)(struct virtqueue *vq),
struct virtqueue **v_queue);
#endif
int32_t virtqueue_add_buffer(struct virtqueue *vq, uint16_t head_idx);
int32_t virtqueue_fill_used_buffers(struct virtqueue *vq, void *buffer, uint32_t len);
int32_t virtqueue_fill_avail_buffers(struct virtqueue *vq, void *buffer, uint32_t len);
void *virtqueue_get_buffer(struct virtqueue *vq, uint32_t *len, uint16_t *idx);
void *virtqueue_get_available_buffer(struct virtqueue *vq, uint16_t *avail_idx, uint32_t *len);
int32_t virtqueue_add_consumed_buffer(struct virtqueue *vq, uint16_t head_idx, uint32_t len);
void virtqueue_disable_cb(struct virtqueue *vq);
int32_t virtqueue_enable_cb(struct virtqueue *vq);
void virtqueue_kick(struct virtqueue *vq);
#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
void virtqueue_free_static(struct virtqueue *vq);
#else
void virtqueue_free(struct virtqueue *vq);
#endif
void virtqueue_dump(struct virtqueue *vq);
void virtqueue_notification(struct virtqueue *vq);
uint32_t virtqueue_get_desc_size(struct virtqueue *vq);
uint32_t virtqueue_get_buffer_length(struct virtqueue *vq, uint16_t idx);
void vq_ring_init(struct virtqueue *vq);
#endif /* VIRTQUEUE_H_ */

View File

@@ -0,0 +1,11 @@
if (CONFIG_ACOMP_FD)
listenai_library_sources(
acomp_fd.c
)
listenai_include_directories(
./
)
endif()

View File

@@ -0,0 +1,28 @@
if ACOMP_FD
config ACOMP_FD_RES_FACE_DETECT_ADDRESS
hex "mlp face detect address"
default 0xd200000
config ACOMP_FD_RES_FACE_DETECT_LENGTH
int "mlp face detect length"
default 3546864
config ACOMP_FD_RES_FACE_ALIGN_ADDRESS
hex "mlp face align address"
default 0xcd00000
config ACOMP_FD_RES_FACE_ALIGN_LENGTH
int "mlp face align length"
default 419904
config ACOMP_FD_RES_FACE_LIVE_ADDRESS
hex "mlp face live address"
default 0xcd00000
config ACOMP_FD_RES_FACE_LIVE_LENGTH
int "mlp face live length"
default 419904
config ACOMP_FD_FACE_VERIFY_ADDRESS
hex "mlp face verify address"
default 0xcd00000
config ACOMP_FD_FACE_VERIFY_LENGTH
int "mlp face verify length"
default 419904
endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,457 @@
#include <string.h>
#include "crc32.h"
#include "ipc/acomp_ipc.h"
#include "gcl_cb_list/gcl_cb_list.h"
#include "comm/stream/acomp_stream_ipc.h"
#include "acomp_err.h"
#include "private/fd_ipc.h"
#include "acomp_fd.h"
#define TAG "acomp_fd"
#include "lisa_log.h"
#define ACOMP_FD_DEV_NAME "acomp.fd"
#define ALIGN_SIZE(len) ((len + IPC_ALIGN_SIZE - 1) / IPC_ALIGN_SIZE * IPC_ALIGN_SIZE)
typedef struct {
uint32_t dev_index;
gcl_cb_list_t event_callbacks;
// acomp_fd_result_info_t fd_info;
acomp_stream_t *stream;
} acomp_fd_handle_t;
acomp_fd_handle_t *fd_handle = NULL;
void event_callback(acomp_ipc_message_t *message, void *priv)
{
acomp_fd_handle_t *handle = (acomp_fd_handle_t *)priv;
if (handle == NULL) {
return;
}
if (message->hdr.hdr.cmd == ACOMP_CONTEXT_IPC_GLB_NOTIFY) {
if (message->acomp_cmd == ACOMP_IPC_CMD_NOTIFY_RESULT) {
if (((void *)message->address != NULL) && (message->len > 0)) {
acomp_ipc_notify_result_t *result = (acomp_ipc_notify_result_t *)message->address;
fd_ipc_notify_subcmd_fd_result_hdr_t *fd_result = (fd_ipc_notify_subcmd_fd_result_hdr_t *)result->data;
if (fd_result->results_cnt >= 0) {
// fd_handle->fd_info.results_cnt = fd_result->results_cnt;
// fd_handle->fd_info.max_area_results_index = fd_result->max_area_results_index;
// fd_handle->fd_info.results = (acomp_fd_result_t *)fd_result->results;
gcl_cb_event_dispatch(handle->event_callbacks, FD_CB_EVENT_ENGINE_RLT, fd_result, result->len);
}
}
}
}
}
int acomp_fd_init(void)
{
LISA_LOGI(TAG, "acomp fd init enter");
int ret = 0;
if (fd_handle != NULL) {
LISA_LOGI(TAG, "acomp fd_handle already exit");
return ACOMP_ERR_INVALID_STATE;
}
fd_handle = (acomp_fd_handle_t *)psram_malloc(sizeof(acomp_fd_handle_t));
if (fd_handle == NULL) {
LISA_LOGE(TAG, "acomp fd init failed! no mem");
return ACOMP_ERR_NO_MEM;
}
memset(fd_handle, 0, sizeof(acomp_fd_handle_t));
fd_handle->event_callbacks = gcl_cb_list_create();
fd_handle->dev_index = acomp_ipc_get_dev_index(ACOMP_FD_DEV_NAME);
LISA_LOGI(TAG, "acomp fd dev index %d,name:%s", fd_handle->dev_index, ACOMP_FD_DEV_NAME);
if (fd_handle->dev_index < 0) {
psram_free(fd_handle);
fd_handle = NULL;
LISA_LOGE(TAG, "acomp fd dev index not found!");
return ACOMP_ERR_NOT_FOUND;
}
LISA_LOGI(TAG, "acomp fd dev index %d,name:%s", fd_handle->dev_index, ACOMP_FD_DEV_NAME);
ret = acomp_ipc_add_callback(fd_handle->dev_index, (ipc_event_cb_t)event_callback, fd_handle);
if (ret != ACOMP_ERR_OK) {
return ret;
}
ret = acomp_ipc_build_frame_send_sync(fd_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_NEW | IPC_HEADER_REQ_REPALY, 0,
0, NULL, 0);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp fd init failed! ret:%d", ret);
return ret;
}
fd_handle->stream = acomp_stream_create(fd_handle->dev_index);
if(fd_handle->stream == NULL){
return ACOMP_ERR_CREATE_STREAM_FAILED;
}
LISA_LOGI(TAG, "acomp fd init exit");
return 0;
}
int acomp_fd_deinit(void)
{
/*TODO*/
return ACOMP_ERR_NOT_SUPPORTED;
}
int acomp_fd_prepare(void)
{
LISA_LOGI(TAG, "acomp fd prepare enter");
acomp_ipc_prepare_t *prepare;
uint32_t size;
int ret;
size = sizeof(acomp_ipc_prepare_t) + sizeof(acomp_res_item_t) * ACOMP_FD_RES_NUMBER;
size = ALIGN_SIZE(size);
prepare = psram_malloc_align(IPC_ALIGN_SIZE, size);
if (prepare == NULL) {
LISA_LOGE(TAG, "acomp fd prepare failed! no mem");
return ACOMP_ERR_NO_MEM;
}
memset(prepare, 0, size);
prepare->number = ACOMP_FD_RES_NUMBER;
prepare->item[0].index = RES_FACE_DETECT;
prepare->item[0].attr.hdr.storage = 0;
prepare->item[0].addr = CONFIG_ACOMP_FD_RES_FACE_DETECT_ADDRESS;
prepare->item[0].offset = 0;
prepare->item[0].size = CONFIG_ACOMP_FD_RES_FACE_DETECT_LENGTH;
prepare->item[1].index = RES_FACE_ALIGN;
prepare->item[1].attr.hdr.storage = 0;
prepare->item[1].addr = CONFIG_ACOMP_FD_RES_FACE_ALIGN_ADDRESS;
prepare->item[1].offset = 0;
prepare->item[1].size = CONFIG_ACOMP_FD_RES_FACE_ALIGN_LENGTH;
prepare->item[2].index = RES_FACE_LIVE;
prepare->item[2].attr.hdr.storage = 0;
prepare->item[2].addr = CONFIG_ACOMP_FD_RES_FACE_LIVE_ADDRESS;
prepare->item[2].offset = 0;
prepare->item[2].size = CONFIG_ACOMP_FD_RES_FACE_LIVE_LENGTH;
prepare->item[3].index = RES_FACE_VERIFY;
prepare->item[3].attr.hdr.storage = 0;
prepare->item[3].addr = CONFIG_ACOMP_FD_FACE_VERIFY_ADDRESS;
prepare->item[3].offset = 0;
prepare->item[3].size = CONFIG_ACOMP_FD_FACE_VERIFY_LENGTH;
LISA_LOGI(TAG, "acomp fd prepare:%p, size:%u", prepare, size);
ret = acomp_ipc_build_frame_send_sync(fd_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_PREPARE, 0, prepare, size);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp fd prepare failed! ret:%d", ret);
return ret;
}
psram_free(prepare);
LISA_LOGI(TAG, "acomp fd prepare exit");
return ret;
}
int acomp_fd_cleanup(void)
{
int ret;
ret = acomp_ipc_build_frame_send_sync(fd_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_CLEANUP, 0, NULL, 0);
return ret;
}
int acomp_fd_start(void)
{
LISA_LOGI(TAG, "acomp fd start enter");
int ret;
ret = acomp_ipc_build_frame_send_sync(fd_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_START, 0, NULL, 0);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp fd start failed! ret:%d", ret);
return ret;
}
LISA_LOGI(TAG, "acomp fd start exit");
return ret;
}
int acomp_fd_stop(void)
{
int ret;
ret = acomp_ipc_build_frame_send_sync(fd_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_STOP, 0, NULL, 0);
return ret;
}
static int acomp_fd_control_subcmd(fd_ipc_control_subcmd_e subcmd, void *data, uint32_t data_len)
{
int ret;
uint32_t size;
acomp_ipc_control_t *ipc_control;
size = sizeof(acomp_ipc_control_t) + data_len;
size = ALIGN_SIZE(size);
ipc_control = psram_malloc_align(IPC_ALIGN_SIZE, size);
if (ipc_control == NULL) {
return ACOMP_ERR_NO_MEM;
}
ipc_control->control = subcmd;
ipc_control->len = data_len;
uint8_t *ipc_data = ipc_control->data;
if (data_len > 0) {
memcpy(ipc_data, data, data_len);
}
ret = acomp_ipc_build_frame_send_sync(fd_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_CONTROL, 0, ipc_control, size);
psram_free(ipc_control);
return ret;
}
int acomp_fd_params_set(const acomp_fd_param_t *params, uint32_t params_cnt)
{
LISA_LOGI(TAG, "acomp_fd_params_set enter");
int len = sizeof(fd_ipc_control_subcmd_parameter_set_t) + sizeof(acomp_fd_param_t) * params_cnt;
fd_ipc_control_subcmd_parameter_set_t *params_set = (fd_ipc_control_subcmd_parameter_set_t *)psram_malloc(len);
if (params_set == NULL) {
return ACOMP_ERR_NO_MEM;
}
params_set->params_cnt = params_cnt;
memcpy((uint8_t*)params_set->data, (uint8_t*)params, sizeof(acomp_fd_param_t) * params_cnt);
int ret = acomp_fd_control_subcmd(FD_ICP_CONTROL_SUBCMD_PARAMETER_SET, params_set, len);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp_fd_params_set failed!");
}
psram_free(params_set);
LISA_LOGI(TAG, "acomp_fd_params_set exit");
return ret;
}
int acomp_fd_align_threshold_set(const acomp_fd_head_pose_t *threshold)
{
LISA_LOGI(TAG, "acomp_fd_align_threshold_set enter");
fd_ipc_control_subcmd_align_threshold_set_t align_threshold_set;
align_threshold_set.head_pose.yaw = threshold->yaw;
align_threshold_set.head_pose.pitch = threshold->pitch;
align_threshold_set.head_pose.roll = threshold->roll;
int ret = acomp_fd_control_subcmd(FD_ICP_CONTROL_SUBCMD_ALIGN_THRESHOLD_SET, &align_threshold_set, sizeof(fd_ipc_control_subcmd_align_threshold_set_t));
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp_fd_align_threshold_set failed!");
}
LISA_LOGI(TAG, "acomp_fd_align_threshold_set exit");
return ret;
}
int acomp_fd_live_detect_mode_set(const acomp_fd_live_detect_mode_t *mode)
{
LISA_LOGI(TAG, "acomp_fd_live_detect_mode_set enter");
fd_ipc_control_subcmd_live_detect_set_t live_detect_set;
live_detect_set.enable = mode->enable;
live_detect_set.score_threshold[0] = mode->score_threshold[0];
live_detect_set.score_threshold[1] = mode->score_threshold[1];
int ret = acomp_fd_control_subcmd(FD_ICP_CONTROL_SUBCMD_LIVE_DETECT_SET, &live_detect_set, sizeof(fd_ipc_control_subcmd_live_detect_set_t));
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp_fd_live_detect_mode_set failed!");
}
LISA_LOGI(TAG, "acomp_fd_live_detect_mode_set exit");
return ret;
}
int acomp_fd_features_load(const acomp_fd_feature_result_t *features, uint32_t count)
{
LISA_LOGI(TAG, "acomp_fd_features_load enter");
int len = sizeof(fd_ipc_control_subcmd_features_load_t) + sizeof(acomp_fd_feature_result_t) * count;
fd_ipc_control_subcmd_features_load_t *features_load = (fd_ipc_control_subcmd_features_load_t *)psram_malloc(len);
if (features_load == NULL) {
LISA_LOGE(TAG, "acomp_fd_features_load malloc failed!");
return ACOMP_ERR_NO_MEM;
}
features_load->feature_cnt = count;
acomp_fd_feature_result_t *features_data = (acomp_fd_feature_result_t *)features_load->data;
memcpy((uint8_t *)features_data, (uint8_t *)features, sizeof(acomp_fd_feature_result_t) * count);
int ret = acomp_fd_control_subcmd(FD_ICP_CONTROL_SUBCMD_FEATURES_LOAD, features_load, len);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp_fd_features_load failed!");
}
psram_free(features_load);
LISA_LOGI(TAG, "acomp_fd_features_load exit");
return ret;
}
int acomp_fd_add_callback(uint32_t events, fd_event_cb_t cb, void *priv)
{
LISA_LOGI(TAG, "acomp fd add callback enter");
int ret;
if (fd_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
ret = gcl_cb_list_add_callback(fd_handle->event_callbacks, events, cb, priv);
LISA_LOGI(TAG, "acomp fd add callback exit");
return ret;
}
int acomp_fd_remove_callback(fd_event_cb_t cb)
{
LISA_LOGI(TAG, "acomp fd remove callback enter");
int ret;
if (fd_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
ret = gcl_cb_list_remove(fd_handle->event_callbacks, cb);
LISA_LOGI(TAG, "acomp fd remove callback exit");
return ret;
}
int acomp_fd_stream_ch_enable(int chn,acomp_stream_chn_create_desc_t *desc){
int ret = 0;
if ((fd_handle == NULL) || (fd_handle->stream == NULL)) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
fd_handle->stream->ch[chn] = acomp_stream_ipc_channel_create(fd_handle->stream,chn,fd_handle->dev_index,desc);
if(fd_handle->stream->ch[chn] == NULL){
LISA_LOGE(TAG, "fd_handle->stream->ch[%d] == NULL, failed", chn);
return ACOMP_ERR_CREATE_STREAM_FAILED;
}
LISA_LOGI(TAG,"acomp_fd_stream_ch_enable chn(%s) index(%d),desc(%p)",desc->cname,chn,desc);
return ret;
}
int acomp_fd_stream_ch_disable(int chn){
int ret;
if (fd_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
ret = acomp_stream_ipc_channel_destroy(chn);
LISA_LOGI(TAG,"acomp_fd_stream_ch_disable chn index(%d),ret(%d)",chn,ret);
fd_handle->stream->ch[chn] = NULL;
return ret;
}
void* acomp_fd_stream_rx_buffer_get(int chn, uint32_t* len, uint16_t* desc_idx){
uint8_t *ptr;
if (fd_handle == NULL) {
return NULL;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return NULL;
}
if(fd_handle->stream->ch[chn] == NULL){
return NULL;
}
ptr = fd_handle->stream->ops.rx_buffer_get(fd_handle->stream->ch[chn], len, desc_idx);
return ptr;
}
int acomp_fd_stream_rx_buffer_release(int chn, uint16_t desc_idx, uint32_t len,void* buffer){
int ret;
if (fd_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
ret = fd_handle->stream->ops.rx_buffer_release(fd_handle->stream->ch[chn],buffer, len, desc_idx);
return ret;
}
void* acomp_fd_stream_tx_buffer_alloc(int chn, uint32_t* len, uint16_t* desc_idx){
uint8_t* buffer;
if (fd_handle == NULL) {
return NULL;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return NULL;
}
if(fd_handle->stream->ch[chn] == NULL){
return NULL;
}
buffer = fd_handle->stream->ops.tx_buffer_alloc(fd_handle->stream->ch[chn], len, desc_idx);
return buffer;
}
int acomp_fd_stream_tx_buffer_submit(int chn, void* buffer, uint32_t len, uint16_t desc_idx){
int ret;
if (fd_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
if(fd_handle->stream->ch[chn] == NULL){
return ACOMP_ERR_INVALID_STATE;
}
ret = fd_handle->stream->ops.tx_buffer_submit(fd_handle->stream->ch[chn], buffer, len, desc_idx);
return ret;
}

View File

@@ -0,0 +1,371 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "../utils/acomp_err.h"
#include "acomp_stream_ipc.h"
#include "acomp_fd_params.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef BIT
#define BIT(x) (1 << (x))
#endif
/* 人脸识别组件参数设置 */
/**
* @brief 人脸识别参数键值对
*/
typedef struct{
uint32_t key; /* 参数键(参见 acomp_fd_params.h的 acomp_fd_params_e枚举 */
float value; /* 参数值 */
}acomp_fd_param_t;
/**
* @brief 人脸识别的输入图片帧结构体
*/
typedef struct{
acomp_fd_pixel_format format; /* 像素格式(如 PIX_FMT_BGR888, PIX_FMT_YUV422 等) */
uint32_t index; /* 帧索引号 */
uint16_t width; /* 图像宽度(像素) */
uint16_t height; /* 图像高度(像素) */
uint32_t length; /* 图像数据长度(字节) */
uint32_t resv[4]; /* reserved */
uint8_t data[0]; /* 图像数据指针 */
} __attribute__((packed)) acomp_fd_input_frame_t;
/**
* @brief 活体检测模式配置
*/
typedef struct{
int enable; /* 是否启用活体检测0:禁用 1:启用) */
float score_threshold[2]; /* 活体检测阈值 [0]:假人阈值 [1]:真人阈值, 在真人得分 > 真人阈值的情况下才会进行人脸特征提取和人脸识别 */
}acomp_fd_live_detect_mode_t;
/* 人脸识别组件结果 */
/**
* @brief 人脸检测矩形框
*/
typedef struct{
int x; /* 矩形框左上角x坐标 */
int y; /* 矩形框左上角y坐标 */
int w; /* 矩形框宽度 */
int h; /* 矩形框高度 */
}acomp_fd_rect_t;
/**
* @brief 人脸头部姿态角度
*/
typedef struct{
float yaw; /* 偏航角(左右转头),范围 -90° ~ +90° */
float pitch; /* 俯仰角(上下点头),范围 -90° ~ +90° */
float roll; /* 翻滚角(左右歪头),范围 -180° ~ +180° */
}acomp_fd_head_pose_t;
/**
* @brief 人脸关键点(特征点)
*/
typedef struct{
int x; /* 关键点x坐标 */
int y; /* 关键点y坐标 */
float score; /* 关键点置信度得分 */
float visable; /* 关键点可见性0-11表示完全可见 */
}acomp_fd_align_point_t;
/**
* @brief 人脸活体检测结果
*/
typedef struct {
float scores[2]; /* 活体检测得分 [0]:假人得分 [1]:真人得分 */
int status; /* 活体检测状态0:假人 1:真人) */
}acomp_fd_live_detect_result_t;
typedef struct {
float features[ACOMP_FD_MAX_FEATURE_CNT]; /* 人脸特征点 */
int feature_cnt; /* 人脸特征点个数 */
}acomp_fd_feature_result_t;
/**
* @brief 人脸检测完整结果
*/
typedef struct {
acomp_fd_rect_t face_rect; /* 人脸检测矩形框 */
float face_score; /* 人脸检测得分 */
acomp_fd_align_point_t align_points[ACOMP_FD_MAX_ALIGN_CNT]; /* 人脸标定点 */
int n_align_point; /* 人脸标定点个数 */
acomp_fd_head_pose_t pose; /* 人脸头部姿势 */
acomp_fd_live_detect_result_t live_result; /* 活体检测结果 */
int face_id; /* 人脸id */
float features[ACOMP_FD_MAX_FEATURE_CNT]; /* 人脸特征点 */
int feature_cnt; /* 人脸特征点个数 */
float compare_scores[ACOMP_FD_MAX_RESULT_CNT];/* 输入图片和已注册人脸特征(最大10个人脸注册)的比较得分 */
int compare_cnt; /* 已经注册人脸特征个数 */
}acomp_fd_result_t;
typedef struct {
uint32_t results_cnt;
uint32_t max_area_results_index;
acomp_fd_result_t results[0];
}acomp_fd_result_info_t;
/*人脸识别组件回调事件定义*/
#define FD_CB_EVENT_ENGINE_RLT BIT(0) /*人脸识别引擎结果返回*/
#define FD_CB_EVENT_ENGINE_WR_ERR BIT(1) /*算法引擎数据写入出错*/
typedef void (*fd_event_cb_t)(uint32_t event, void *event_data, uint32_t event_data_len, void *priv);
/**
* @brief 初始化人脸识别组件FD
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_NOT_FOUND : 设备未找到
*
*/
extern int acomp_fd_init(void);
// /**
// * @brief 逆初始化人脸识别组件FD
// *
// * @return GCL_OK : 成功
// * @retval -GCL_ERR_COMM_FAIL(209) : 通讯失败
// *
// */
// extern int acomp_fd_deinit(void);
/**
* @brief 就绪人脸识别组件FD
*
* @note 该函数会初始化内存块及算法资源在调用fd_gcl_start之前必须调用该函数让组件进入就绪状态。
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_fd_prepare(void);
/**
* @brief 复位人脸识别组件FD
*
* @note 该函数会释放内存块及算法资源。
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_fd_cleanup(void);
/**
* @brief 启动人脸识别组件FD
*
* @note 该函数会启动人脸识别组件,之后用户可以向组件输入视频流
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_fd_start(void);
/**
* @brief 停止人脸识别组件FD
*
* @note 该函数会停止人脸识别组件,之后不会处理视频流数据
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_fd_stop(void);
/**
* @brief 设置组件参数
*
* @note 设置组件参数信息该参数项将在组件进入就绪态时生效调用fd_gcl_prepare
*
* @param params[in] 存储acomp_fd_param_t参数的结构指针
* @param params_cnt[in] acomp_fd_param_t的个数
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_fd_params_set(const acomp_fd_param_t *params, uint32_t params_cnt);
/**
* @brief 获取组件参数
*
* @note 获取组件参数信息该参数项将在组件进入就绪态时生效调用fd_gcl_prepare
*
* @param params[out] 存储acomp_fd_param_t参数的结构指针
* @param params_cnt[out] acomp_fd_param_t的个数
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_fd_params_get(const acomp_fd_param_t *params, uint32_t *params_cnt);
/**
* @brief 设置人脸标定的头部姿势的阈值
*
* @note 如果超过这些阈值,就不会进行活体检测和人脸特征获取
*
* @param params[in] 存储acomp_fd_head_pose_t参数的结构指针
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_fd_align_threshold_set(const acomp_fd_head_pose_t *threshold);
/**
* @brief 设置人脸活体检测的模式
*
* @note 这个函数用来设置是否使能活体检测,以及设置活体检测的得分阈值
*
* @param params[in] 存储acomp_fd_live_detect_mode_t参数的结构指针
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_fd_live_detect_mode_set(const acomp_fd_live_detect_mode_t *mode);
/**
* @brief 从外部导入人脸特征到注册库
*
* @note 导入外部特征到注册库,用于后续人脸识别
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_fd_features_load(const acomp_fd_feature_result_t *features, uint32_t count);
/**
* @brief 给组件增加事件回调函数
*
* @param events[in] 待增加的事件位,可以同步注册多个事件位;
* @param cb[in] 回调的函数指针;
* @param priv[in] 回调函数的私有数据指针;
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_fd_add_callback(uint32_t events, fd_event_cb_t cb, void *priv);
/**
* @brief 移除组件的毁掉函数
*
* @param cb[in] 待移除的回调函数
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_NOT_SUPPORTED : 无效操作
*
*
*/
extern int acomp_fd_remove_callback(fd_event_cb_t cb);
/**
* @brief 使能流通道
*
* @param chn[in] 通道索引
* @param desc[in] 通道描述符指针
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_CREATE_STREAM_FAILED : 创建流失败
*
*/
extern int acomp_fd_stream_ch_enable(int chn, acomp_stream_chn_create_desc_t *desc);
/**
* @brief 禁用流通道
*
* @param chn[in] 通道索引
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
*
*/
extern int acomp_fd_stream_ch_disable(int chn);
/**
* @brief 获取RX流缓冲区
*
* @param chn[in] 通道索引
* @param len[out] 数据长度指针
* @param desc_idx[out] 描述符索引指针
*
* @return 缓冲区指针如果失败返回NULL
*
*/
extern void* acomp_fd_stream_rx_buffer_get(int chn, uint32_t* len, uint16_t* desc_idx);
/**
* @brief 释放RX流缓冲区
*
* @param chn[in] 通道索引
* @param desc_idx[in] 描述符索引
* @param len[in] 数据长度
* @param buffer[in] 缓冲区指针
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
*
*/
extern int acomp_fd_stream_rx_buffer_release(int chn, uint16_t desc_idx, uint32_t len, void* buffer);
/**
* @brief 分配TX流缓冲区用于向remote发送视频数据
*
* @param chn[in] 通道索引
* @param len[out] 可用缓冲区长度指针
* @param desc_idx[out] 描述符索引指针
*
* @return 缓冲区指针如果失败返回NULL
*
*/
extern void* acomp_fd_stream_tx_buffer_alloc(int chn, uint32_t* len, uint16_t* desc_idx);
/**
* @brief 提交TX流缓冲区发送视频数据到remote
*
* @param chn[in] 通道索引
* @param buffer[in] 缓冲区指针
* @param len[in] 数据长度
* @param desc_idx[in] 描述符索引
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
*
*/
extern int acomp_fd_stream_tx_buffer_submit(int chn, void* buffer, uint32_t len, uint16_t desc_idx);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,52 @@
#pragma once
#define ACOMP_FD_MAX_RESULT_CNT (10)
#define ACOMP_FD_MAX_ALIGN_CNT (68)
#define ACOMP_FD_MAX_FEATURE_CNT (384)
typedef enum
{
PARAM_FACE_NONE = 0,
//FACE DETECT
PARAM_FACE_DETECT_BEGIN = 10000,
PARAM_DETECT_OUT_THRES = 10001,
PARAM_DETECT_PROTHRES = 10002,
PARAM_DETECT_NMSTHRES = 10003,
PARAM_DETECT_PIXESIZE = 10004,
PARAM_FACE_DETECT_END,
//FACE ALIGN
PARAM_FACE_ALIGN_BEGIN = 20000,
PARAM_FACE_ALIGN_END,
//FACE LIVE
PARAM_FACE_LIVE_BEGIN = 30000,
PARAM_LIVE_THRES = 30001,
PARAM_FACE_LIVE_END,
//FACE VERIFY
PARAM_FACE_VERIFY_BEGIN = 40000,
PARAM_VERIFY_THRES = 40001,
PARAM_VERIFY_REGU_A = 40002,
PARAM_VERIFY_REGU_B = 40003,
PARAM_FACE_VERIFY_END,
PARAM_FACE_MAX = 0XFFFFFFFF,
}acomp_fd_params_e;
typedef enum
{
PIX_FMT_RGB888 = 0,
PIX_FMT_BGR888 = 1,
PIX_FMT_RGB565 = 2,
PIX_FMT_BGR565 = 3,
PIX_FMT_YUV444_PACKED = 4,
PIX_FMT_YUV422_YUYV_PACKED = 5,
PIX_FMT_YUV422_UYVY_PACKED = 6,
PIX_FMT_YUV422_YVYU_PACKED = 7,
PIX_FMT_YUV422_VYUY_PACKED = 8,
PIX_FMT_GRAY = 9,
PIX_FMT_MAX = 0XFFFFFFFF,
}acomp_fd_pixel_format;

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@@ -0,0 +1,77 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "../acomp_fd_params.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ACOMP_FD_RES_NUMBER (4)
typedef enum _ls_face_res_type
{
RES_NULL = 0,
RES_FACE_DETECT = 1,
RES_FACE_ALIGN = 2,
RES_FACE_LIVE = 3,
RES_FACE_VERIFY = 4,
RES_FACE_COUNT ,
RES_MAX = 0XFFFFFFFF,
}ls_face_res_type;
/* CP -> AP control subcmd */
typedef enum{
FD_ICP_CONTROL_SUBCMD_PARAMETER_SET = 1,
FD_ICP_CONTROL_SUBCMD_PARAMETER_GET = 2,
FD_ICP_CONTROL_SUBCMD_ALIGN_THRESHOLD_SET = 3,
FD_ICP_CONTROL_SUBCMD_LIVE_DETECT_SET = 4,
FD_ICP_CONTROL_SUBCMD_FEATURES_LOAD = 5,
}fd_ipc_control_subcmd_e;
typedef struct {
acomp_fd_params_e key;
float value;
} fd_param_t;
typedef struct{
uint32_t params_cnt;
uint8_t data[0];
}__attribute__((packed)) fd_ipc_control_subcmd_parameter_set_t;
typedef struct{
float yaw;
float pitch;
float roll;
}__attribute__((packed)) head_pose_t;
typedef struct {
head_pose_t head_pose;
}__attribute__((packed)) fd_ipc_control_subcmd_align_threshold_set_t;
typedef struct{
uint32_t enable;
float score_threshold[2];
}__attribute__((packed)) fd_ipc_control_subcmd_live_detect_set_t;
typedef struct {
uint32_t feature_cnt;
uint8_t data[0];
}__attribute__((packed)) fd_ipc_control_subcmd_features_load_t;
/* AP -> CP notify subcmd*/
typedef struct{
uint32_t results_cnt;
uint32_t max_area_results_index;
uint8_t results[0];
}__attribute__((packed)) fd_ipc_notify_subcmd_fd_result_hdr_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,10 @@
.. _components_acomp:
算法组件
========
.. toctree::
:maxdepth: 2
wakeup/README.md
fd/README.rst

View File

@@ -0,0 +1,7 @@
FILE(GLOB SRCS
acomp_ipc.c
)
listenai_library_sources(${SRCS})

View File

@@ -0,0 +1,212 @@
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include "assert.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "ic_message.h"
#include "acomp_ipc.h"
#include "gcl_cb_list/gcl_cb_list.h"
#include "dlist.h"
#include "acomp_err.h"
#define TAG "acomp_ipc"
#include "lisa_log.h"
#define CACHE_LINE_SIZE (32)
#define ASSERT(exp, fmt, ...) \
do { \
if (!(exp)) { \
LISA_LOGI(TAG, "error:" fmt, ##__VA_ARGS__); \
assert(exp); \
} \
} while (0)
#define IPC_TIMOUT_MS (10000)
typedef struct {
uint32_t dev_index;
ipc_event_cb_t cb;
void *priv;
sys_dnode_t node;
} acomp_cb_list_item_t;
typedef struct {
uint32_t index;
uint8_t name[ACOMP_DEV_NAME_MAX_LEN];
sys_dnode_t node;
} acomp_ipc_dev_info_list_item_t;
typedef struct {
sys_dlist_t cb_list;
sys_dlist_t dev_info_list;
SemaphoreHandle_t reply_sem;
} acomp_ipc_handle_t;
acomp_ipc_handle_t *ipc_handle = NULL;
static int32_t acomp_ipc_callback_wrap(ic_message_handle_info_t *handle_info, ic_message_msg_info_t *msg)
{
sys_dnode_t *node;
acomp_cb_list_item_t *cb_item;
acomp_ipc_handle_t *handle = (acomp_ipc_handle_t *)handle_info->user_datas;
acomp_ipc_message_t *ipc_msg = (acomp_ipc_message_t *)msg->msg;
if (ipc_msg->hdr.hdr.cmd == ACOMP_CONTEXT_IPC_GLB_REPLY) {
xSemaphoreGive(handle->reply_sem);
return 0;
}
if ((ipc_msg->address != 0) && (ipc_msg->len > 0)) {
HAL_InvalidateDCache_by_Addr((void *)ipc_msg->address, ipc_msg->len);
}
if (ipc_msg->hdr.hdr.cmd == ACOMP_CONTEXT_IPC_GLB_NOTIFY) {
SYS_DLIST_FOR_EACH_NODE(&handle->cb_list, node)
{
cb_item = CONTAINER_OF(node, acomp_cb_list_item_t, node);
if (cb_item->dev_index == ipc_msg->dev_index) {
cb_item->cb(ipc_msg, cb_item->priv);
}
}
} else if (ipc_msg->hdr.hdr.cmd == ACOMP_CONTEXT_IPC_GLB_DEVINFO_QUERY_RESP) {
acomp_ipc_dev_info_query_msg_t *dev_info = (acomp_ipc_dev_info_query_msg_t *)ipc_msg->address;
for (int ii = 0; ii < dev_info->number; ii++) {
acomp_ipc_dev_info_list_item_t *dev_info_item =
(acomp_ipc_dev_info_list_item_t *)psram_malloc(sizeof(acomp_ipc_dev_info_list_item_t));
if (dev_info_item == NULL) {
LISA_LOGE(TAG, "acomp ipc dev info item malloc failed");
return -1;
}
memset(dev_info_item, 0, sizeof(acomp_ipc_dev_info_list_item_t));
dev_info_item->index = dev_info->item[ii].index;
snprintf(dev_info_item->name, sizeof(dev_info_item->name), "%s", dev_info->item[ii].name);
sys_dlist_append(&handle->dev_info_list, &dev_info_item->node);
LISA_LOGI(TAG, "[%d]acomp remote dev index %d name %s", ii, dev_info_item->index, dev_info_item->name);
}
} else {
LISA_LOGE(TAG, "acomp ipc unknown cmd:%d", ipc_msg->hdr.hdr.cmd);
}
if (ipc_msg->hdr.hdr.req_reply) {
acomp_ipc_build_frame_send_sync(ipc_msg->dev_index, ACOMP_CONTEXT_IPC_GLB_REPLY, 0, 0, 0, 0);
}
return 0;
}
int acomp_ipc_init(void)
{
int ret;
if (ipc_handle != NULL) {
return -ACOMP_ERR_INVALID_STATE;
}
ipc_handle = (acomp_ipc_handle_t *)psram_malloc(sizeof(acomp_ipc_handle_t));
if (ipc_handle == NULL) {
return -ACOMP_ERR_NO_MEM;
}
memset(ipc_handle, 0, sizeof(acomp_ipc_handle_t));
sys_dlist_init(&ipc_handle->cb_list);
sys_dlist_init(&ipc_handle->dev_info_list);
ipc_handle->reply_sem = xSemaphoreCreateBinary();
ic_message_register_by_id(IC_MESSAGE_ID_ACOMP, acomp_ipc_callback_wrap, ipc_handle);
ret =
acomp_ipc_build_frame_send_sync(0, ACOMP_CONTEXT_IPC_GLB_DEVINFO_QUERY | IPC_HEADER_REQ_REPALY, 0, 0, NULL, 0);
return ret;
}
int acomp_ipc_add_callback(uint32_t dev_index, ipc_event_cb_t cb, void *priv)
{
acomp_cb_list_item_t *cb_item;
cb_item = (acomp_cb_list_item_t *)psram_malloc(sizeof(acomp_cb_list_item_t));
if (cb_item == NULL) {
return -ACOMP_ERR_NO_MEM;
}
memset(cb_item, 0, sizeof(acomp_cb_list_item_t));
cb_item->dev_index = dev_index;
cb_item->cb = cb;
cb_item->priv = priv;
sys_dlist_append(&ipc_handle->cb_list, &cb_item->node);
return 0;
}
int acomp_ipc_remove_callback(uint32_t dev_index, ipc_event_cb_t cb)
{
acomp_cb_list_item_t *cb_item;
sys_dnode_t *node, *tmp;
int ret = -ACOMP_ERR_NOT_FOUND;
if (ipc_handle == NULL || cb == NULL) {
return -EINVAL;
}
// Search for the callback item with matching dev_index
SYS_DLIST_FOR_EACH_NODE_SAFE(&ipc_handle->cb_list, node, tmp)
{
cb_item = CONTAINER_OF(node, acomp_cb_list_item_t, node);
if (cb_item->dev_index == dev_index) {
sys_dlist_remove(&cb_item->node);
psram_free(cb_item);
return 0;
}
}
return ret;
}
int acomp_ipc_build_frame_send_sync(int dev_index, int cmd, int acomp_cmd, uint8_t flags, void *data, uint16_t len)
{
acomp_ipc_message_t ipc_msg;
uint8_t *pdata;
int ret = 0;
ipc_msg.hdr.glb_cmd = cmd;
ipc_msg.dev_index = dev_index;
ipc_msg.acomp_cmd = acomp_cmd;
ipc_msg.req.flags = flags;
ipc_msg.len = len;
ipc_msg.address = (uint32_t)data;
if ((data != NULL) && (len > 0)) {
ASSERT(!(((uint32_t)data % CACHE_LINE_SIZE) && (len % CACHE_LINE_SIZE)),
"comp ipc buffer not aligned cache line(32)");
HAL_FlushDCache_by_Addr((void *)data, len);
}
pdata = (uint8_t *)&ipc_msg;
ic_message_msg_send_by_id(IC_MESSAGE_ID_ACOMP, IC_MESSAGE_MSG_TYPE_CMD, (uint8_t *)&ipc_msg,
sizeof(acomp_ipc_message_t));
if ((ipc_msg.hdr.hdr.cmd != ACOMP_CONTEXT_IPC_GLB_REPLY) && (ipc_msg.hdr.hdr.req_reply)) {
if (xSemaphoreTake(ipc_handle->reply_sem, pdMS_TO_TICKS(IPC_TIMOUT_MS)) != pdTRUE) {
CLOGE("[%s %d]urpc_send_async_client hdr(0x%x) timeout(%d),ret %d !\n", __FUNCTION__, __LINE__,
ipc_msg.hdr.glb_cmd, IPC_TIMOUT_MS, ret);
ret = -ACOMP_ERR_TIMEOUT;
}
}
return ret;
}
int acomp_ipc_get_dev_index(const char *name)
{
int ret;
sys_dnode_t *node;
SYS_DLIST_FOR_EACH_NODE(&ipc_handle->dev_info_list, node)
{
acomp_ipc_dev_info_list_item_t *dev_info_item = CONTAINER_OF(node, acomp_ipc_dev_info_list_item_t, node);
if (strcmp(dev_info_item->name, name) == 0) {
return dev_info_item->index;
}
}
return -ACOMP_ERR_NOT_FOUND;
}

View File

@@ -0,0 +1,165 @@
#ifndef __ACOMP_IPC_H_
#define __ACOMP_IPC_H_
#include <stdint.h>
#define IPC_ALIGN_SIZE (32)
#define ACOMP_DEV_NAME_MAX_LEN (16)
/*components ipc global define*/
typedef union{
uint8_t glb_cmd;
struct{
uint8_t cmd:4; /*global cmd*/
uint8_t req_reply:1; /*request global reply*/
uint8_t resp:1; /*resp 0:成功1失败*/
uint8_t reserved:2;
}hdr;
}__attribute__((packed)) acomp_ipc_header_t;
/*components ipc global define*/
#ifndef BIT
#define BIT(x) (1<<x)
#endif
#define IPC_HEADER_REQ_REPALY BIT(4)
#define IPC_HEADER_GLB_CMD(cmd) (cmd & 0x0f)
/*components global commands APP CORE <-> ALGO CORE */
#define ACOMP_CONTEXT_IPC_GLB_REPLY (0x00)
/*components global commands APP CORE -> ALGO CORE*/
#define ACOMP_CONTEXT_IPC_GLB_NEW (0x01)
#define ACOMP_CONTEXT_IPC_GLB_FREE (0x02)
#define ACOMP_CONTEXT_IPC_GLB_CONTROL (0x03)
#define ACOMP_CONTEXT_IPC_GLB_DEVINFO_QUERY (0x04)
/*components global commands ALGO CORE -> APP CORE*/
#define ACOMP_CONTEXT_IPC_GLB_NOTIFY (0x01)
#define ACOMP_CONTEXT_IPC_GLB_DEVINFO_QUERY_RESP (0x02)
/*components subcmd define APP CORE -> ALGO CORE*/
#define ACOMP_IPC_CMD_PREPARE (0x00) /*sync prepare load resource ,init memory ...*/
#define ACOMP_IPC_CMD_CLEANUP (0x01) /*sync cleanup free resource ,free memory ...*/
#define ACOMP_IPC_CMD_START (0x02)
#define ACOMP_IPC_CMD_STOP (0x03)
#define ACOMP_IPC_CMD_ABORT (0x04)
#define ACOMP_IPC_CMD_CONTROL (0x05)
#define ACOMP_IPC_CMD_STREAM_CREATE (0x06)
#define ACOMP_IPC_CMD_STREAM_DESTROY (0x07)
#define ACOMP_IPC_CMD_STREAM_UPDATE (0x08) /*sync stream update*/
/*components subcmd ALGO CORE -> APP CORE*/
#define ACOMP_IPC_CMD_NOTIFY_RESULT (0x01) /*async notify result*/
#define ACOMP_IPC_CMD_NOTIFY_STREAM_UPDATE (0x02) /*async notify data write*/
#define ACOMP_IPC_CMD_NOTIFY_SUBCMD (0x03) /*async notify subcmd*/
typedef struct {
acomp_ipc_header_t hdr;
uint8_t dev_index;
uint8_t acomp_cmd;
union {
struct{
uint8_t flags;
}req;
struct{
uint8_t err;
}reply;
};
uint16_t len;
uint32_t address;
} __attribute__((packed)) acomp_ipc_message_t;
typedef union{
uint32_t data;
struct{
uint32_t storage:3; /*0:FLASH;1:SD;2:PSRAM;*/
uint32_t reserved:29;
}hdr;
}acomp_res_item_attr_t;
typedef struct {
uint32_t index;
acomp_res_item_attr_t attr;
uint32_t addr;
uint32_t offset;
uint32_t size;
}__attribute__((packed))acomp_res_item_t;
typedef struct {
uint32_t number;
acomp_res_item_t item[0];
}__attribute__((packed,aligned(32)))acomp_ipc_prepare_t;
typedef struct{
uint32_t index;
uint8_t name[ACOMP_DEV_NAME_MAX_LEN];
}__attribute__((packed))acomp_ipc_dev_info_t;
typedef struct{
uint32_t number;
acomp_ipc_dev_info_t item[0];
}__attribute__((packed,aligned(32)))acomp_ipc_dev_info_query_msg_t;
typedef struct{
int control;
uint32_t len;
uint8_t data[0];
}__attribute__((packed,aligned(32)))acomp_ipc_control_t;
typedef struct{
uint32_t len;
uint8_t data[];
}__attribute__((packed,aligned(32)))acomp_ipc_notify_result_t;
typedef struct{
uint32_t len;
uint8_t data[];
}__attribute__((packed,aligned(32)))acomp_ipc_notify_subcmd_t;
typedef struct{
char name[16];
uint8_t direction; /*0: m2r,1: r2m*/
uint8_t index;
uint32_t kick_policy; /* Kick policy: 0:manual/ >0: buffer count to kick */
/* vring memory*/
void *phy_addr;
uint32_t mem_size;
uint32_t align;
uint32_t buffer_size;
}__attribute__((packed,aligned(32)))acomp_ipc_stream_create_desc_t;
typedef struct{
uint32_t index;
}__attribute__((packed,aligned(32)))acomp_ipc_stream_destroy_desc_t;
typedef struct{
uint32_t index;
}__attribute__((packed,aligned(32)))acomp_ipc_stream_update_t;
#define ACOMP_IPC_NOTIFY_DATA_ALIGN(len) ACOMP_IPC_ALIGN(offsetof(acomp_ipc_notify_t,data)+ len)
typedef void(*ipc_event_cb_t) (acomp_ipc_message_t *message, void *priv);
extern int acomp_ipc_init(void);
extern int acomp_ipc_add_callback(uint32_t dev_index, ipc_event_cb_t cb, void *priv);
extern int acomp_ipc_build_frame_send_sync(int dev_index,int cmd,int acomp_cmd,uint8_t flags,void* data,uint16_t len);
extern int acomp_ipc_get_dev_index(const char *name);
#endif

View File

@@ -0,0 +1,11 @@
if (CONFIG_ACOMP)
listenai_library_sources(
acomp_logger.c
)
listenai_include_directories(
./
)
endif()

View File

@@ -0,0 +1,126 @@
# SPDX-License-Identifier: Apache-2.0
if ACOMP_LOGGER
config ACOMP_LOGGER_STREAM_BUFFER_SIZE
int "Logger stream buffer size (bytes)"
default 256
help
Size of each buffer in the logger stream.
This determines the maximum size of data that can be written
to a single buffer before it needs to be submitted.
config ACOMP_LOGGER_STREAM_BUFFER_COUNT
int "Logger stream buffer count"
default 128
help
Number of buffers in the logger stream.
More buffers can improve performance but consume more memory.
NOTE: Must be a power of 2 (e.g., 2, 4, 8, 16, 32, etc.).
choice ACOMP_LOGGER_KICK_POLICY
prompt "Logger stream kick policy"
default ACOMP_LOGGER_KICK_MANUAL
help
Determines when the logger stream is kicked/flushed.
config ACOMP_LOGGER_KICK_MANUAL
bool "Manual kick"
help
Stream is manually polled and kicked/flushed.
Requires periodic polling based on configured interval.
config ACOMP_LOGGER_KICK_AUTO
bool "Auto kick"
help
Stream is automatically kicked/flushed when buffer threshold is reached.
endchoice
if ACOMP_LOGGER_KICK_AUTO
config ACOMP_LOGGER_AUTO_KICK_BUFFER_THRESHOLD
int "Logger auto kick buffer threshold"
default 4
help
Number of buffers received before automatically triggering a kick/flush.
Only used when kick policy is set to auto.
The logger will automatically flush data after receiving this many buffers.
endif
if ACOMP_LOGGER_KICK_MANUAL
config ACOMP_LOGGER_MANUAL_POLL_INTERVAL_MS
int "Logger buffer poll interval (ms)"
default 10
help
Polling interval in milliseconds for checking buffers.
Only used when kick policy is set to manual.
The logger will check for available buffers at this interval.
endif # ACOMP_LOGGER_KICK_MANUAL
config ACOMP_LOGGER_THREAD_PRIORITY
int "Logger thread priority"
default 5
help
Thread priority for logger processing thread.
Higher values indicate higher priority.
Range: 0 (lowest) to 31 (highest).
config ACOMP_LOGGER_THREAD_STACK_SIZE
int "Logger thread stack size (bytes)"
default 2048
help
Stack size in bytes for logger processing thread.
Adjust based on logger processing requirements.
config ACOMP_LOGGER_USE_COLOR
bool "Enable color highlighting for AP logs"
default y
help
Enable ANSI color codes to highlight AP logs with background color.
This makes it easier to distinguish AP logs from Master logs.
if ACOMP_LOGGER_USE_COLOR
choice ACOMP_LOGGER_COLOR_SCHEME
prompt "AP log color scheme"
default ACOMP_LOGGER_COLOR_CYAN_BG
help
Select the color scheme for AP logs.
config ACOMP_LOGGER_COLOR_CYAN_BG
bool "Cyan background"
help
Use cyan background for AP logs (recommended).
config ACOMP_LOGGER_COLOR_BLUE_BG
bool "Blue background"
help
Use blue background for AP logs.
config ACOMP_LOGGER_COLOR_GREEN_BG
bool "Green background"
help
Use green background for AP logs.
config ACOMP_LOGGER_COLOR_YELLOW_BG
bool "Yellow background"
help
Use yellow background for AP logs.
config ACOMP_LOGGER_COLOR_PURPLE_BG
bool "Purple background"
help
Use purple background for AP logs.
config ACOMP_LOGGER_COLOR_LIGHT_GRAY_BG
bool "Light gray background"
help
Use light gray background for AP logs.
endchoice
endif # ACOMP_LOGGER_USE_COLOR
endif # ACOMP_LOGGER

View File

@@ -0,0 +1,633 @@
# ACOMP Logger 组件
## 概述
ACOMP Logger 组件是一个用于在 Remote (AP) 和 Master 核心之间传输系统日志的通信组件。它通过创建 R2M (Remote to Master) 数据流,将 Remote 端的日志实时传输到 Master 端进行输出和显示。
## 架构
```
Remote 端 (AP) Master 端
┌────────────────────┐ ┌────────────────────┐
│ lisa_log 系统 │ │ 日志接收线程 │
│ ↓ │ │ ↓ │
│ logger backend │ │ 从 R2M 流读取 │
│ ↓ │ R2M Stream │ ↓ │
│ acomp logger 设备 │ ──────────────→ │ 缓冲区处理 │
│ ↓ │ (IPC) │ ↓ │
│ stream tx │ │ lisa_log 输出 │
└────────────────────┘ └────────────────────┘
```
## 功能特性
### Remote 端
- 通过 `lisa_log_backend_add` 自动注册为日志后端
- 拦截并捕获所有系统日志
- 通过 R2M 流将日志数据实时发送到 Master 端
- 无需手动配置,开箱即用
### Master 端
- **自动化管理**:通过简单的 API 完成所有初始化和启动
- **灵活配置**:通过 Kconfig 自定义缓冲区、触发策略等参数
- **两种触发模式**
- **手动触发模式**(默认):按固定间隔轮询检查日志
- **自动触发模式**:基于信号量自动触发,收到指定数量缓冲区后处理
- **线程安全**:专用接收线程自动处理日志接收和输出
- **高性能**:支持大容量缓冲池(最多 128 个缓冲区)和可配置的缓冲区大小
## 使用方法
### Remote 端
Remote 端的 logger 设备会自动注册并初始化,只需要确保:
1. 在 Kconfig 中启用 logger 组件
2. 系统会自动加载 `acomp.logger` 设备并注册日志后端
无需编写任何代码Remote 端会自动开始捕获日志。
### Master 端
#### 快速开始
最简单的使用方式,只需两步:
```c
#include "acomp_logger.h"
int main(void)
{
int ret;
// 1. 初始化 logger 组件
ret = acomp_logger_init();
if (ret != ACOMP_ERR_OK) {
printf("Logger init failed: %d\n", ret);
return ret;
}
// 2. 启动 logger - 自动创建接收线程并输出日志
ret = acomp_logger_start();
if (ret != ACOMP_ERR_OK) {
printf("Logger start failed: %d\n", ret);
return ret;
}
printf("Logger started! Remote logs will appear here.\n");
// Remote 端的日志现在会自动显示在 Master 端
// 应用程序继续运行...
return 0;
}
```
#### `acomp_logger_start()` 自动完成的工作
调用 `acomp_logger_start()` 后,组件会自动:
1. **配置 R2M 流通道**:根据 Kconfig 设置配置缓冲区大小和数量
2. **启动 Remote 端日志捕获**:通知 Remote 端开始发送日志
3. **创建接收线程**:根据配置的触发策略自动接收和输出日志
- 手动触发模式:按配置的轮询间隔定期检查
- 自动触发模式:等待信号量触发,收到指定数量缓冲区后处理
#### 停止日志传输
```c
// 停止 logger - 自动停止线程和流通道
int ret = acomp_logger_stop();
if (ret != ACOMP_ERR_OK) {
printf("Logger stop failed: %d\n", ret);
}
```
`acomp_logger_stop()` 会自动:
1. 停止日志接收线程
2. 停止 Remote 端的日志捕获
3. 禁用并清理 R2M 流通道
## API 参考
### 基础 API
ACOMP Logger 组件提供了简洁的 API
#### `acomp_logger_init()`
初始化 logger 组件。
**返回值:**
- `ACOMP_ERR_OK` - 成功
- `ACOMP_ERR_NO_MEM` - 内存不足
- `ACOMP_ERR_INVALID_STATE` - 无效状态
- `ACOMP_ERR_NOT_FOUND` - 设备未找到
#### `acomp_logger_start()`
启动日志传输功能。
该函数会自动完成:
1. 根据 Kconfig 配置 R2M 流通道
2. 启动 Remote 端的日志捕获
3. 创建日志接收线程(根据触发策略工作)
**返回值:**
- `ACOMP_ERR_OK` - 成功
- `ACOMP_ERR_NO_MEM` - 内存不足
- `ACOMP_ERR_INVALID_STATE` - 无效状态
#### `acomp_logger_stop()`
停止日志传输功能。
该函数会自动完成:
1. 停止日志接收线程
2. 停止 Remote 端的日志捕获
3. 禁用并清理 R2M 流通道
**返回值:**
- `ACOMP_ERR_OK` - 成功
- `ACOMP_ERR_INVALID_STATE` - 无效状态
---
### 高级 API - 自定义输出
#### `acomp_logger_set_output_callback()`
```c
typedef int (*acomp_logger_output_cb_t)(const uint8_t *log, uint32_t len);
int acomp_logger_set_output_callback(acomp_logger_output_cb_t cb);
```
设置自定义日志输出回调函数,允许应用层完全控制 AP 日志的输出方式。
**参数:**
- `cb` - 输出回调函数指针
- 传入函数指针:使用自定义输出方式
- 传入 `NULL`恢复默认输出LISA_LOG_RAW
**返回值:**
- `ACOMP_ERR_OK` - 成功
- `ACOMP_ERR_INVALID_STATE` - 组件未初始化
**回调函数原型:**
```c
int output_callback(const uint8_t *log, uint32_t len)
{
// log: 日志数据缓冲区(已格式化的字符串)
// len: 日志数据长度(字节数)
// 返回: 0 成功,非 0 失败
}
```
**使用示例:**
```c
/* 示例 1: 输出到 printf */
int my_output(const uint8_t *log, uint32_t len) {
printf("[AP] %.*s", len, log);
return 0;
}
/* 示例 2: 带颜色高亮 */
int colored_output(const uint8_t *log, uint32_t len) {
printf("\033[46m[AP]\033[0m %.*s", len, log);
return 0;
}
/* 示例 3: 写入文件 */
int file_output(const uint8_t *log, uint32_t len) {
fwrite(log, 1, len, log_file);
return 0;
}
/* 使用自定义输出 */
acomp_logger_init();
acomp_logger_set_output_callback(my_output); // 设置自定义输出
acomp_logger_start();
/* 恢复默认输出 */
acomp_logger_set_output_callback(NULL); // 恢复默认
```
**注意事项:**
- 必须在 `acomp_logger_init()` 之后调用
- 可以在运行时动态切换回调函数
- 回调函数在日志接收线程中执行,应避免阻塞操作
- 回调函数应尽快返回,避免影响日志接收性能
## 配置参数
所有配置参数通过 Kconfig 系统管理,可通过 `make menuconfig` 进行配置。
### Master 端配置
`Components config -> ACOMP Logger` 菜单中配置:
#### 缓冲区配置
- **`ACOMP_LOGGER_STREAM_BUFFER_SIZE`**
- 说明:每个流缓冲区的大小(字节)
- 默认值256
- 建议:根据日志量调整,较大的缓冲区可以减少传输次数
- **`ACOMP_LOGGER_STREAM_BUFFER_COUNT`**
- 说明:流缓冲区的数量
- 默认值128
- 要求:**必须是 2 的幂次方**例如2, 4, 8, 16, 32, 64, 128
- 建议:增加缓冲区数量可提高性能,但会消耗更多内存
#### 触发策略
- **`ACOMP_LOGGER_KICK_POLICY`**
- 选项:
- **Manual kick**(默认):手动轮询模式
- **Auto kick**:自动触发模式
**手动触发模式配置:**
- **`ACOMP_LOGGER_MANUAL_POLL_INTERVAL_MS`**
- 说明:缓冲区检查轮询间隔(毫秒)
- 默认值10
- 适用:手动触发模式
- 建议:减小间隔可提高实时性,但会增加 CPU 占用
**自动触发模式配置:**
- **`ACOMP_LOGGER_AUTO_KICK_BUFFER_THRESHOLD`**
- 说明:自动触发的缓冲区阈值
- 默认值4
- 适用:自动触发模式
- 说明:接收到此数量的缓冲区后自动处理
#### 线程配置
- **`ACOMP_LOGGER_THREAD_PRIORITY`**
- 说明:接收线程优先级
- 默认值5
- 范围0最低到 31最高
- 建议:根据系统负载调整,较高优先级可确保日志及时输出
- **`ACOMP_LOGGER_THREAD_STACK_SIZE`**
- 说明:接收线程栈大小(字节)
- 默认值2048
- 建议:如遇栈溢出,适当增加此值
#### 日志显示配置
- **`ACOMP_LOGGER_USE_COLOR`**
- 说明:启用颜色高亮显示 AP 日志
- 类型:布尔值
- 默认值:是(启用)
- 功能:使用 ANSI 颜色码为 AP 日志添加背景色,便于区分 AP 日志和 Master 日志
- 注意:需要终端支持 ANSI 转义码
- **`ACOMP_LOGGER_COLOR_SCHEME`**(仅当启用颜色时可用)
- 说明AP 日志的颜色方案
- 类型:单选
- 默认值:`Cyan background`(青色背景)
- 可选值:
- **Cyan background**(推荐) - 青色背景 + 黑色文字
- **Blue background** - 蓝色背景 + 白色文字
- **Green background** - 绿色背景 + 黑色文字
- **Yellow background** - 黄色背景 + 黑色文字
- **Purple background** - 紫色背景 + 白色文字
- **Light gray background** - 浅灰色背景 + 黑色文字
**颜色效果示例:**
启用颜色后AP 日志将显示为:
```
[AP] I (12345) tag: This is an AP log message
^-- 带颜色背景的 [AP] 前缀
```
禁用颜色后AP 日志将显示为:
```
[AP] I (12345) tag: This is an AP log message
^-- 纯文本 [AP] 前缀
```
**配置方法:**
方式 1 - 通过 menuconfig
```bash
make menuconfig
# 导航到: Components config -> ACOMP Logger
# 选择: Enable color highlighting for AP logs
# 选择: AP log color scheme
```
方式 2 - 通过配置文件prj.conf
```conf
CONFIG_ACOMP_LOGGER_USE_COLOR=y
CONFIG_ACOMP_LOGGER_COLOR_CYAN_BG=y
```
方式 3 - 禁用颜色:
```conf
CONFIG_ACOMP_LOGGER_USE_COLOR=n
```
### Remote 端配置
Remote 端无需配置,设备会自动注册并初始化。
## 注意事项
### 一般使用
1. **简单 API**:只需调用 `init()``start()`,无需手动管理流通道和缓冲区
2. **自动化管理**:所有资源管理由组件内部自动完成
3. **线程安全**:专用接收线程自动处理所有日志接收和输出
4. **优雅停止**`acomp_logger_stop()` 会等待线程安全退出后再清理资源
5. **日志格式**:接收到的日志已是格式化字符串,直接通过 `lisa_log` 输出
### 配置建议
1. **缓冲区数量**
- 必须是 2 的幂次方2, 4, 8, 16, 32, 64, 128
- 默认 128 个缓冲区适合大多数场景
- 日志量大时可适当增加
2. **缓冲区大小**
- 默认 256 字节适合一般日志
- 单条日志超过缓冲区大小会被截断
- 根据实际日志长度调整
3. **触发模式选择**
- **手动触发**(推荐):简单可靠,适合大多数场景
- **自动触发**:减少轮询开销,适合日志密集的场景
4. **性能调优**
- 手动模式:减小轮询间隔可提高实时性,但增加 CPU 占用
- 自动模式:调整缓冲区阈值平衡实时性和批处理效率
- 线程优先级:根据系统负载调整,确保日志及时输出
5. **自定义输出**
- **默认行为**:不设置回调时,使用 `LISA_LOG_RAW` 输出,带颜色底纹
- **自定义控制**:通过 `acomp_logger_set_output_callback()` 完全控制输出方式
- **应用场景**
- 输出到不同目标(文件、网络、数据库)
- 日志过滤和分类
- 格式转换JSON、XML 等)
- 日志统计和分析
- **性能注意**:回调函数在接收线程中执行,避免长时间阻塞
6. **颜色显示**
- 默认启用青色背景,便于区分 AP 日志
- 可通过 Kconfig 选择不同颜色或禁用
- 需要终端支持 ANSI 转义码
- 自定义输出时可自行决定是否使用颜色
### 故障排查
1. **日志丢失**:增加缓冲区数量或大小
2. **延迟高**:减小轮询间隔或降低自动触发阈值
3. **CPU 占用高**:增加轮询间隔或使用自动触发模式
4. **栈溢出**:增加线程栈大小
5. **颜色不显示**
- 检查终端是否支持 ANSI 颜色码
- 确认 `CONFIG_ACOMP_LOGGER_USE_COLOR=y`
- 部分串口工具可能不支持颜色显示
6. **自定义输出无效**
- 确认在 `acomp_logger_init()` 之后调用
- 检查回调函数是否正确注册
- 验证回调函数返回值
## 文件结构
```
arcs-sdk/components/acomp/logger/
├── acomp_logger.c # Master 端核心实现
├── acomp_logger.h # Master 端公共 API
├── acomp_logger_example.c # 使用示例代码
├── Kconfig # 配置选项定义
├── CMakeLists.txt # Master 端构建配置
└── README.md # 本文档
apps/aopu-yuba_ap/acomp/logger/
├── logger.c # Remote 端实现
├── logger.h # Remote 端头文件
├── Kconfig # Remote 端配置
└── CMakeLists.txt # Remote 端构建配置
```
## 使用示例
完整的使用示例请参考 [acomp_logger_example.c](acomp_logger_example.c) 文件。
### 基础示例
```c
#include "acomp_logger.h"
#define TAG "my_app"
#include "lisa_log.h"
int main(void)
{
int ret;
/* 1. 初始化 logger 组件 */
ret = acomp_logger_init();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Logger init failed: %d", ret);
return ret;
}
/* 2. 启动 logger 组件 */
ret = acomp_logger_start();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Logger start failed: %d", ret);
return ret;
}
LISA_LOGI(TAG, "Logger started successfully!");
/* 应用程序主循环 */
while (1) {
/* Remote 端的日志会自动显示在这里 */
vTaskDelay(pdMS_TO_TICKS(1000));
}
/* 3. 停止 logger 组件(可选) */
acomp_logger_stop();
return 0;
}
```
### 自定义输出示例
#### 示例 1输出到 printf
```c
#include "acomp_logger.h"
/* 自定义输出函数 */
int my_log_output(const uint8_t *log, uint32_t len)
{
printf("[AP] %.*s", len, log);
return 0;
}
int main(void)
{
/* 初始化 */
acomp_logger_init();
/* 设置自定义输出 */
acomp_logger_set_output_callback(my_log_output);
/* 启动 */
acomp_logger_start();
/* AP 日志现在通过 printf 输出 */
return 0;
}
```
#### 示例 2带颜色高亮
```c
/* 带 ANSI 颜色码的输出 */
int colored_log_output(const uint8_t *log, uint32_t len)
{
/* 青色背景 + 黑色文字 */
printf("\033[46m\033[30m[AP]\033[0m %.*s", len, log);
return 0;
}
int main(void)
{
acomp_logger_init();
acomp_logger_set_output_callback(colored_log_output);
acomp_logger_start();
return 0;
}
```
#### 示例 3日志过滤
```c
/* 只输出错误日志 */
int error_only_output(const uint8_t *log, uint32_t len)
{
const char *log_str = (const char *)log;
/* 检查是否包含错误标记 */
if (strstr(log_str, " E (") || strstr(log_str, "ERROR")) {
printf("[AP ERROR] %.*s", len, log);
}
return 0;
}
```
#### 示例 4写入文件
```c
#include <stdio.h>
static FILE *log_file = NULL;
/* 初始化日志文件 */
void log_file_init(void)
{
log_file = fopen("/sdcard/ap_logs.txt", "a");
}
/* 输出到文件 */
int file_log_output(const uint8_t *log, uint32_t len)
{
if (log_file) {
fwrite(log, 1, len, log_file);
fflush(log_file); /* 立即刷新 */
}
return 0;
}
int main(void)
{
log_file_init();
acomp_logger_init();
acomp_logger_set_output_callback(file_log_output);
acomp_logger_start();
return 0;
}
```
#### 示例 5运行时切换输出方式
```c
int main(void)
{
acomp_logger_init();
acomp_logger_start();
/* 阶段 1使用默认输出 */
LISA_LOGI(TAG, "Using default output");
vTaskDelay(pdMS_TO_TICKS(5000));
/* 阶段 2切换到自定义输出 */
acomp_logger_set_output_callback(my_log_output);
LISA_LOGI(TAG, "Switched to custom output");
vTaskDelay(pdMS_TO_TICKS(5000));
/* 阶段 3恢复默认输出 */
acomp_logger_set_output_callback(NULL);
LISA_LOGI(TAG, "Restored default output");
vTaskDelay(pdMS_TO_TICKS(5000));
acomp_logger_stop();
return 0;
}
```
## 常见问题
### Q: 日志没有显示?
检查以下几点:
1. 确认 Remote 端已启用 logger 组件
2. 确认 Master 端已调用 `acomp_logger_start()`
3. 检查 Remote 端是否有日志输出
4. 检查配置参数是否正确
### Q: 如何提高日志传输性能?
1. 增加缓冲区数量(`ACOMP_LOGGER_STREAM_BUFFER_COUNT`
2. 增大缓冲区大小(`ACOMP_LOGGER_STREAM_BUFFER_SIZE`
3. 使用自动触发模式并调整阈值
4. 提高接收线程优先级
### Q: 日志出现乱码或截断?
1. 确认缓冲区大小足够容纳单条日志
2. 检查字符编码是否一致
3. 确认没有缓冲区溢出
## 更新日志
### v2.0
- 简化 API移除手动管理函数
- 移除事件回调机制
- 新增 Kconfig 配置系统
- 支持自动触发和手动触发两种模式
- 增加缓冲区配置灵活性
- 优化线程管理和资源清理
### v1.0
- 初始版本

View File

@@ -0,0 +1,397 @@
#include <string.h>
#include "ipc/acomp_ipc.h"
#include "acomp_logger.h"
#include "acomp_err.h"
#include "gcl_cb_list/gcl_cb_list.h"
#include "acomp_stream_ipc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#define TAG "acomp_logger"
#include "lisa_log.h"
#define ACOMP_LOGGER_DEV_NAME "acomp.logger"
#define ACOMP_LOGGER_STREAM_CH_NAME "stream.logger"
#define ACOMP_LOGGER_STREAM_CHN (0)
/* 从 Kconfig 获取配置参数 */
#define ACOMP_LOGGER_RX_TASK_STACK_SIZE CONFIG_ACOMP_LOGGER_THREAD_STACK_SIZE
#define ACOMP_LOGGER_RX_TASK_PRIORITY CONFIG_ACOMP_LOGGER_THREAD_PRIORITY
typedef struct {
uint32_t dev_index;
gcl_cb_list_t event_callbacks;
acomp_stream_t *stream;
TaskHandle_t rx_task_handle;
volatile bool rx_task_running;
SemaphoreHandle_t rx_sem; /* 接收信号量,用于自动触发模式 */
acomp_logger_output_cb_t output_cb; /* 自定义输出回调函数 */
} acomp_logger_handle_t;
static acomp_logger_handle_t *logger_handle = NULL;
static int _logger_stream_ch_enable(int chn, acomp_stream_chn_create_desc_t *desc);
static int _logger_stream_ch_disable(int chn);
static void *_logger_stream_rx_buffer_get(int chn, uint32_t *len, uint16_t *desc_idx);
static int _logger_stream_rx_buffer_release(int chn, uint16_t desc_idx, uint32_t len, void *buffer);
/* 日志接收线程 */
static void logger_rx_task(void *param)
{
uint8_t *buffer;
uint32_t len;
uint16_t desc_idx;
int ret;
acomp_logger_handle_t *handle = (acomp_logger_handle_t *)param;
#ifdef CONFIG_ACOMP_LOGGER_KICK_AUTO
LISA_LOGI(TAG, "Logger RX task started (auto kick mode, threshold=%d)",
CONFIG_ACOMP_LOGGER_AUTO_KICK_BUFFER_THRESHOLD);
#else
LISA_LOGI(TAG, "Logger RX task started (manual kick mode, poll interval=%dms)",
CONFIG_ACOMP_LOGGER_MANUAL_POLL_INTERVAL_MS);
#endif
while (handle->rx_task_running) {
#ifdef CONFIG_ACOMP_LOGGER_KICK_AUTO
/* 自动触发模式:等待来自回调的信号量 */
xSemaphoreTake(handle->rx_sem, pdMS_TO_TICKS(100));
#else
/* 手动触发模式:等待超时时间 */
vTaskDelay(pdMS_TO_TICKS(CONFIG_ACOMP_LOGGER_MANUAL_POLL_INTERVAL_MS));
#endif
/* 从 R2M 流中获取日志数据 */
while (handle->rx_task_running) {
buffer = _logger_stream_rx_buffer_get(ACOMP_LOGGER_STREAM_CHN, &len, &desc_idx);
if (buffer != NULL && len > 0) {
/* 使用自定义回调或默认方式输出日志 */
if (handle->output_cb) {
/* 调用用户自定义的输出回调 */
handle->output_cb(buffer, len);
}
/* 释放缓冲区 */
ret = _logger_stream_rx_buffer_release(ACOMP_LOGGER_STREAM_CHN, desc_idx, len, buffer);
if (ret != ACOMP_ERR_OK) {
LISA_LOGW(TAG, "Failed to release buffer: %d", ret);
}
} else {
/* 没有更多数据,跳出内层循环 */
break;
}
}
}
LISA_LOGI(TAG, "Logger RX task exited");
handle->rx_task_handle = NULL;
vTaskDelete(NULL);
}
static void event_callback(acomp_ipc_message_t *message, void *priv)
{
acomp_logger_handle_t *handle = (acomp_logger_handle_t *)priv;
if (handle == NULL) {
return;
}
if (message->hdr.hdr.cmd == ACOMP_CONTEXT_IPC_GLB_NOTIFY) {
if (message->acomp_cmd == ACOMP_IPC_CMD_NOTIFY_STREAM_UPDATE) {
if (((void *)message->address != NULL) && (message->len > 0)) {
acomp_ipc_stream_update_t *ipc_msg;
uint32_t chn;
ipc_msg = (acomp_ipc_stream_update_t *)message->address;
chn = ipc_msg->index;
if (chn < sizeof(handle->stream->ch) / sizeof(handle->stream->ch[0])) {
if(chn == ACOMP_LOGGER_STREAM_CHN) {
#ifdef CONFIG_ACOMP_LOGGER_KICK_AUTO
/* 自动触发模式:发送信号量通知接收线程 */
if (handle->rx_sem != NULL) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(handle->rx_sem, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
#endif
}
}
}
}
}
}
/**
* @brief 设置日志输出回调函数
*
* @param cb 输出回调函数,传入 NULL 则恢复默认输出方式
* @return ACOMP_ERR_OK 成功
* @return ACOMP_ERR_INVALID_STATE 组件未初始化
*/
int acomp_logger_set_output_callback(acomp_logger_output_cb_t cb)
{
if (logger_handle == NULL) {
LISA_LOGE(TAG, "Logger not initialized");
return ACOMP_ERR_INVALID_STATE;
}
logger_handle->output_cb = cb;
if (cb == NULL) {
LISA_LOGI(TAG, "Output callback cleared, using default output");
} else {
LISA_LOGI(TAG, "Output callback set to %p", cb);
}
return ACOMP_ERR_OK;
}
int acomp_logger_init(void)
{
int ret = 0;
if (logger_handle != NULL) {
return ACOMP_ERR_INVALID_STATE;
}
logger_handle = (acomp_logger_handle_t *)psram_malloc(sizeof(acomp_logger_handle_t));
if (logger_handle == NULL) {
return ACOMP_ERR_NO_MEM;
}
memset(logger_handle, 0, sizeof(acomp_logger_handle_t));
logger_handle->event_callbacks = gcl_cb_list_create();
logger_handle->dev_index = acomp_ipc_get_dev_index(ACOMP_LOGGER_DEV_NAME);
if (logger_handle->dev_index < 0) {
psram_free(logger_handle);
logger_handle = NULL;
LISA_LOGE(TAG, "acomp logger dev index not found!");
return ACOMP_ERR_NOT_FOUND;
}
LISA_LOGI(TAG, "acomp logger dev index %d, name:%s", logger_handle->dev_index, ACOMP_LOGGER_DEV_NAME);
#ifdef CONFIG_ACOMP_LOGGER_KICK_AUTO
/* 自动触发模式:创建二值信号量 */
logger_handle->rx_sem = xSemaphoreCreateBinary();
if (logger_handle->rx_sem == NULL) {
LISA_LOGE(TAG, "Failed to create rx semaphore");
psram_free(logger_handle);
logger_handle = NULL;
return ACOMP_ERR_NO_MEM;
}
#endif
ret = acomp_ipc_add_callback(logger_handle->dev_index, (ipc_event_cb_t)event_callback, logger_handle);
if (ret != ACOMP_ERR_OK) {
return ret;
}
ret = acomp_ipc_build_frame_send_sync(logger_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_NEW | IPC_HEADER_REQ_REPALY, 0,
0, NULL, 0);
if (ret != ACOMP_ERR_OK) {
return ret;
}
logger_handle->stream = acomp_stream_create(logger_handle->dev_index);
if (logger_handle->stream == NULL) {
return ACOMP_ERR_CREATE_STREAM_FAILED;
}
return 0;
}
static int _logger_prepare(void)
{
int ret;
ret = acomp_ipc_build_frame_send_sync(logger_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_PREPARE, 0, NULL, 0);
return ret;
}
int acomp_logger_start(void)
{
int ret;
acomp_stream_chn_create_desc_t stream_desc = {
.cname = "logger.stream.0",
.direction = ACOMP_STREAM_DIRECTION_R2M,
.index = 0,
.buffer_size = CONFIG_ACOMP_LOGGER_STREAM_BUFFER_SIZE,
.num_descs = CONFIG_ACOMP_LOGGER_STREAM_BUFFER_COUNT,
#ifdef CONFIG_ACOMP_LOGGER_KICK_AUTO
.kick_policy = CONFIG_ACOMP_LOGGER_AUTO_KICK_BUFFER_THRESHOLD, /* 自动触发 */
#else
.kick_policy = 0, /* 手动触发 */
#endif
};
BaseType_t task_ret;
if (logger_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
/* 准备资源 */
_logger_prepare();
/* 启用流通道 */
ret = _logger_stream_ch_enable(ACOMP_LOGGER_STREAM_CHN, &stream_desc);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Failed to enable stream channel: %d", ret);
return ret;
}
/* 创建日志接收线程 */
logger_handle->rx_task_running = true;
task_ret = xTaskCreate(logger_rx_task, "logger_rx", ACOMP_LOGGER_RX_TASK_STACK_SIZE,
logger_handle, ACOMP_LOGGER_RX_TASK_PRIORITY, &logger_handle->rx_task_handle);
if (task_ret != pdPASS) {
LISA_LOGE(TAG, "Failed to create logger RX task");
logger_handle->rx_task_running = false;
_logger_stream_ch_disable(ACOMP_LOGGER_STREAM_CHN);
return ACOMP_ERR_NO_MEM;
}
/* 启动 remote 端的 logger */
ret = acomp_ipc_build_frame_send_sync(logger_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_START, 0, NULL, 0);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Failed to start remote logger: %d", ret);
_logger_stream_ch_disable(ACOMP_LOGGER_STREAM_CHN);
return ret;
}
LISA_LOGI(TAG, "Logger started successfully (buffer_size=%d, buffer_count=%d)",
CONFIG_ACOMP_LOGGER_STREAM_BUFFER_SIZE, CONFIG_ACOMP_LOGGER_STREAM_BUFFER_COUNT);
return ACOMP_ERR_OK;
}
int acomp_logger_stop(void)
{
int ret;
if (logger_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
/* 停止接收线程 */
if (logger_handle->rx_task_handle != NULL) {
logger_handle->rx_task_running = false;
#ifdef CONFIG_ACOMP_LOGGER_KICK_AUTO
/* 自动触发模式:发送信号量以唤醒阻塞的线程,使其能够退出 */
if (logger_handle->rx_sem != NULL) {
xSemaphoreGive(logger_handle->rx_sem);
}
#endif
/* 等待任务退出,最多等待 1 秒 */
int wait_count = 100;
while (logger_handle->rx_task_handle != NULL && wait_count-- > 0) {
vTaskDelay(pdMS_TO_TICKS(10));
}
if (logger_handle->rx_task_handle != NULL) {
LISA_LOGW(TAG, "Logger RX task did not exit gracefully, force delete");
vTaskDelete(logger_handle->rx_task_handle);
logger_handle->rx_task_handle = NULL;
}
}
#ifdef CONFIG_ACOMP_LOGGER_KICK_AUTO
/* 自动触发模式:删除信号量 */
if (logger_handle->rx_sem != NULL) {
vSemaphoreDelete(logger_handle->rx_sem);
logger_handle->rx_sem = NULL;
}
#endif
/* 停止 remote 端的 logger */
ret = acomp_ipc_build_frame_send_sync(logger_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_STOP, 0, NULL, 0);
if (ret != ACOMP_ERR_OK) {
LISA_LOGW(TAG, "Failed to stop remote logger: %d", ret);
}
/* 禁用流通道 */
_logger_stream_ch_disable(ACOMP_LOGGER_STREAM_CHN);
LISA_LOGI(TAG, "Logger stopped");
return ACOMP_ERR_OK;
}
static int _logger_stream_ch_enable(int chn, acomp_stream_chn_create_desc_t *desc)
{
int ret = 0;
if ((logger_handle == NULL) || (logger_handle->stream == NULL)) {
return ACOMP_ERR_INVALID_STATE;
}
if (chn >= ACOMP_STREAM_MAX_CHANNEL) {
return ACOMP_ERR_INVALID_ARG;
}
logger_handle->stream->ch[chn] =
acomp_stream_ipc_channel_create(logger_handle->stream, chn, logger_handle->dev_index, desc);
if (logger_handle->stream->ch[chn] == NULL) {
return ACOMP_ERR_CREATE_STREAM_FAILED;
}
LISA_LOGI(TAG, "acomp_logger_stream_ch_enable chn(%s) index(%d), desc(%p)", desc->cname, chn, desc);
return ret;
}
int _logger_stream_ch_disable(int chn)
{
int ret;
if (logger_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if (chn >= ACOMP_STREAM_MAX_CHANNEL) {
return ACOMP_ERR_INVALID_ARG;
}
ret = acomp_stream_ipc_channel_destroy(chn);
LISA_LOGI(TAG, "acomp_logger_stream_ch_disable chn index(%d), ret(%d)", chn, ret);
logger_handle->stream->ch[chn] = NULL;
return ret;
}
static void *_logger_stream_rx_buffer_get(int chn, uint32_t *len, uint16_t *desc_idx)
{
uint8_t *ptr;
if (logger_handle == NULL) {
return NULL;
}
if (chn >= ACOMP_STREAM_MAX_CHANNEL) {
return NULL;
}
if (logger_handle->stream->ch[chn] == NULL) {
return NULL;
}
ptr = logger_handle->stream->ops.rx_buffer_get(logger_handle->stream->ch[chn], len, desc_idx);
return ptr;
}
static int _logger_stream_rx_buffer_release(int chn, uint16_t desc_idx, uint32_t len, void *buffer)
{
int ret;
if (logger_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if (chn >= ACOMP_STREAM_MAX_CHANNEL) {
return ACOMP_ERR_INVALID_ARG;
}
ret = logger_handle->stream->ops.rx_buffer_release(logger_handle->stream->ch[chn], buffer, len, desc_idx);
return ret;
}

View File

@@ -0,0 +1,105 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "utils/acomp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 日志输出回调函数类型
*
* 应用层可以通过 acomp_logger_set_output_callback() 注册此回调函数,
* 自定义 AP 日志的输出方式。
*
* @param log 日志数据缓冲区指针
* @param len 日志数据长度(字节数)
*
* @return 0 表示成功,非 0 表示失败
*
* @note 此回调函数在日志接收线程中调用,应避免阻塞操作
* @note 日志数据已经是格式化好的字符串,可以直接输出
*/
typedef int (*acomp_logger_output_cb_t)(const uint8_t *log, uint32_t len);
/**
* @brief 初始化日志传输组件Logger
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_NOT_FOUND : 设备未找到
*/
extern int acomp_logger_init(void);
/**
* @brief 设置日志输出回调函数
*
* 允许应用层自定义 AP 日志的输出方式。如果不设置回调函数,
* 日志将使用默认方式通过 LISA_LOG_RAW 输出。
*
* @param cb 日志输出回调函数指针,传入 NULL 则恢复使用默认输出方式
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 组件未初始化
*
* @note 必须在 acomp_logger_init() 之后调用
* @note 可以在运行时动态更改回调函数
* @note 回调函数在日志接收线程中执行,应避免长时间阻塞操作
*
* @example
* ```c
* // 自定义输出函数
* int my_log_output(const uint8_t *log, uint32_t len)
* {
* printf("[AP] %.*s", len, log);
* return 0;
* }
*
* // 注册回调
* acomp_logger_set_output_callback(my_log_output);
*
* // 恢复默认输出
* acomp_logger_set_output_callback(NULL);
* ```
*/
extern int acomp_logger_set_output_callback(acomp_logger_output_cb_t cb);
/**
* @brief 启动日志传输组件Logger
*
* @note 该函数会自动完成以下操作:
* 1. 配置 R2M 流通道(缓冲区大小和数量由 Kconfig 配置决定)
* - CONFIG_ACOMP_LOGGER_STREAM_BUFFER_SIZE: 每个缓冲区大小
* - CONFIG_ACOMP_LOGGER_STREAM_BUFFER_COUNT: 缓冲区数量必须是2的指数倍
* 2. 启动 Remote 端的日志捕获
* 3. 创建日志接收线程,根据 Kconfig 配置的触发策略工作:
* - 自动触发模式:等待流更新信号量,收到指定数量的 buffer 后自动处理
* - 手动触发模式:按配置的轮询间隔定期检查并处理 buffer
* 无需手动调用流管理或缓冲区操作 API
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*/
extern int acomp_logger_start(void);
/**
* @brief 停止日志传输组件Logger
*
* @note 该函数会自动完成以下操作:
* 1. 停止日志接收线程
* 2. 停止 Remote 端的日志捕获
* 3. 禁用 R2M 流通道
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*/
extern int acomp_logger_stop(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,267 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* ACOMP Logger 使用示例
*
* 展示如何在 Master 端启用和使用日志传输功能。
* Logger 组件通过 R2M 流从 Remote (AP) 端接收日志并输出到 Master 端。
*/
#include "acomp_logger.h"
#define TAG "logger_example"
#include "lisa_log.h"
/**
* @brief 基础使用示例
*
* 这是最简单的使用方式只需两步init 和 start
*
* acomp_logger_start() 会自动完成:
* - 根据 Kconfig 配置 R2M 流通道(缓冲区大小和数量)
* - 启动 Remote 端的日志捕获
* - 创建接收线程(根据触发策略工作)
* - 自动接收并输出日志
*/
int acomp_logger_example_init(void)
{
int ret;
/* 步骤 1: 初始化 logger 组件 */
ret = acomp_logger_init();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Failed to initialize logger: %d", ret);
return ret;
}
/* 步骤 2: 启动 logger 组件 */
ret = acomp_logger_start();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Failed to start logger: %d", ret);
return ret;
}
LISA_LOGI(TAG, "Logger started successfully!");
LISA_LOGI(TAG, "Remote logs will now be displayed automatically");
return ACOMP_ERR_OK;
}
/**
* @brief 停止日志传输
*
* 停止 logger 组件会自动:
* - 停止接收线程
* - 停止 Remote 端的日志捕获
* - 禁用并清理 R2M 流通道
*/
int acomp_logger_example_deinit(void)
{
int ret;
/* 停止 logger 组件 */
ret = acomp_logger_stop();
if (ret != ACOMP_ERR_OK) {
LISA_LOGW(TAG, "Failed to stop logger: %d", ret);
return ret;
}
LISA_LOGI(TAG, "Logger stopped successfully");
return ACOMP_ERR_OK;
}
/**
* @brief 完整示例:初始化、运行、停止
*
* 这个示例展示了 logger 组件的完整生命周期
*/
int acomp_logger_example_full(void)
{
int ret;
LISA_LOGI(TAG, "=== ACOMP Logger Full Example ===");
/* 初始化 */
ret = acomp_logger_example_init();
if (ret != ACOMP_ERR_OK) {
return ret;
}
/*
* 应用程序主循环
* 在这期间Remote 端的日志会自动显示
*/
LISA_LOGI(TAG, "Logger is running...");
LISA_LOGI(TAG, "You should now see Remote logs appearing");
/* 模拟运行一段时间 */
for (int i = 0; i < 10; i++) {
LISA_LOGI(TAG, "Main loop iteration %d", i);
vTaskDelay(pdMS_TO_TICKS(1000));
}
/* 停止 logger */
LISA_LOGI(TAG, "Stopping logger...");
ret = acomp_logger_example_deinit();
if (ret != ACOMP_ERR_OK) {
return ret;
}
LISA_LOGI(TAG, "=== Example completed ===");
return ACOMP_ERR_OK;
}
/**
* @brief 错误处理示例
*
* 展示如何处理 logger API 的错误返回值
*/
int acomp_logger_example_with_error_handling(void)
{
int ret;
/* 初始化 logger */
ret = acomp_logger_init();
switch (ret) {
case ACOMP_ERR_OK:
LISA_LOGI(TAG, "Logger initialized");
break;
case ACOMP_ERR_NO_MEM:
LISA_LOGE(TAG, "Initialization failed: Out of memory");
return ret;
case ACOMP_ERR_INVALID_STATE:
LISA_LOGE(TAG, "Initialization failed: Invalid state");
return ret;
case ACOMP_ERR_NOT_FOUND:
LISA_LOGE(TAG, "Initialization failed: Logger device not found");
return ret;
default:
LISA_LOGE(TAG, "Initialization failed: Unknown error %d", ret);
return ret;
}
/* 启动 logger */
ret = acomp_logger_start();
switch (ret) {
case ACOMP_ERR_OK:
LISA_LOGI(TAG, "Logger started");
break;
case ACOMP_ERR_NO_MEM:
LISA_LOGE(TAG, "Start failed: Out of memory");
goto cleanup;
case ACOMP_ERR_INVALID_STATE:
LISA_LOGE(TAG, "Start failed: Invalid state (already started?)");
goto cleanup;
default:
LISA_LOGE(TAG, "Start failed: Unknown error %d", ret);
goto cleanup;
}
LISA_LOGI(TAG, "Logger running normally");
/* 应用程序运行... */
vTaskDelay(pdMS_TO_TICKS(5000));
/* 停止 logger */
ret = acomp_logger_stop();
if (ret != ACOMP_ERR_OK) {
LISA_LOGW(TAG, "Stop returned error: %d (continuing anyway)", ret);
}
return ACOMP_ERR_OK;
cleanup:
/* 清理资源 */
acomp_logger_stop();
return ret;
}
/**
* @brief 自定义日志输出回调示例
*
* 展示如何使用自定义回调函数来控制 AP 日志的输出方式
*/
/* 自定义日志输出函数 - 输出到 printf */
static int custom_log_output_printf(const uint8_t *log, uint32_t len)
{
printf("[AP] %.*s", len, log);
return 0;
}
/* 自定义日志输出函数 - 带颜色高亮 */
static int custom_log_output_with_color(const uint8_t *log, uint32_t len)
{
/* 使用 ANSI 颜色码:青色背景 + 黑色文字 */
printf("\033[46m\033[30m[AP]\033[0m %.*s", len, log);
return 0;
}
/* 自定义日志输出函数 - 写入文件 */
static int custom_log_output_to_file(const uint8_t *log, uint32_t len)
{
/* 这里可以实现写入文件的逻辑 */
/* 例如: fwrite(log, 1, len, log_file); */
LISA_LOGI(TAG, "[AP->FILE] %.*s", len, log);
return 0;
}
int acomp_logger_example_with_custom_output(void)
{
int ret;
LISA_LOGI(TAG, "=== Custom Output Callback Example ===");
/* 初始化 logger */
ret = acomp_logger_init();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Logger init failed: %d", ret);
return ret;
}
/* 方式 1: 使用默认输出 */
LISA_LOGI(TAG, "Using default output...");
ret = acomp_logger_start();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Logger start failed: %d", ret);
return ret;
}
vTaskDelay(pdMS_TO_TICKS(3000));
/* 方式 2: 切换到自定义 printf 输出 */
LISA_LOGI(TAG, "Switching to custom printf output...");
acomp_logger_set_output_callback(custom_log_output_printf);
vTaskDelay(pdMS_TO_TICKS(3000));
/* 方式 3: 切换到带颜色的输出 */
LISA_LOGI(TAG, "Switching to colored output...");
acomp_logger_set_output_callback(custom_log_output_with_color);
vTaskDelay(pdMS_TO_TICKS(3000));
/* 方式 4: 切换到文件输出 */
LISA_LOGI(TAG, "Switching to file output...");
acomp_logger_set_output_callback(custom_log_output_to_file);
vTaskDelay(pdMS_TO_TICKS(3000));
/* 恢复默认输出 */
LISA_LOGI(TAG, "Restoring default output...");
acomp_logger_set_output_callback(NULL);
vTaskDelay(pdMS_TO_TICKS(2000));
/* 停止 logger */
ret = acomp_logger_stop();
if (ret != ACOMP_ERR_OK) {
LISA_LOGW(TAG, "Logger stop failed: %d", ret);
}
LISA_LOGI(TAG, "=== Example completed ===");
return ACOMP_ERR_OK;
}

View File

@@ -0,0 +1,2 @@
add_subdirectory(gcl_cb_list)
target_include_directories(${TARGET_NAME} PUBLIC .)

View File

@@ -0,0 +1,26 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#define ACOMP_ERR_OK 0
#define ACOMP_ERR_NOT_FOUND -1
#define ACOMP_ERR_NO_MEM -2
#define ACOMP_ERR_INVALID_ARG -3
#define ACOMP_ERR_TIMEOUT -4
#define ACOMP_ERR_INVALID_STATE -5
#define ACOMP_ERR_BUSY -6
#define ACOMP_ERR_NOT_PERMITTED -7
#define ACOMP_ERR_NOT_SUPPORTED -8
#define ACOMP_ERR_UNKNOWN -9
#define ACOMP_ERR_CREATE_STREAM_FAILED -10

View File

@@ -0,0 +1,567 @@
/*
* Copyright (c) 2013-2015 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @defgroup doubly-linked-list_apis Doubly-linked list
* @ingroup datastructure_apis
*
* @brief Doubly-linked list implementation
*
* Doubly-linked list implementation using inline macros/functions.
* This API is not thread safe, and thus if a list is used across threads,
* calls to functions must be protected with synchronization primitives.
*
* The lists are expected to be initialized such that both the head and tail
* pointers point to the list itself. Initializing the lists in such a fashion
* simplifies the adding and removing of nodes to/from the list.
*
* @{
*/
#ifndef _DLIST_H_
#define _DLIST_H_
#include <stddef.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef CONTAINER_OF
#define CONTAINER_OF(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
#endif
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#endif
struct _dnode {
union {
struct _dnode *head; /* ptr to head of list (sys_dlist_t) */
struct _dnode *next; /* ptr to next node (sys_dnode_t) */
};
union {
struct _dnode *tail; /* ptr to tail of list (sys_dlist_t) */
struct _dnode *prev; /* ptr to previous node (sys_dnode_t) */
};
};
/**
* @brief Doubly-linked list structure.
*/
typedef struct _dnode sys_dlist_t;
/**
* @brief Doubly-linked list node structure.
*/
typedef struct _dnode sys_dnode_t;
/**
* @brief Provide the primitive to iterate on a list
* Note: the loop is unsafe and thus __dn should not be removed
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* SYS_DLIST_FOR_EACH_NODE(l, n) {
* <user code>
* }
*
* This and other SYS_DLIST_*() macros are not thread safe.
*
* @param __dl A pointer on a sys_dlist_t to iterate on
* @param __dn A sys_dnode_t pointer to peek each node of the list
*/
#define SYS_DLIST_FOR_EACH_NODE(__dl, __dn) \
for (__dn = sys_dlist_peek_head(__dl); __dn != NULL; \
__dn = sys_dlist_peek_next(__dl, __dn))
/**
* @brief Provide the primitive to iterate on a list, from a node in the list
* Note: the loop is unsafe and thus __dn should not be removed
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* SYS_DLIST_ITERATE_FROM_NODE(l, n) {
* <user code>
* }
*
* Like SYS_DLIST_FOR_EACH_NODE(), but __dn already contains a node in the list
* where to start searching for the next entry from. If NULL, it starts from
* the head.
*
* This and other SYS_DLIST_*() macros are not thread safe.
*
* @param __dl A pointer on a sys_dlist_t to iterate on
* @param __dn A sys_dnode_t pointer to peek each node of the list;
* it contains the starting node, or NULL to start from the head
*/
#define SYS_DLIST_ITERATE_FROM_NODE(__dl, __dn) \
for (__dn = __dn ? sys_dlist_peek_next_no_check(__dl, __dn) \
: sys_dlist_peek_head(__dl); \
__dn != NULL; \
__dn = sys_dlist_peek_next(__dl, __dn))
/**
* @brief Provide the primitive to safely iterate on a list
* Note: __dn can be removed, it will not break the loop.
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* SYS_DLIST_FOR_EACH_NODE_SAFE(l, n, s) {
* <user code>
* }
*
* This and other SYS_DLIST_*() macros are not thread safe.
*
* @param __dl A pointer on a sys_dlist_t to iterate on
* @param __dn A sys_dnode_t pointer to peek each node of the list
* @param __dns A sys_dnode_t pointer for the loop to run safely
*/
#define SYS_DLIST_FOR_EACH_NODE_SAFE(__dl, __dn, __dns) \
for ((__dn) = sys_dlist_peek_head(__dl), \
(__dns) = sys_dlist_peek_next((__dl), (__dn)); \
(__dn) != NULL; (__dn) = (__dns), \
(__dns) = sys_dlist_peek_next(__dl, __dn))
/**
* @brief Provide the primitive to resolve the container of a list node
* Note: it is safe to use with NULL pointer nodes
*
* @param __dn A pointer on a sys_dnode_t to get its container
* @param __cn Container struct type pointer
* @param __n The field name of sys_dnode_t within the container struct
*/
#define SYS_DLIST_CONTAINER(__dn, __cn, __n) \
(((__dn) != NULL) ? CONTAINER_OF(__dn, __typeof__(*(__cn)), __n) : NULL)
/**
* @brief Provide the primitive to peek container of the list head
*
* @param __dl A pointer on a sys_dlist_t to peek
* @param __cn Container struct type pointer
* @param __n The field name of sys_dnode_t within the container struct
*/
#define SYS_DLIST_PEEK_HEAD_CONTAINER(__dl, __cn, __n) \
SYS_DLIST_CONTAINER(sys_dlist_peek_head(__dl), __cn, __n)
/**
* @brief Provide the primitive to peek the next container
*
* @param __dl A pointer on a sys_dlist_t to peek
* @param __cn Container struct type pointer
* @param __n The field name of sys_dnode_t within the container struct
*/
#define SYS_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n) \
(((__cn) != NULL) ? \
SYS_DLIST_CONTAINER(sys_dlist_peek_next((__dl), &((__cn)->__n)), \
__cn, __n) : NULL)
/**
* @brief Provide the primitive to iterate on a list under a container
* Note: the loop is unsafe and thus __cn should not be detached
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* SYS_DLIST_FOR_EACH_CONTAINER(l, c, n) {
* <user code>
* }
*
* @param __dl A pointer on a sys_dlist_t to iterate on
* @param __cn A container struct type pointer to peek each entry of the list
* @param __n The field name of sys_dnode_t within the container struct
*/
#define SYS_DLIST_FOR_EACH_CONTAINER(__dl, __cn, __n) \
for ((__cn) = SYS_DLIST_PEEK_HEAD_CONTAINER(__dl, __cn, __n); \
(__cn) != NULL; \
(__cn) = SYS_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n))
/**
* @brief Provide the primitive to safely iterate on a list under a container
* Note: __cn can be detached, it will not break the loop.
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* SYS_DLIST_FOR_EACH_CONTAINER_SAFE(l, c, cn, n) {
* <user code>
* }
*
* @param __dl A pointer on a sys_dlist_t to iterate on
* @param __cn A container struct type pointer to peek each entry of the list
* @param __cns A container struct type pointer for the loop to run safely
* @param __n The field name of sys_dnode_t within the container struct
*/
#define SYS_DLIST_FOR_EACH_CONTAINER_SAFE(__dl, __cn, __cns, __n) \
for ((__cn) = SYS_DLIST_PEEK_HEAD_CONTAINER(__dl, __cn, __n), \
(__cns) = SYS_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n); \
(__cn) != NULL; (__cn) = (__cns), \
(__cns) = SYS_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n))
/**
* @brief initialize list to its empty state
*
* @param list the doubly-linked list
*/
static inline void sys_dlist_init(sys_dlist_t *list)
{
list->head = (sys_dnode_t *)list;
list->tail = (sys_dnode_t *)list;
}
/**
* @brief Static initializer for a doubly-linked list
*/
#define SYS_DLIST_STATIC_INIT(ptr_to_list) { {(ptr_to_list)}, {(ptr_to_list)} }
/**
* @brief initialize node to its state when not in a list
*
* @param node the node
*/
static inline void sys_dnode_init(sys_dnode_t *node)
{
node->next = NULL;
node->prev = NULL;
}
/**
* @brief check if a node is a member of any list
*
* @param node the node
*
* @return true if node is linked into a list, false if it is not
*/
static inline bool sys_dnode_is_linked(const sys_dnode_t *node)
{
return node->next != NULL;
}
/**
* @brief check if a node is the list's head
*
* @param list the doubly-linked list to operate on
* @param node the node to check
*
* @return true if node is the head, false otherwise
*/
static inline bool sys_dlist_is_head(sys_dlist_t *list, sys_dnode_t *node)
{
return list->head == node;
}
/**
* @brief check if a node is the list's tail
*
* @param list the doubly-linked list to operate on
* @param node the node to check
*
* @return true if node is the tail, false otherwise
*/
static inline bool sys_dlist_is_tail(sys_dlist_t *list, sys_dnode_t *node)
{
return list->tail == node;
}
/**
* @brief check if the list is empty
*
* @param list the doubly-linked list to operate on
*
* @return true if empty, false otherwise
*/
static inline bool sys_dlist_is_empty(sys_dlist_t *list)
{
return list->head == list;
}
/**
* @brief check if more than one node present
*
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
*
* @return true if multiple nodes, false otherwise
*/
static inline bool sys_dlist_has_multiple_nodes(sys_dlist_t *list)
{
return list->head != list->tail;
}
/**
* @brief get a reference to the head item in the list
*
* @param list the doubly-linked list to operate on
*
* @return a pointer to the head element, NULL if list is empty
*/
static inline sys_dnode_t *sys_dlist_peek_head(sys_dlist_t *list)
{
return sys_dlist_is_empty(list) ? NULL : list->head;
}
/**
* @brief get a reference to the head item in the list
*
* The list must be known to be non-empty.
*
* @param list the doubly-linked list to operate on
*
* @return a pointer to the head element
*/
static inline sys_dnode_t *sys_dlist_peek_head_not_empty(sys_dlist_t *list)
{
return list->head;
}
/**
* @brief get a reference to the next item in the list, node is not NULL
*
* Faster than sys_dlist_peek_next() if node is known not to be NULL.
*
* @param list the doubly-linked list to operate on
* @param node the node from which to get the next element in the list
*
* @return a pointer to the next element from a node, NULL if node is the tail
*/
static inline sys_dnode_t *sys_dlist_peek_next_no_check(sys_dlist_t *list,
sys_dnode_t *node)
{
return (node == list->tail) ? NULL : node->next;
}
/**
* @brief get a reference to the next item in the list
*
* @param list the doubly-linked list to operate on
* @param node the node from which to get the next element in the list
*
* @return a pointer to the next element from a node, NULL if node is the tail
* or NULL (when node comes from reading the head of an empty list).
*/
static inline sys_dnode_t *sys_dlist_peek_next(sys_dlist_t *list,
sys_dnode_t *node)
{
return (node != NULL) ? sys_dlist_peek_next_no_check(list, node) : NULL;
}
/**
* @brief get a reference to the previous item in the list, node is not NULL
*
* Faster than sys_dlist_peek_prev() if node is known not to be NULL.
*
* @param list the doubly-linked list to operate on
* @param node the node from which to get the previous element in the list
*
* @return a pointer to the previous element from a node, NULL if node is the
* tail
*/
static inline sys_dnode_t *sys_dlist_peek_prev_no_check(sys_dlist_t *list,
sys_dnode_t *node)
{
return (node == list->head) ? NULL : node->prev;
}
/**
* @brief get a reference to the previous item in the list
*
* @param list the doubly-linked list to operate on
* @param node the node from which to get the previous element in the list
*
* @return a pointer to the previous element from a node, NULL if node is the
* tail or NULL (when node comes from reading the head of an empty
* list).
*/
static inline sys_dnode_t *sys_dlist_peek_prev(sys_dlist_t *list,
sys_dnode_t *node)
{
return (node != NULL) ? sys_dlist_peek_prev_no_check(list, node) : NULL;
}
/**
* @brief get a reference to the tail item in the list
*
* @param list the doubly-linked list to operate on
*
* @return a pointer to the tail element, NULL if list is empty
*/
static inline sys_dnode_t *sys_dlist_peek_tail(sys_dlist_t *list)
{
return sys_dlist_is_empty(list) ? NULL : list->tail;
}
/**
* @brief add node to tail of list
*
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
* @param node the element to append
*/
static inline void sys_dlist_append(sys_dlist_t *list, sys_dnode_t *node)
{
sys_dnode_t *const tail = list->tail;
node->next = list;
node->prev = tail;
tail->next = node;
list->tail = node;
}
/**
* @brief add node to head of list
*
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
* @param node the element to append
*/
static inline void sys_dlist_prepend(sys_dlist_t *list, sys_dnode_t *node)
{
sys_dnode_t *const head = list->head;
node->next = head;
node->prev = list;
head->prev = node;
list->head = node;
}
/**
* @brief Insert a node into a list
*
* Insert a node before a specified node in a dlist.
*
* @param successor the position before which "node" will be inserted
* @param node the element to insert
*/
static inline void sys_dlist_insert(sys_dnode_t *successor, sys_dnode_t *node)
{
sys_dnode_t *const prev = successor->prev;
node->prev = prev;
node->next = successor;
prev->next = node;
successor->prev = node;
}
/**
* @brief insert node at position
*
* Insert a node in a location depending on a external condition. The cond()
* function checks if the node is to be inserted _before_ the current node
* against which it is checked.
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
* @param node the element to insert
* @param cond a function that determines if the current node is the correct
* insert point
* @param data parameter to cond()
*/
static inline void sys_dlist_insert_at(sys_dlist_t *list, sys_dnode_t *node,
int (*cond)(sys_dnode_t *node, void *data), void *data)
{
if (sys_dlist_is_empty(list)) {
sys_dlist_append(list, node);
} else {
sys_dnode_t *pos = sys_dlist_peek_head(list);
while ((pos != NULL) && (cond(pos, data) == 0)) {
pos = sys_dlist_peek_next(list, pos);
}
if (pos != NULL) {
sys_dlist_insert(pos, node);
} else {
sys_dlist_append(list, node);
}
}
}
/**
* @brief remove a specific node from a list
*
* The list is implicit from the node. The node must be part of a list.
* This and other sys_dlist_*() functions are not thread safe.
*
* @param node the node to remove
*/
static inline void sys_dlist_remove(sys_dnode_t *node)
{
sys_dnode_t *const prev = node->prev;
sys_dnode_t *const next = node->next;
prev->next = next;
next->prev = prev;
sys_dnode_init(node);
}
/**
* @brief get the first node in a list
*
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
*
* @return the first node in the list, NULL if list is empty
*/
static inline sys_dnode_t *sys_dlist_get(sys_dlist_t *list)
{
sys_dnode_t *node = NULL;
if (!sys_dlist_is_empty(list)) {
node = list->head;
sys_dlist_remove(node);
}
return node;
}
/**
* @brief Compute the size of the given list in O(n) time
*
* @param list A pointer on the list
*
* @return an integer equal to the size of the list, or 0 if empty
*/
static inline size_t sys_dlist_len(sys_dlist_t *list)
{
size_t len = 0;
sys_dnode_t *node = NULL;
SYS_DLIST_FOR_EACH_NODE(list, node) {
len++;
}
return len;
}
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* _DLIST_H_ */

View File

@@ -0,0 +1,3 @@
# SPDX-License-Identifier: Apache-2.0
listenai_library_sources(gcl_cb_list.c)

View File

@@ -0,0 +1,126 @@
#include "sysheap.h"
#include "gcl_cb_list.h"
#include "gcl_common.h"
#include "dlist.h"
#include <stddef.h>
struct gcl_cb {
sys_dlist_t cb_list;
gcl_mutex_t mutex;
};
typedef struct {
sys_dnode_t node;
gcl_event_cb_t cb;
uint32_t events;
void *arg;
} gcl_cb_list_item_t;
static inline gcl_cb_list_item_t *item_create(uint32_t events, gcl_event_cb_t cb, void *arg)
{
gcl_cb_list_item_t *new_item = psram_malloc(sizeof(gcl_cb_list_item_t));
if (!new_item) {
return NULL;
}
new_item->cb = cb;
new_item->arg = arg;
new_item->events = events;
return new_item;
}
static inline void item_delete(gcl_cb_list_item_t *item)
{
gcl_heap_free(item);
}
gcl_cb_list_t gcl_cb_list_create(void)
{
struct gcl_cb *gcl_cb = psram_malloc(sizeof(struct gcl_cb));
if (gcl_cb == NULL) {
return NULL;
}
sys_dlist_init(&gcl_cb->cb_list);
gcl_mutex_init(&gcl_cb->mutex);
return (gcl_cb_list_t)gcl_cb;
}
int gcl_cb_event_dispatch(gcl_cb_list_t gcl_cb_list, uint32_t event, void *event_data, uint32_t event_data_len)
{
int ret = -GCL_ERR_NOT_FOUND;
struct gcl_cb *gcl_cb = gcl_cb_list;
sys_dlist_t *list = &gcl_cb->cb_list;
sys_dnode_t *item, *tmp;
gcl_cb_list_item_t *cb_item;
gcl_mutex_lock(&gcl_cb->mutex);
SYS_DLIST_FOR_EACH_NODE_SAFE(list, item, tmp) {
cb_item = SYS_DLIST_CONTAINER(item, cb_item, node);
if (cb_item->events & event) {
cb_item->cb(cb_item->events & event, event_data, event_data_len, cb_item->arg);
ret = 0;
}
}
gcl_mutex_unlock(&gcl_cb->mutex);
return ret;
}
int gcl_cb_list_add_callback(gcl_cb_list_t gcl_cb_list, uint32_t events, gcl_event_cb_t callback, void *arg)
{
struct gcl_cb *gcl_cb = gcl_cb_list;
gcl_cb_list_item_t *new_cb = item_create(events, callback, arg);
if (!new_cb) {
return -GCL_ERR_NO_MEM;
}
gcl_mutex_lock(&gcl_cb->mutex);
sys_dlist_prepend(&gcl_cb->cb_list, &new_cb->node);
gcl_mutex_unlock(&gcl_cb->mutex);
return GCL_OK;
}
int gcl_cb_list_remove_callback(gcl_cb_list_t gcl_cb_list, gcl_event_cb_t callback)
{
int ret = -GCL_ERR_NOT_FOUND;
struct gcl_cb *gcl_cb = gcl_cb_list;
sys_dlist_t *list = &gcl_cb->cb_list;
sys_dnode_t *item, *tmp;
gcl_cb_list_item_t *cb_item;
gcl_mutex_lock(&gcl_cb->mutex);
SYS_DLIST_FOR_EACH_NODE_SAFE(list, item, tmp) {
cb_item = SYS_DLIST_CONTAINER(item, cb_item, node);
if (cb_item->cb == callback) {
sys_dlist_remove(&cb_item->node);
item_delete(cb_item);
ret = 0;
break;
}
}
gcl_mutex_unlock(&gcl_cb->mutex);
return ret;
}
void gcl_cb_list_clean_callbacks(gcl_cb_list_t gcl_cb_list)
{
struct gcl_cb *gcl_cb = gcl_cb_list;
sys_dlist_t *list = &gcl_cb->cb_list;
sys_dnode_t *item, *tmp;
gcl_cb_list_item_t *cb_item;
gcl_mutex_lock(&gcl_cb->mutex);
SYS_DLIST_FOR_EACH_NODE_SAFE(list, item, tmp) {
cb_item = SYS_DLIST_CONTAINER(item, cb_item, node);
sys_dlist_remove(&cb_item->node);
item_delete(cb_item);
}
gcl_mutex_unlock(&gcl_cb->mutex);
}
void gcl_cb_list_delete(gcl_cb_list_t gcl_cb_list)
{
struct gcl_cb *gcl_cb = gcl_cb_list;
gcl_cb_list_clean_callbacks(gcl_cb_list);
gcl_mutex_delete(&gcl_cb->mutex);
gcl_heap_free(gcl_cb_list);
}

View File

@@ -0,0 +1,27 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
typedef void* gcl_cb_list_t;
typedef void(*gcl_event_cb_t) (uint32_t event, void *event_data, uint32_t event_data_len, void *arg);
gcl_cb_list_t gcl_cb_list_create(void);
int gcl_cb_event_dispatch(gcl_cb_list_t gcl_cb_list, uint32_t event, void *event_data, uint32_t event_data_len);
int gcl_cb_list_add_callback(gcl_cb_list_t gcl_cb_list, uint32_t events, gcl_event_cb_t callback, void *arg);
int gcl_cb_list_remove_callback(gcl_cb_list_t gcl_cb_list, gcl_event_cb_t callback);
void gcl_cb_list_clean_callbacks(gcl_cb_list_t gcl_cb_list);
void gcl_cb_list_delete(gcl_cb_list_t gcl_cb_list);
static inline int gcl_cb_event_dispatch_nodata(gcl_cb_list_t gcl_cb_list, uint32_t event)
{
return gcl_cb_event_dispatch(gcl_cb_list, event, NULL, 0);
}

View File

@@ -0,0 +1,50 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Error codes */
#define GCL_OK 0
#define GCL_ERR_NOT_FOUND -1
#define GCL_ERR_NO_MEM -2
#define GCL_ERR_INVALID_ARG -3
#define GCL_ERR_TIMEOUT -4
/* Memory management functions */
#define gcl_heap_free(ptr) psram_free(ptr)
/* Simple mutex implementation using critical sections */
typedef struct {
volatile uint32_t locked;
} gcl_mutex_t;
static inline void gcl_mutex_init(gcl_mutex_t *mutex) {
mutex->locked = 0;
}
static inline void gcl_mutex_lock(gcl_mutex_t *mutex) {
// Simple spinlock implementation
while (__sync_lock_test_and_set(&mutex->locked, 1)) {
// Spin wait
}
}
static inline void gcl_mutex_unlock(gcl_mutex_t *mutex) {
__sync_lock_release(&mutex->locked);
}
static inline void gcl_mutex_delete(gcl_mutex_t *mutex) {
mutex->locked = 0;
}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,130 @@
/* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright(c) 2016 Intel Corporation. All rights reserved.
*
* Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
* Keyon Jie <yang.jie@linux.intel.com>
*/
#ifndef __SOF_LIST_H__
#define __SOF_LIST_H__
#include <stddef.h>
/* Really simple list manipulation */
#define container_of(ptr, type, member) \
({const __typeof__(((type *)0)->member)*__memberptr = (ptr); \
(type *)((char *)__memberptr - offsetof(type, member)); })
struct list_item;
struct list_item {
struct list_item *next;
struct list_item *prev;
};
/* initialise list before any use - list will point to itself */
static inline void list_init(struct list_item *list)
{
list->next = list;
list->prev = list;
}
/* add new item to the start or head of the list */
static inline void list_item_prepend(struct list_item *item,
struct list_item *list)
{
struct list_item *next = list->next;
next->prev = item;
item->next = next;
item->prev = list;
list->next = item;
}
/* add new item to the end or tail of the list */
static inline void list_item_append(struct list_item *item,
struct list_item *list)
{
struct list_item *tail = list->prev;
tail->next = item;
item->next = list;
item->prev = tail;
list->prev = item;
}
/* delete item from the list leaves deleted list item
*in undefined state list_is_empty will return true
*/
static inline void list_item_del(struct list_item *item)
{
item->next->prev = item->prev;
item->prev->next = item->next;
list_init(item);
}
/* is list item the last item in list ? */
static inline int list_item_is_last(struct list_item *item,
struct list_item *list)
{
return item->next == list;
}
/* is list empty ? */
#define list_is_empty(item) \
((item)->next == item)
#define __list_object(item, type, offset) \
((type *)((char *)(item) - (offset)))
/* get the container object of the list item */
#define list_item(item, type, member) \
__list_object(item, type, offsetof(type, member))
/* get the container object of the first item in the list */
#define list_first_item(list, type, member) \
__list_object((list)->next, type, offsetof(type, member))
/* get the next container object in the list */
#define list_next_item(object, member) \
list_item((object)->member.next, typeof(*(object)), member)
/* list iterator */
#define list_for_item(item, list) \
for (item = (list)->next; item != (list); item = item->next)
/* list iterator */
#define list_for_item_prev(item, list) \
for (item = (list)->prev; item != (list); item = item->prev)
/* list iterator - safe to delete items */
#define list_for_item_safe(item, tmp, list) \
for (item = (list)->next, tmp = item->next;\
item != (list); \
item = tmp, tmp = item->next)
/**
* Re-links the list when head address changed (list moved).
* @param new_list New address of the head.
* @param old_list Old address of the head.
*/
static inline void list_relink(struct list_item *new_list,
struct list_item *old_list)
{
struct list_item *li;
if (new_list->next == old_list) {
list_init(new_list);
} else {
list_for_item(li, new_list)
if (li->next == old_list)
li->next = new_list; /* for stops here */
list_for_item_prev(li, new_list)
if (li->prev == old_list)
li->prev = new_list; /* for stops here */
}
}
#endif /* __SOF_LIST_H__ */

View File

@@ -0,0 +1,11 @@
if (CONFIG_ACOMP_WAKEUP)
listenai_library_sources(
acomp_wakeup.c
)
listenai_include_directories(
./
)
endif()

View File

@@ -0,0 +1,27 @@
if ACOMP_WAKEUP
choice ACOMP_WAKEUP_ALGORITHM_TYPE
prompt "Wakeup type"
default ACOMP_WAKEUP_ALGORITHM_TYPE_SINGLE_MIC
config ACOMP_WAKEUP_ALGORITHM_TYPE_SINGLE_MIC
bool "Single mic wakeup algorithm"
config ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC
bool "dual mic wakeup algorithm"
endchoice
config ACOMP_WAKEUP_RES_CAE_ESR_MLP_ADDRESS
hex "cae esr mlp address"
default 0xcd00000
config ACOMP_WAKEUP_RES_CAE_ESR_MLP_LENGTH
int "cae esr mlp length"
default 419904
config ACOMP_WAKEUP_RES_AI_WRAP_ADDRESS
hex "ai wrap address"
default 0xcd00000
config ACOMP_WAKEUP_RES_AI_WRAP_LENGTH
int "ai wrap length"
default 419904
endif

View File

@@ -0,0 +1,953 @@
# 语音唤醒组件
## 简介
ACOMP Wakeup 是 ARCS SDK 的语音唤醒组件提供基于音频流的语音唤醒功能。该组件支持单麦克风和双麦克风唤醒算法支持WAKUP/ESR模式并提供灵活的唤醒门限配置和音频流管理接口。
## 主要特性
- **多麦克风支持**:支持单麦克风和双麦克风唤醒算法
- **双模式运行**:支持唤醒模式和 ESR 模式
- **灵活门限配置**:提供 6 级唤醒门限等级,从极易唤醒到禁用唤醒
- **事件回调机制**:支持唤醒结果、音频流更新、超时等多种事件通知
- **音频流管理**:提供完整的音频流通道管理和缓冲区操作接口
- **状态管理**:支持初始化、就绪、启动、停止等完整的生命周期管理
- **跨核通信**:基于 IPC 机制实现音频流的跨核传输
## 组件架构
ACOMP Wakeup 组件采用双核异构架构,CP 核(应用核)通过 ACOMP IPC 框架与 AP 核(算法核)进行通信,实现语音唤醒功能。
```
┌─────────────────────────────────────────────────────────────────────┐
│ CP 核 (Application Core) │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 用户应用层 │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ • 唤醒事件回调处理 (关键词/角度/超时) │ │ │
│ │ │ • 音频数据采集 (麦克风/回采) │ │ │
│ │ │ • 算法输出音频处理 (回声消除后的音频) │ │ │
│ │ └────────────────────┬───────────────────────────────────┘ │ │
│ └───────────────────────┼──────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────▼──────────────────────────────────────┐ │
│ │ ACOMP Wakeup 组件 (CP 侧) │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ 生命周期管理 │ │ 事件回调管理 │ │ 参数配置接口 │ │ │
│ │ │ init/prepare │ │ add_callback │ │ set_mode/ │ │ │
│ │ │ start/stop │ │ remove_cb │ │ threshold │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │
│ │ │ │ │ │ │
│ │ ┌──────▼─────────────────▼─────────────────▼───────┐ │ │
│ │ │ 音频流管理 (Stream API) │ │ │
│ │ │ • M2R 流: 音频输入 (mic+ref → 算法) │ │ │
│ │ │ • R2M 流: 算法输出 (回声消除后的音频 → 应用) │ │ │
│ │ │ • tx_alloc/submit, rx_get/release │ │ │
│ │ └──────────────────────┬───────────────────────────┘ │ │
│ └─────────────────────────┼───────────────────────────────────── │
│ │ │
│ ┌─────────────────────────▼─────────────────────────────────────┐ │
│ │ ACOMP IPC 层 (CP 侧) │ │
│ │ • IPC 消息封装 (NEW/FREE/CONTROL/NOTIFY) │ │
│ │ • 设备查询和管理 │ │
│ │ • 同步/异步命令处理 │ │
│ └─────────────────────────┬─────────────────────────────────────┘ │
└────────────────────────────┼────────────────────────────────────────┘
│ IC Message (IPC 消息通道)
┌────────────────────────────▼────────────────────────────────────────┐
│ AP 核 (Algorithm Core) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ACOMP Framework (AP 侧) │ │
│ │ │ │
│ │ • 接收 CP 核的 IPC 命令 (prepare/start/stop/control) │ │
│ │ • 处理音频流数据 (M2R 接收 / R2M 发送) │ │
│ │ • 执行唤醒算法引擎 │ │
│ │ • 推送唤醒事件到 CP 核 (关键词/角度/超时) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 唤醒算法引擎 (Wakeup Engine) │ │
│ │ │ │
│ │ • 音频预处理 (降噪/回声消除) │ │
│ │ • 特征提取和关键词识别 │ │
│ │ • 角度估计 (DOA) │ │
│ │ • 支持单麦/双麦、Wakeup/ESR 模式 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────┐
│ 共享内存 (Shared Mem) │
│ ┌────────────────────┐ │
│ │ VirtQueue 环形缓冲 │ │
│ │ (Stream IPC 数据) │ │
│ └────────────────────┘ │
└─────────────────────────┘
```
### 架构说明
#### 1. CP 核 (应用核) - 开放接口层
- **用户应用层**: 处理唤醒事件,采集音频数据,处理算法输出
- **ACOMP Wakeup 组件**: 提供生命周期管理、事件回调、参数配置等 API
- **音频流管理**: 管理 M2R(Master to Remote) 和 R2M(Remote to Master) 数据流
- **ACOMP IPC 层**: 封装 IPC 消息,与 AP 核通信
#### 2. AP 核 (算法核) - 算法实现层
- **ACOMP Framework**: 接收 IPC 命令,管理算法生命周期,处理音频流数据
- **唤醒算法引擎**: 执行语音唤醒算法,支持单麦/双麦、Wakeup/ESR 模式
> **注意**: AP 核的具体实现为闭源算法库,开发者只需关注 CP 核的 API 使用即可。
#### 3. 通信机制
- **控制通道**: 通过 IC Message 传递控制命令 (prepare/start/stop/control)
- **数据通道**: 通过 VirtQueue 共享内存实现零拷贝音频流传输
- **事件通知**: AP 核通过 NOTIFY 消息主动推送唤醒结果到 CP 核
#### 4. 数据流向
- **M2R 流**: CP 核采集的音频数据 (麦克风+回采) → AP 核算法引擎
- **R2M 流**: AP 核算法输出 (回声消除后的音频) → CP 核应用层
- **事件流**: AP 核唤醒结果/角度/超时事件 → CP 核回调函数
## 音频数据结构
### 输入音频数据结构
根据配置的算法类型,输入音频数据结构有所不同:
**双麦克风模式** (`CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC=y`):
```c
typedef struct {
short mic0; /* mic0的音频 */
short mic1; /* mic1的音频 */
short ref0; /* 回采的音频 */
short ref1; /* 回采的音频如只有一路回采则可复制ref0的数据 */
} acomp_wakeup_audio_in_t;
```
**单麦克风模式** (`CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_SINGLE_MIC=y`):
```c
typedef struct {
short mic0; /* mic0的音频 */
short ref0; /* 回采的音频 */
} acomp_wakeup_audio_in_t;
```
### 输出音频数据结构
算法处理后输出的音频数据为 5 通道格式:
```c
typedef struct {
short mic0; /* mic0的音频 */
short mic1; /* mic1的音频在单麦算法的情况下mic1的音频实际为mic0的音频 */
short ref0; /* 回采的音频 */
short out1; /* 回声消除之后的音频 */
short out2; /* 算法输出的其他音频 */
} acomp_wakeup_audio_out_t;
```
### 音频帧大小定义
组件提供了一组宏定义用于计算音频帧大小:
**输入音频帧信息**:
| 宏定义 | 说明 | 值 |
|--------|------|----|
| `ACOMP_WAKEUP_AUDIO_INPUT_LEN_ONE_SAMPLE` | 单个输入音频采样点的字节数 | `sizeof(acomp_wakeup_audio_in_t)` |
| `ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT` | 每帧输入音频的采样点数量 | 256 |
| `ACOMP_WAKEUP_AUDIO_INPUT_LEN_ONCE_FRAME` | 单帧输入音频数据的总字节数 | 单麦: 1024 字节<br>双麦: 2048 字节 |
| `ACOMP_WAKEUP_ESR_NEED_FRAME_CNT` | 一次完整识别需要输入的音频帧数量 | 10 |
**输出音频帧信息**:
| 宏定义 | 说明 | 值 |
|--------|------|----|
| `ACOMP_WAKEUP_AUDIO_OUTPUT_CHANNELS_PER_SAMPLE` | 输出音频每个采样点的通道数 | 5 |
| `ACOMP_WAKEUP_AUDIO_OUTPUT_LEN_ONE_SAMPLE` | 单个输出音频采样点的字节数 | 10 字节 |
| `ACOMP_WAKEUP_AUDIO_OUTPUT_MAX_LEN_ONCE_FRAME` | 单次输出音频数据的最大字节数 | 25600 字节 |
## 配置选项
### 基本配置
| 配置项 | 说明 | 默认值 |
|--------|------|--------|
| `CONFIG_ACOMP` | 启用ACOMP组件 | n |
| `CONFIG_ACOMP_WAKEUP` | 启用ACOMP组件的语音唤醒组件 | n |
### 算法类型配置
**注意**: 单麦和双麦算法互斥,只能选择其中一种。
| 配置项 | 说明 | 默认值 |
|--------|------|--------|
| `CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_SINGLE_MIC` | 单麦克风唤醒算法 | y |
| `CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC` | 双麦克风唤醒算法 | n |
### 资源配置
| 配置项 | 说明 | 默认值 |
|--------|------|--------|
| `CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_ADDRESS` | CAE ESR MLP 模型地址(存储在flash) | 0xcd00000 |
| `CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_LENGTH` | CAE ESR MLP 模型长度 | 419904 |
| `CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_ADDRESS` | AI Wrap 资源地址(存储在flash) | 0xcd00000 |
| `CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_LENGTH` | AI Wrap 资源长度 | 419904 |
## 快速开始
### 1. 启用组件
在项目的 `prj.conf` 文件中添加:
```kconfig
# 启用 ACOMP 组件
CONFIG_ACOMP=y
# 启用ACOMP组件的WAKEUP组件
CONFIG_ACOMP_WAKEUP=y
# 选择算法类型(单麦或双麦)
CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC=y
# 配置wakeup组件所需资源在flash的位置和大小(由用户来决定资源的放置位置和大小)
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_ADDRESS=0x30200000
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_LENGTH=2926656
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_ADDRESS=0x304D0000
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_LENGTH=1008
```
### 2. 初始化唤醒组件
在应用程序中初始化唤醒组件:
```c
#include "acomp.h"
#include "wakeup/acomp_wakeup.h"
int main(int argc, char **argv)
{
// 初始化 ACOMP 框架
acomp_init();
// 初始化唤醒组件
int ret = acomp_wakeup_init();
if (ret != ACOMP_ERR_OK) {
printf("Wakeup init failed: %d\n", ret);
return -1;
}
// 你的应用代码
return 0;
}
```
### 3. 注册事件回调
使用事件回调函数接收唤醒结果和状态更新:
```c
#include "wakeup/acomp_wakeup.h"
void wakeup_event_handler(uint32_t event, void *event_data,
uint32_t event_data_len, void *priv)
{
if (event & WAKEUP_CB_EVENT_ENGINE_RLT) {
// 唤醒结果
printf("Wakeup result: %s\n", (char *)event_data);
} else if (event & WAKEUP_CB_EVENT_STREAM_UPDATE) {
// 音频流更新
printf("Stream update\n");
} else if (event & WAKEUP_CB_EVENT_ENGINE_TIMEOUT) {
// 唤醒超时
printf("Wakeup timeout\n");
} else if (event & WAKEUP_CB_EVENT_ENGINE_ANGLE) {
// 角度信息
printf("Angle info received\n");
}
}
// 注册回调
int ret = acomp_wakeup_add_callback(
WAKEUP_CB_EVENT_ENGINE_RLT |
WAKEUP_CB_EVENT_STREAM_UPDATE |
WAKEUP_CB_EVENT_ENGINE_TIMEOUT |
WAKEUP_CB_EVENT_ENGINE_ANGLE,
wakeup_event_handler,
NULL
);
```
### 4. 启动唤醒服务
完整的启动流程:
```c
#include "wakeup/acomp_wakeup.h"
void start_wakeup_service(void)
{
int ret;
// 1. 就绪组件(分配资源)
ret = acomp_wakeup_prepare();
if (ret != ACOMP_ERR_OK) {
printf("Wakeup prepare failed: %d\n", ret);
return;
}
// 2. 启动唤醒
ret = acomp_wakeup_start();
if (ret != ACOMP_ERR_OK) {
printf("Wakeup start failed: %d\n", ret);
return;
}
printf("Wakeup service started\n");
}
void stop_wakeup_service(void)
{
int ret;
// 1. 停止唤醒
ret = acomp_wakeup_stop();
if (ret != ACOMP_ERR_OK) {
printf("Wakeup stop failed: %d\n", ret);
return;
}
// 2. 清理资源
ret = acomp_wakeup_cleanup();
if (ret != ACOMP_ERR_OK) {
printf("Wakeup cleanup failed: %d\n", ret);
return;
}
printf("Wakeup service stopped\n");
}
```
### 5. 配置唤醒参数
设置算法模式和唤醒门限(必须在启动后设置):
```c
#include "wakeup/acomp_wakeup.h"
void configure_wakeup(void)
{
int ret;
// 1. 就绪并启动唤醒组件
ret = acomp_wakeup_prepare();
ret = acomp_wakeup_start();
// 2. 启动后设置算法模式和门限(必须在 start 之后)
acomp_wakeup_set_algo_mode(ACOMP_WAKEUP_ALGO_MODE_WAKEUP);
acomp_wakeup_set_threshold(ACOMP_WAKEUP_THRESHOLD_LEVEL_3);
// 或者设置为 ESR 模式
// acomp_wakeup_set_algo_mode(ACOMP_WAKEUP_ALGO_MODE_ESR);
}
```
### 6. 把音频数据通过TX流通道发送给AP核算法引擎处理
使能 M2R (Master to Remote) 流通道,用于将音频数据发送给 AP 核:
```c
#include "wakeup/acomp_wakeup.h"
#define TX_STREAM_CH_INDEX 0
#define TX_STREAM_CH_NAME "stream.mix2ch"
void setup_tx_stream(void)
{
// 1. 创建 M2R 流通道描述符
acomp_stream_chn_create_desc_t desc = {
.cname = TX_STREAM_CH_NAME,
.direction = ACOMP_STREAM_DIRECTION_M2R, // Master to Remote
.index = TX_STREAM_CH_INDEX,
.buffer_size = ACOMP_WAKEUP_AUDIO_INPUT_LEN_ONCE_FRAME, // 单帧音频数据大小
.num_descs = 4, // 缓冲区描述符数量(根据实际需求设置)
.kick_policy = 1, // 自动触发
};
// 2. 使能流通道
int ret = acomp_wakeup_stream_ch_enable(TX_STREAM_CH_INDEX, &desc);
if (ret != ACOMP_ERR_OK) {
printf("Failed to enable tx stream: %d\n", ret);
}
}
void send_data_to_remote(void)
{
uint8_t *buffer;
uint32_t buf_size;
uint16_t desc_idx;
// 1. 分配发送缓冲区
buffer = acomp_wakeup_stream_tx_buffer_alloc(TX_STREAM_CH_INDEX,
&buf_size, &desc_idx);
if (buffer && buf_size > 0) {
// 2. 填充音频数据到缓冲区
// 数据格式为 acomp_wakeup_audio_in_t 数组,包含 256 个采样点
// 双麦: {mic0, mic1, ref0, ref1} * 256
// 单麦: {mic0, ref0} * 256
acomp_wakeup_audio_in_t *audio_frame = (acomp_wakeup_audio_in_t *)buffer;
// 填充从麦克风采集的音频数据
// fill_audio_data(audio_frame, ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT);
// 3. 提交缓冲区发送
acomp_wakeup_stream_tx_buffer_submit(TX_STREAM_CH_INDEX,
buffer, buf_size, desc_idx);
}
}
```
### 7. 使能 RX 流通道并接收数据
使能 R2M (Remote to Master) 流通道,用于接收 AP 核的输出数据:
```c
#include "wakeup/acomp_wakeup.h"
#define RX_STREAM_CH_INDEX 1
#define RX_STREAM_CH_NAME "stream.tocloud"
void setup_rx_stream(void)
{
// 1. 创建 R2M 流通道描述符
acomp_stream_chn_create_desc_t desc = {
.cname = RX_STREAM_CH_NAME,
.direction = ACOMP_STREAM_DIRECTION_R2M, // Remote to Master
.index = RX_STREAM_CH_INDEX,
.buffer_size = ACOMP_WAKEUP_AUDIO_OUTPUT_MAX_LEN_ONCE_FRAME, // 最大输出帧大小
.num_descs = 8, // 缓冲区描述符数量
.kick_policy = 1, // 自动触发
};
// 2. 使能流通道
int ret = acomp_wakeup_stream_ch_enable(RX_STREAM_CH_INDEX, &desc);
if (ret != ACOMP_ERR_OK) {
printf("Failed to enable rx stream: %d\n", ret);
}
}
void receive_data_from_remote(void)
{
uint8_t *buffer;
uint32_t len;
uint16_t desc_idx;
// 1. 获取算法输出的音频数据
buffer = acomp_wakeup_stream_rx_buffer_get(RX_STREAM_CH_INDEX, &len, &desc_idx);
if (buffer && len > 0) {
// 2. 处理接收到的数据
// 数据格式为 acomp_wakeup_audio_out_t 数组5通道交织
// {mic0, mic1, ref0, out1, out2} * N 个采样点
// 其中 out1 为回声消除后的音频,可用于云端识别
acomp_wakeup_audio_out_t *audio_out = (acomp_wakeup_audio_out_t *)buffer;
int sample_count = len / sizeof(acomp_wakeup_audio_out_t);
// process_output_audio(audio_out, sample_count);
// 3. 释放缓冲区
acomp_wakeup_stream_rx_buffer_release(RX_STREAM_CH_INDEX,
desc_idx, len, buffer);
}
}
```
## API 参考
### 生命周期管理
#### acomp_wakeup_init
```c
int acomp_wakeup_init(void);
```
**功能**:初始化语音唤醒组件
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_NO_MEM`:内存不足
- `ACOMP_ERR_INVALID_STATE`:无效状态
- `ACOMP_ERR_NOT_FOUND`:设备未找到
#### acomp_wakeup_prepare
```c
int acomp_wakeup_prepare(void);
```
**功能**:就绪语音唤醒组件,初始化内存块及算法资源
**说明**:在调用 `acomp_wakeup_start()` 之前必须调用此函数
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_NO_MEM`:内存不足
- `ACOMP_ERR_INVALID_STATE`:无效状态
#### acomp_wakeup_cleanup
```c
int acomp_wakeup_cleanup(void);
```
**功能**:复位语音唤醒组件,释放内存块及算法资源
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_INVALID_STATE`:无效状态
#### acomp_wakeup_start
```c
int acomp_wakeup_start(void);
```
**功能**:启动语音唤醒组件,同步启动音频流传输
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_NO_MEM`:内存不足
- `ACOMP_ERR_INVALID_STATE`:无效状态
#### acomp_wakeup_stop
```c
int acomp_wakeup_stop(void);
```
**功能**:停止语音唤醒组件,同步停止音频流传输
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_INVALID_STATE`:无效状态
### 参数配置
#### acomp_wakeup_set_algo_mode
```c
int acomp_wakeup_set_algo_mode(acomp_wakeup_algo_mode_e mode);
```
**功能**:设置算法模式
**说明**:该函数必须在调用 `acomp_wakeup_start()` 之后设置才能生效
**参数**
- `mode`:算法模式
- `ACOMP_WAKEUP_ALGO_MODE_WAKEUP`:唤醒模式
- `ACOMP_WAKEUP_ALGO_MODE_ESR`ESR 模式
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_INVALID_ARG`:参数错误
- `ACOMP_ERR_INVALID_STATE`:无效状态
#### acomp_wakeup_set_threshold
```c
int acomp_wakeup_set_threshold(acomp_wakeup_threshold_level_e level);
```
**功能**:设置唤醒门限等级
**说明**:该函数必须在调用 `acomp_wakeup_start()` 之后设置才能生效
**参数**
- `level`:门限等级 (1-6)
- `ACOMP_WAKEUP_THRESHOLD_LEVEL_1`:最低,极易唤醒
- `ACOMP_WAKEUP_THRESHOLD_LEVEL_2`:易唤醒
- `ACOMP_WAKEUP_THRESHOLD_LEVEL_3`:默认档位
- `ACOMP_WAKEUP_THRESHOLD_LEVEL_4`:难唤醒
- `ACOMP_WAKEUP_THRESHOLD_LEVEL_5`:极难唤醒
- `ACOMP_WAKEUP_THRESHOLD_LEVEL_6`:最高,禁用唤醒
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_INVALID_ARG`:参数错误
- `ACOMP_ERR_INVALID_STATE`:无效状态
### 事件回调
#### acomp_wakeup_add_callback
```c
int acomp_wakeup_add_callback(uint32_t events, wakeup_event_cb_t cb, void *priv);
```
**功能**:添加事件回调函数
**参数**
- `events`:事件位掩码,可同时注册多个事件
- `WAKEUP_CB_EVENT_STREAM_UPDATE`:音频数据流更新
- `WAKEUP_CB_EVENT_ENGINE_RLT`:语音唤醒引擎结果返回
- `WAKEUP_CB_EVENT_ENGINE_TIMEOUT`:唤醒超时
- `WAKEUP_CB_EVENT_ENGINE_ANGLE`:角度信息
- `cb`:回调函数指针
- `priv`:回调函数的私有数据指针
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_NO_MEM`:内存不足
- `ACOMP_ERR_INVALID_ARG`:参数错误
- `ACOMP_ERR_INVALID_STATE`:无效状态
#### acomp_wakeup_remove_callback
```c
int acomp_wakeup_remove_callback(wakeup_event_cb_t cb);
```
**功能**:移除回调函数
**参数**
- `cb`:待移除的回调函数
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_INVALID_ARG`:参数错误
- `ACOMP_ERR_INVALID_STATE`:无效状态
- `ACOMP_ERR_NOT_SUPPORTED`:不支持的操作
### 音频流管理
#### acomp_wakeup_stream_ch_enable
```c
int acomp_wakeup_stream_ch_enable(int chn, acomp_stream_chn_create_desc_t *desc);
```
**功能**:使能音频流通道
**参数**
- `chn`:通道索引
- `desc`:通道描述符指针
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_INVALID_STATE`:无效状态
- `ACOMP_ERR_INVALID_ARG`:参数错误
- `ACOMP_ERR_CREATE_STREAM_FAILED`:创建流失败
#### acomp_wakeup_stream_ch_disable
```c
int acomp_wakeup_stream_ch_disable(int chn);
```
**功能**:禁用音频流通道
**参数**
- `chn`:通道索引
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_INVALID_STATE`:无效状态
- `ACOMP_ERR_INVALID_ARG`:参数错误
#### acomp_wakeup_stream_rx_buffer_get
```c
void* acomp_wakeup_stream_rx_buffer_get(int chn, uint32_t* len, uint16_t* desc_idx);
```
**功能**:获取 RX 流缓冲区
**参数**
- `chn`:通道索引
- `len`:数据长度指针(输出)
- `desc_idx`:描述符索引指针(输出)
**返回值**:缓冲区指针,失败返回 NULL
#### acomp_wakeup_stream_rx_buffer_release
```c
int acomp_wakeup_stream_rx_buffer_release(int chn, uint16_t desc_idx,
uint32_t len, void* buffer);
```
**功能**:释放 RX 流缓冲区
**参数**
- `chn`:通道索引
- `desc_idx`:描述符索引
- `len`:数据长度
- `buffer`:缓冲区指针
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_INVALID_STATE`:无效状态
- `ACOMP_ERR_INVALID_ARG`:参数错误
#### acomp_wakeup_stream_tx_buffer_alloc
```c
void* acomp_wakeup_stream_tx_buffer_alloc(int chn, uint32_t* len, uint16_t* desc_idx);
```
**功能**:分配 TX 流缓冲区用于向 remote 发送音频数据
**参数**
- `chn`:通道索引
- `len`:可用缓冲区长度指针(输出)
- `desc_idx`:描述符索引指针(输出)
**返回值**:缓冲区指针,失败返回 NULL
#### acomp_wakeup_stream_tx_buffer_submit
```c
int acomp_wakeup_stream_tx_buffer_submit(int chn, void* buffer,
uint32_t len, uint16_t desc_idx);
```
**功能**:提交 TX 流缓冲区发送音频数据到 remote
**参数**
- `chn`:通道索引
- `buffer`:缓冲区指针
- `len`:数据长度
- `desc_idx`:描述符索引
**返回值**
- `ACOMP_ERR_OK`:成功
- `ACOMP_ERR_INVALID_STATE`:无效状态
- `ACOMP_ERR_INVALID_ARG`:参数错误
## 使用示例
### 完整示例
```c
#include "acomp.h"
#include "wakeup/acomp_wakeup.h"
#include "lisa_log.h"
#define TAG "wakeup_demo"
// 事件回调函数
void wakeup_event_handler(uint32_t event, void *event_data,
uint32_t event_data_len, void *priv)
{
if (event & WAKEUP_CB_EVENT_ENGINE_RLT) {
LISA_LOGI(TAG, "Wakeup result: %d, %s",
event_data_len, (char *)event_data);
} else if (event & WAKEUP_CB_EVENT_STREAM_UPDATE) {
LISA_LOGI(TAG, "Stream update data: %d, len: %d",
*(uint32_t*)event_data, event_data_len);
} else if (event & WAKEUP_CB_EVENT_ENGINE_TIMEOUT) {
LISA_LOGI(TAG, "Wakeup timeout");
} else {
LISA_LOGW(TAG, "Unknown wakeup event: 0x%X", event);
}
}
void wakeup_demo(void)
{
int ret;
// 1. 初始化 ACOMP 框架
acomp_init();
// 2. 初始化唤醒组件
ret = acomp_wakeup_init();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Wakeup init failed: %d", ret);
return;
}
// 3. 注册事件回调
ret = acomp_wakeup_add_callback(
WAKEUP_CB_EVENT_ENGINE_RLT |
WAKEUP_CB_EVENT_STREAM_UPDATE |
WAKEUP_CB_EVENT_ENGINE_TIMEOUT,
wakeup_event_handler,
NULL
);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Add callback failed: %d", ret);
return;
}
// 4. 就绪组件
ret = acomp_wakeup_prepare();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Wakeup prepare failed: %d", ret);
return;
}
LISA_LOGI(TAG, "Wakeup prepared");
// 5. 启动唤醒
ret = acomp_wakeup_start();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Wakeup start failed: %d", ret);
return;
}
LISA_LOGI(TAG, "Wakeup started");
// 6. 配置唤醒参数(必须在 start 之后)
acomp_wakeup_set_algo_mode(ACOMP_WAKEUP_ALGO_MODE_WAKEUP);
acomp_wakeup_set_threshold(ACOMP_WAKEUP_THRESHOLD_LEVEL_3);
// 7. 运行一段时间
vTaskDelay(pdMS_TO_TICKS(10000));
// 8. 停止唤醒
ret = acomp_wakeup_stop();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Wakeup stop failed: %d", ret);
}
LISA_LOGI(TAG, "Wakeup stopped");
// 9. 清理资源
ret = acomp_wakeup_cleanup();
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "Wakeup cleanup failed: %d", ret);
}
LISA_LOGI(TAG, "Wakeup cleaned up");
}
```
### 音频流处理示例
#### 音频数据输入给算法引擎
```c
#include "wakeup/acomp_wakeup.h"
void audio_stream_tx_example(void)
{
int chn = 0; // 通道 0
uint32_t len;
uint16_t desc_idx;
void *buffer;
while (1) {
// 发送音频数据
buffer = acomp_wakeup_stream_tx_buffer_alloc(chn, &len, &desc_idx);
if (buffer) {
// 填充音频数据
fill_audio_data(buffer, len);
// 提交发送
acomp_wakeup_stream_tx_buffer_submit(chn, buffer, len, desc_idx);
}
}
}
```
#### 获取算法引擎输出的音频数据,并处理
```c
#include "wakeup/acomp_wakeup.h"
void audio_stream_rx_example(void)
{
int chn = 0; // 通道 0
uint32_t len;
uint16_t desc_idx;
void *buffer;
while (1) {
// 接收音频数据
buffer = acomp_wakeup_stream_rx_buffer_get(chn, &len, &desc_idx);
if (buffer) {
// 处理算法输出的音频数据
process_audio_data(buffer, len);
// 释放缓冲区
acomp_wakeup_stream_rx_buffer_release(chn, desc_idx, len, buffer);
}
}
}
```
## 注意事项
1. **初始化顺序**:必须先调用 `acomp_init()` 初始化 ACOMP 框架,再调用 `acomp_wakeup_init()`
2. **生命周期管理**:启动前必须调用 `acomp_wakeup_prepare()`,停止后应调用 `acomp_wakeup_cleanup()` 释放资源
3. **状态检查**:所有 API 都会进行状态检查,确保在正确的状态下调用相应的函数
4. **事件回调**:回调函数在组件内部线程中执行,应避免长时间阻塞操作
5. **内存管理**:组件使用动态内存分配,确保系统有足够的堆内存
6. **资源配置**:确保配置的模型地址和长度与实际烧录的模型匹配
7. **算法选择**:单麦和双麦算法互斥,只能选择其中一种
8. **门限调整**:根据实际使用场景调整唤醒门限,平衡误唤醒率和唤醒成功率
9. **音频流管理**:使用音频流接口时,必须成对调用 get/release 和 alloc/submit
10. **跨核通信**:音频流基于 IPC 机制,注意跨核数据传输的延迟和同步问题
## 常见问题
**Q: 唤醒组件初始化失败怎么办?**
A: 检查以下几点:
- 确保 ACOMP 框架已正确初始化
- 检查系统是否有足够的堆内存
- 确认音频硬件设备是否正常工作
- 检查配置的模型资源地址和长度是否正确
**Q: 无法触发唤醒回调?**
A: 检查:
- 确认已正确注册事件回调函数
- 检查唤醒门限设置是否过高(尝试降低门限等级)
- 确认麦克风是否正常采集音频数据
- 检查算法模式设置是否正确
**Q: 唤醒成功率低怎么办?**
A: 可以尝试:
- 降低唤醒门限等级(如从 3 降到 2
- 检查麦克风音频质量和增益设置
- 确认使用的算法类型(单麦/双麦)与硬件配置匹配
- 检查环境噪声是否过大
**Q: 误唤醒率高怎么办?**
A: 可以尝试:
- 提高唤醒门限等级(如从 3 升到 4
- 检查是否有持续的背景噪声干扰
- 确认麦克风位置和方向是否合理
**Q: 如何在唤醒模式和 ESR 模式之间切换?**
A: 使用 `acomp_wakeup_set_algo_mode()` 函数:
```c
// 切换到唤醒模式
acomp_wakeup_set_algo_mode(ACOMP_WAKEUP_ALGO_MODE_WAKEUP);
// 切换到 ESR 模式
acomp_wakeup_set_algo_mode(ACOMP_WAKEUP_ALGO_MODE_ESR);
```
注意:模式切换应在停止状态下进行。
## 依赖项
- `ACOMP 框架`:音频组件框架
- `ACOMP Stream IPC`:音频流跨核通信
- `lisa_log`:日志系统
- `FreeRTOS`:实时操作系统
- `唤醒算法库`:语音唤醒算法引擎
- `音频驱动`:麦克风和音频处理硬件驱动
## 相关文档
- ACOMP 框架文档
- 音频流 IPC 接口文档
- 语音唤醒算法说明文档

View File

@@ -0,0 +1,520 @@
#include <string.h>
#include "ipc/acomp_ipc.h"
#include "acomp_wakeup.h"
#include "acomp_err.h"
#include "gcl_cb_list/gcl_cb_list.h"
#include "private/wakeup_ipc.h"
#include "comm/stream/acomp_stream_ipc.h"
#define TAG "acomp_wakeup"
#include "lisa_log.h"
#define ACOMP_WAKEUP_DEV_NAME "acomp.wakeup"
typedef struct {
uint32_t dev_index;
gcl_cb_list_t event_callbacks;
acomp_stream_t *stream;
} acomp_wakeup_handle_t;
acomp_wakeup_handle_t *wakeup_handle = NULL;
static int acomp_wakeup_control_subcmd(wakeup_ipc_control_subcmd_e subcmd, void *data, uint32_t data_len);
void event_callback(acomp_ipc_message_t *message, void *priv)
{
acomp_wakeup_handle_t *handle = (acomp_wakeup_handle_t *)priv;
if (handle == NULL) {
return;
}
if (message->hdr.hdr.cmd == ACOMP_CONTEXT_IPC_GLB_NOTIFY) {
if (message->acomp_cmd == ACOMP_IPC_CMD_NOTIFY_RESULT) {
if (((void *)message->address != NULL) && (message->len > 0)) {
acomp_ipc_notify_result_t *result = (acomp_ipc_notify_result_t *)message->address;
gcl_cb_event_dispatch(handle->event_callbacks, WAKEUP_CB_EVENT_ENGINE_RLT, result->data, result->len);
}
} else if (message->acomp_cmd == ACOMP_IPC_CMD_NOTIFY_SUBCMD) {
if (((void *)message->address != NULL) && (message->len > 0)) {
acomp_ipc_notify_subcmd_t *subcmd = (acomp_ipc_notify_subcmd_t *)message->address;
wakeup_ipc_notify_subcmd_hdr_t *hdr = (wakeup_ipc_notify_subcmd_hdr_t *)subcmd->data;
if (hdr->cmd == WAKEUP_IPC_NOTIFY_SUBCMD_PCM_DATA) {
wakeup_ipc_notify_subcmd_pcm_data_t *pcm_data = (wakeup_ipc_notify_subcmd_pcm_data_t *)subcmd->data;
gcl_cb_event_dispatch(handle->event_callbacks, WAKEUP_CB_EVENT_STREAM_UPDATE, pcm_data->data,
pcm_data->len);
} else if (hdr->cmd == WAKEUP_IPC_NOTIFY_SUBCMD_WAKEUP_TIMEOUT) {
gcl_cb_event_dispatch(handle->event_callbacks, WAKEUP_CB_EVENT_ENGINE_TIMEOUT, NULL,
0);
} else if (hdr->cmd == WAKEUP_IPC_NOTIFY_SUBCMD_ANGLE) {
wakeup_ipc_notify_subcmd_angle_t *angle = (wakeup_ipc_notify_subcmd_angle_t *)subcmd->data;
gcl_cb_event_dispatch(handle->event_callbacks, WAKEUP_CB_EVENT_ENGINE_ANGLE, angle->angle_data,
angle->angle_cnt * sizeof(short));
}else if(hdr->cmd == WAKEUP_IPC_NOTIFY_SUBCMD_MODE_SWITCH)
{
wakeup_ipc_notify_subcmd_switch_mode_t *mode_switch = (wakeup_ipc_notify_subcmd_switch_mode_t *)subcmd->data;
acomp_wakeup_algo_mode_e mode;
if(mode_switch->mode == 0) {
mode = ACOMP_WAKEUP_ALGO_MODE_WAKEUP;
}
else {
mode = ACOMP_WAKEUP_ALGO_MODE_ESR;
}
gcl_cb_event_dispatch(handle->event_callbacks, WAKEUP_CB_EVENT_ENGINE_SWITCH_MODE, &mode,
sizeof(acomp_wakeup_algo_mode_e));
}
else{
LISA_LOGW(TAG,"unknow notify hdr cmd:%d",hdr->cmd);
}
}
}
else if(message->acomp_cmd == ACOMP_IPC_CMD_NOTIFY_STREAM_UPDATE){
if (((void *)message->address != NULL) && (message->len > 0)) {
acomp_ipc_stream_update_t *ipc_msg;
uint32_t chn;
ipc_msg = (acomp_ipc_stream_update_t *)message->address;
chn = ipc_msg->index;
if(chn < sizeof(handle->stream->ch)/sizeof(handle->stream->ch[0])){
gcl_cb_event_dispatch(handle->event_callbacks,
WAKEUP_CB_EVENT_STREAM_UPDATE,
handle->stream->ch[chn],
sizeof(acomp_stream_channel_t));
}
}
}
}
}
#if 0
static int acomp_wakeup_default_params_set(void)
{
LISA_LOGI(TAG, "acomp wakeup default params set enter");
wakeup_ipc_control_subcmd_parameter_set_t params;
memset(&params, 0, sizeof(wakeup_ipc_control_subcmd_parameter_set_t));
params.mic_channel_num = CONFIG_ACOMP_WAKEUP_MIC_CHANNEL_NUM;
params.mic_channel_index = CONFIG_ACOMP_WAKEUP_MIC_CHANNEL_INDEX;
params.ref_type = CONFIG_ACOMP_WAKEUP_REF_TYPE;
params.ref_channel_index = CONFIG_ACOMP_WAKEUP_REF_CHANNEL_INDEX;
params.mic_l_gain_a_val = 0;
params.mic_l_gain_d_val = 0;
params.mic_r_gain_a_val = 0;
params.mic_r_gain_d_val = 0;
params.ref_gain_d_val = 0;
params.ref_gain_a_val = 0;
params.debug_enable = 0;
int ret = acomp_wakeup_control_subcmd(WAKEUP_IPC_CONTROL_SUBCMD_PARAMETER_SET, &params, sizeof(acomp_wakeup_params_t));
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp wakeup default params set failed!");
return ret;
}
LISA_LOGI(TAG, "acomp wakeup default params set exit");
return ret;
}
#endif
int acomp_wakeup_init(void)
{
int ret = 0;
LISA_LOGI(TAG, "acomp wakeup init enter");
if (wakeup_handle != NULL) {
return ACOMP_ERR_INVALID_STATE;
}
wakeup_handle = (acomp_wakeup_handle_t *)psram_malloc(sizeof(acomp_wakeup_handle_t));
if (wakeup_handle == NULL) {
return ACOMP_ERR_NO_MEM;
}
memset(wakeup_handle, 0, sizeof(acomp_wakeup_handle_t));
wakeup_handle->event_callbacks = gcl_cb_list_create();
wakeup_handle->dev_index = acomp_ipc_get_dev_index(ACOMP_WAKEUP_DEV_NAME);
if (wakeup_handle->dev_index < 0) {
psram_free(wakeup_handle);
wakeup_handle = NULL;
LISA_LOGE(TAG, "acomp wakeup dev index not found!");
return ACOMP_ERR_NOT_FOUND;
}
LISA_LOGI(TAG, "acomp wakeup dev index %d,name:%s", wakeup_handle->dev_index, ACOMP_WAKEUP_DEV_NAME);
ret = acomp_ipc_add_callback(wakeup_handle->dev_index, (ipc_event_cb_t)event_callback, wakeup_handle);
if (ret != ACOMP_ERR_OK) {
return ret;
}
ret = acomp_ipc_build_frame_send_sync(wakeup_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_NEW | IPC_HEADER_REQ_REPALY, 0,
0, NULL, 0);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp wakeup init failed!");
return ret;
}
wakeup_handle->stream = acomp_stream_create(wakeup_handle->dev_index);
if(wakeup_handle->stream == NULL){
return ACOMP_ERR_CREATE_STREAM_FAILED;
}
// ret = acomp_wakeup_default_params_set();
LISA_LOGI(TAG, "acomp wakeup init exit");
return 0;
}
int acomp_wakeup_deinit(void)
{
/*TODO*/
return ACOMP_ERR_NOT_SUPPORTED;
}
int acomp_wakeup_prepare(void)
{
LISA_LOGI(TAG, "acomp wakeup prepare enter");
acomp_ipc_prepare_t *prepare;
uint32_t size;
int ret;
size = sizeof(acomp_ipc_prepare_t) + sizeof(acomp_res_item_t) * ACOMP_WAKEUP_RES_NUMBER;
size = ALIGN_SIZE(size);
prepare = psram_malloc_align(IPC_ALIGN_SIZE, size);
if (prepare == NULL) {
return ACOMP_ERR_NO_MEM;
}
prepare->number = ACOMP_WAKEUP_RES_NUMBER;
prepare->item[0].index = WAKEUP_INDEX_CAE_ESR_MLP;
prepare->item[0].addr = CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_ADDRESS;
prepare->item[0].offset = 0;
prepare->item[0].size = CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_LENGTH;
prepare->item[1].index = WAKEUP_INDEX_AI_WRAP;
prepare->item[1].addr = CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_ADDRESS;
prepare->item[1].offset = 0;
prepare->item[1].size = CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_LENGTH;
ret = acomp_ipc_build_frame_send_sync(wakeup_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_PREPARE, 0, prepare, size);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp wakeup prepare failed!");
}
psram_free(prepare);
LISA_LOGI(TAG, "acomp wakeup prepare exit");
return ret;
}
int acomp_wakeup_prepare_with_config(acomp_ipc_prepare_t *prepare)
{
LISA_LOGI(TAG, "acomp wakeup prepare enter");
if (prepare == NULL) {
return -1;
}
uint32_t size;
int ret;
size = sizeof(acomp_ipc_prepare_t) + sizeof(acomp_res_item_t) * ACOMP_WAKEUP_RES_NUMBER;
size = ALIGN_SIZE(size);
prepare->number = ACOMP_WAKEUP_RES_NUMBER;
prepare->item[0].index = WAKEUP_INDEX_CAE_ESR_MLP;
prepare->item[1].index = WAKEUP_INDEX_AI_WRAP;
ret = acomp_ipc_build_frame_send_sync(wakeup_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_PREPARE, 0, prepare, size);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp wakeup prepare failed!");
}
LISA_LOGI(TAG, "acomp wakeup prepare exit");
return ret;
}
int acomp_wakeup_cleanup(void)
{
int ret;
ret = acomp_ipc_build_frame_send_sync(wakeup_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_CLEANUP, 0, NULL, 0);
return ret;
}
int acomp_wakeup_start(void)
{
LISA_LOGI(TAG, "acomp wakeup start enter");
int ret;
ret = acomp_ipc_build_frame_send_sync(wakeup_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_START, 0, NULL, 0);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp wakeup start failed!");
}
LISA_LOGI(TAG, "acomp wakeup start exit");
return ret;
}
int acomp_wakeup_stop(void)
{
LISA_LOGI(TAG, "acomp wakeup stop enter");
int ret;
ret = acomp_ipc_build_frame_send_sync(wakeup_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_STOP, 0, NULL, 0);
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp wakeup stop failed!");
}
LISA_LOGI(TAG, "acomp wakeup stop exit");
return ret;
}
static int acomp_wakeup_control_subcmd(wakeup_ipc_control_subcmd_e subcmd, void *data, uint32_t data_len)
{
int ret;
uint32_t size;
acomp_ipc_control_t *ipc_control;
size = sizeof(acomp_ipc_control_t) + data_len;
size = ALIGN_SIZE(size);
ipc_control = psram_malloc_align(IPC_ALIGN_SIZE, size);
if (ipc_control == NULL) {
return ACOMP_ERR_NO_MEM;
}
ipc_control->control = subcmd;
ipc_control->len = data_len;
uint8_t *ipc_data = ipc_control->data;
if (data_len > 0) {
memcpy(ipc_data, data, data_len);
}
ret = acomp_ipc_build_frame_send_sync(wakeup_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_CONTROL, 0, ipc_control, size);
psram_free(ipc_control);
return ret;
}
// int acomp_wakeup_params_set(acomp_wakeup_params_t *params)
// {
// LISA_LOGI(TAG, "acomp wakeup params set enter");
// LISA_LOGI(TAG, "acomp wakeup params set exit");
// return 0;
// }
int acomp_wakeup_set_debug_mode(uint8_t enable)
{
LISA_LOGI(TAG, "acomp wakeup set debug mode enter");
wakeup_ipc_control_subcmd_debug_mode_set_t debug_mode_set;
debug_mode_set.enable = enable;
int ret = acomp_wakeup_control_subcmd(WAKEUP_IPC_CONTROL_SUDCMD_DEBUG_MODE_SET, &debug_mode_set, sizeof(wakeup_ipc_control_subcmd_debug_mode_set_t));
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp wakeup set debug mode failed!");
}
LISA_LOGI(TAG, "acomp wakeup set debug mode exit");
return ret;
}
int acomp_wakeup_set_algo_mode(acomp_wakeup_algo_mode_e mode)
{
LISA_LOGI(TAG, "acomp wakeup set algo mode enter, mode=%d", mode);
if (mode != ACOMP_WAKEUP_ALGO_MODE_WAKEUP && mode != ACOMP_WAKEUP_ALGO_MODE_ESR) {
LISA_LOGE(TAG, "invalid algo mode: %d", mode);
return ACOMP_ERR_INVALID_ARG;
}
wakeup_ipc_control_subcmd_algo_mode_set_t algo_mode_set;
algo_mode_set.mode = (uint8_t)mode;
int ret = acomp_wakeup_control_subcmd(WAKEUP_IPC_CONTROL_SUBCMD_ALGO_MODE_SET, &algo_mode_set, sizeof(wakeup_ipc_control_subcmd_algo_mode_set_t));
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp wakeup set algo mode failed!");
}
LISA_LOGI(TAG, "acomp wakeup set algo mode exit");
return ret;
}
int acomp_wakeup_set_threshold(acomp_wakeup_threshold_level_e level)
{
LISA_LOGI(TAG, "acomp wakeup set threshold enter, level=%d", level);
if (level < ACOMP_WAKEUP_THRESHOLD_LEVEL_1 || level > ACOMP_WAKEUP_THRESHOLD_LEVEL_6) {
LISA_LOGE(TAG, "invalid threshold level: %d", level);
return ACOMP_ERR_INVALID_ARG;
}
wakeup_ipc_control_subcmd_threshold_set_t threshold_set;
threshold_set.level = (uint8_t)level;
int ret = acomp_wakeup_control_subcmd(WAKEUP_IPC_CONTROL_SUBCMD_THRESHOLD_SET, &threshold_set, sizeof(wakeup_ipc_control_subcmd_threshold_set_t));
if (ret != ACOMP_ERR_OK) {
LISA_LOGE(TAG, "acomp wakeup set threshold failed!");
}
LISA_LOGI(TAG, "acomp wakeup set threshold exit");
return ret;
}
int acomp_wakeup_add_callback(uint32_t events, wakeup_event_cb_t cb, void *priv)
{
int ret;
if (wakeup_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
ret = gcl_cb_list_add_callback(wakeup_handle->event_callbacks, events, cb, priv);
return ret;
}
int acomp_wakeup_remove_callback(wakeup_event_cb_t cb)
{
int ret;
if (wakeup_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
ret = gcl_cb_list_remove(wakeup_handle->event_callbacks, cb);
return ret;
}
int acomp_wakeup_stream_ch_enable(int chn,acomp_stream_chn_create_desc_t *desc){
int ret = 0;
if ((wakeup_handle == NULL) || (wakeup_handle->stream == NULL)) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
wakeup_handle->stream->ch[chn] = acomp_stream_ipc_channel_create(wakeup_handle->stream,chn,wakeup_handle->dev_index,desc);
if(wakeup_handle->stream->ch[chn] == NULL){
return ACOMP_ERR_CREATE_STREAM_FAILED;
}
LISA_LOGI(TAG,"acomp_wakeup_stream_ch_enable chn(%s) index(%d),desc(%p)",desc->cname,chn,desc);
return ret;
}
int acomp_wakeup_stream_ch_disable(int chn){
int ret;
if (wakeup_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
ret = acomp_stream_ipc_channel_destroy(chn);
LISA_LOGI(TAG,"acomp_wakeup_stream_ch_disable chn index(%d),ret(%d)",chn,ret);
wakeup_handle->stream->ch[chn] = NULL;
return ret;
}
void* acomp_wakeup_stream_rx_buffer_get(int chn, uint32_t* len, uint16_t* desc_idx){
uint8_t *ptr;
if (wakeup_handle == NULL) {
return NULL;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return NULL;
}
if(wakeup_handle->stream->ch[chn] == NULL){
return NULL;
}
ptr = wakeup_handle->stream->ops.rx_buffer_get(wakeup_handle->stream->ch[chn], len, desc_idx);
return ptr;
}
int acomp_wakeup_stream_rx_buffer_release(int chn, uint16_t desc_idx, uint32_t len,void* buffer){
int ret;
if (wakeup_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
ret = wakeup_handle->stream->ops.rx_buffer_release(wakeup_handle->stream->ch[chn],buffer, len, desc_idx);
return ret;
}
void* acomp_wakeup_stream_tx_buffer_alloc(int chn, uint32_t* len, uint16_t* desc_idx){
uint8_t* buffer;
if (wakeup_handle == NULL) {
return NULL;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return NULL;
}
if(wakeup_handle->stream->ch[chn] == NULL){
return NULL;
}
buffer = wakeup_handle->stream->ops.tx_buffer_alloc(wakeup_handle->stream->ch[chn], len, desc_idx);
return buffer;
}
int acomp_wakeup_stream_tx_buffer_submit(int chn, void* buffer, uint32_t len, uint16_t desc_idx){
int ret;
if (wakeup_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
if(wakeup_handle->stream->ch[chn] == NULL){
return ACOMP_ERR_INVALID_STATE;
}
ret = wakeup_handle->stream->ops.tx_buffer_submit(wakeup_handle->stream->ch[chn], buffer, len, desc_idx);
return ret;
}

View File

@@ -0,0 +1,321 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "../utils/acomp_err.h"
#include "acomp_stream_ipc.h"
#include "ipc/acomp_ipc.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef BIT
#define BIT(x) (1 << (x))
#endif
#define ACOMP_WAKEUP_RES_NUMBER (2)
#define WAKEUP_INDEX_CAE_ESR_MLP (1)
#define WAKEUP_INDEX_AI_WRAP (2)
#define ALIGN_SIZE(len) ((len + IPC_ALIGN_SIZE - 1) / IPC_ALIGN_SIZE * IPC_ALIGN_SIZE)
/*语音唤醒组件回调事件定义*/
#define WAKEUP_CB_EVENT_ENGINE_RLT BIT(0) /*语音唤醒引擎结果返回*/
#define WAKEUP_CB_EVENT_STREAM_UPDATE BIT(1) /*音频数据流更新*/
#define WAKEUP_CB_EVENT_ENGINE_TIMEOUT BIT(2) /*唤醒超时*/
#define WAKEUP_CB_EVENT_ENGINE_ANGLE BIT(3) /*角度信息*/
#define WAKEUP_CB_EVENT_ENGINE_SWITCH_MODE BIT(4) /*模式切换*/
/* 语音唤醒组件输入音频数据帧信息 */
#define ACOMP_WAKEUP_AUDIO_INPUT_LEN_ONE_SAMPLE (sizeof(acomp_wakeup_audio_in_t)) /* 单个输入音频采样点的字节数 */
#define ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT (256) /* 每帧输入音频的采样点数量 */
#define ACOMP_WAKEUP_AUDIO_INPUT_LEN_ONCE_FRAME (ACOMP_WAKEUP_AUDIO_INPUT_LEN_ONE_SAMPLE * ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT) /* 单帧输入音频数据的总字节数 */
#define ACOMP_WAKEUP_ESR_NEED_FRAME_CNT (10) /* 一次完整识别需要输入的音频帧数量 */
/* 语音唤醒组件输出音频数据信息 */
#define ACOMP_WAKEUP_AUDIO_OUTPUT_CHANNELS_PER_SAMPLE (5) /* 输出音频每个采样点的通道数 */
#define ACOMP_WAKEUP_AUDIO_OUTPUT_LEN_ONE_SAMPLE (sizeof(acomp_wakeup_audio_out_t)) /* 单个输出音频采样点的字节数 */
#define ACOMP_WAKEUP_AUDIO_OUTPUT_MAX_LEN_ONCE_FRAME (ACOMP_WAKEUP_AUDIO_OUTPUT_LEN_ONE_SAMPLE * ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT * ACOMP_WAKEUP_ESR_NEED_FRAME_CNT) /* 单次输出音频数据的最大字节数 */
typedef void (*wakeup_event_cb_t)(uint32_t event, void *event_data, uint32_t event_data_len, void *priv);
/*算法模式定义*/
typedef enum {
ACOMP_WAKEUP_ALGO_MODE_WAKEUP = 0, /*唤醒模式*/
ACOMP_WAKEUP_ALGO_MODE_ESR = 1, /*ESR模式*/
} acomp_wakeup_algo_mode_e;
/*门限等级定义*/
typedef enum {
ACOMP_WAKEUP_THRESHOLD_LEVEL_1 = 1, /*门限等级1最低极易唤醒*/
ACOMP_WAKEUP_THRESHOLD_LEVEL_2 = 2, /*门限等级2易唤醒*/
ACOMP_WAKEUP_THRESHOLD_LEVEL_3 = 3, /*门限等级3默认档位*/
ACOMP_WAKEUP_THRESHOLD_LEVEL_4 = 4, /*门限等级4难唤醒*/
ACOMP_WAKEUP_THRESHOLD_LEVEL_5 = 5, /*门限等级5极难唤醒*/
ACOMP_WAKEUP_THRESHOLD_LEVEL_6 = 6, /*门限等级6最高禁用唤醒*/
} acomp_wakeup_threshold_level_e;
#ifdef CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC
typedef struct {
short mic0; /* mic0的音频 */
short mic1; /* mic1的音频 */
short ref0; /* 回采的音频 */
short ref1; /* 回采的音频如只有一路回采则可复制ref0的数据 */
} acomp_wakeup_audio_in_t;
#else
typedef struct {
short mic0; /* mic0的音频 */
short ref0; /* 回采的音频 */
} acomp_wakeup_audio_in_t;
#endif
typedef struct {
short mic0; /* mic0的音频 */
short mic1; /* mic1的音频在单麦算法的情况下mic1的音频实际为mic0的音频 */
short ref0; /* 回采的音频 */
short out1; /* 回声消除之后的音频 */
short out2; /* 算法输出的其他音频 */
} acomp_wakeup_audio_out_t;
/**
* @brief 初始化语音唤醒组件WAKEUP
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_NOT_FOUND : 设备未找到
*
*/
extern int acomp_wakeup_init(void);
// /**
// * @brief 逆初始化语音唤醒组件WAKEUP
// *
// * @return GCL_OK : 成功
// *
// */
// extern int acomp_wakeup_deinit(void);
/**
* @brief 就绪语音唤醒组件WAKEUP
*
* @note 该函数会初始化内存块及算法资源在调用acomp_wakeup_start之前必须调用该函数让组件进入就绪状态。
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_wakeup_prepare(void);
/**
* @brief 复位语音唤醒组件WAKEUP
*
* @note 该函数会释放内存块及算法资源。
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_wakeup_cleanup(void);
/**
* @brief 启动语音唤醒组件WAKEUP
*
* @note 该函数会启动语音唤醒组件,同步会启动音频流传输
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_wakeup_start(void);
/**
* @brief 停止语音唤醒组件WAKEUP
*
* @note 该函数会停止语音唤醒组件,同步会停止音频流传输
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_wakeup_stop(void);
/**
* @brief 设置组件参数
*
* @note 设置组件参数信息该参数项将在组件进入就绪态时生效调用wakeup_gcl_prepare
*
* @param params[in] 存储参数的结构指针
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
// extern int acomp_wakeup_params_set(acomp_wakeup_params_t *params);
/**
* @brief 设置是否使能调试模式
*
* @param enable[in] 0不使能1使能
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
// extern int acomp_wakeup_set_debug_mode(uint8_t enable);
/**
* @brief 设置算法模式
*
* @note 该函数必须在调用 acomp_wakeup_start() 之后设置才能生效
*
* @param mode[in] 算法模式,参考 acomp_wakeup_algo_mode_e
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_wakeup_set_algo_mode(acomp_wakeup_algo_mode_e mode);
/**
* @brief 设置唤醒门限等级
*
* @note 该函数必须在调用 acomp_wakeup_start() 之后设置才能生效
*
* @param level[in] 门限等级,参考 acomp_wakeup_threshold_level_e (1-6)
* 等级1最容易唤醒,等级6最难唤醒
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_wakeup_set_threshold(acomp_wakeup_threshold_level_e level);
/**
* @brief 给组件增加事件回调函数
*
* @param events[in] 待增加的事件位,可以同步注册多个事件位;
* @param cb[in] 回调的函数指针;
* @param priv[in] 回调函数的私有数据指针;
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_NO_MEM : 没有足够内存
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
*
*/
extern int acomp_wakeup_add_callback(uint32_t events, wakeup_event_cb_t cb, void *priv);
/**
* @brief 移除组件的毁掉函数
*
* @param cb[in] 待移除的回调函数
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_NOT_SUPPORTED : 无效操作
*
*
*/
extern int acomp_wakeup_remove_callback(wakeup_event_cb_t cb);
/**
* @brief 使能流通道
*
* @param chn[in] 通道索引
* @param desc[in] 通道描述符指针
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
* @retval ACOMP_ERR_CREATE_STREAM_FAILED : 创建流失败
*
*/
extern int acomp_wakeup_stream_ch_enable(int chn, acomp_stream_chn_create_desc_t *desc);
/**
* @brief 禁用流通道
*
* @param chn[in] 通道索引
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
*
*/
extern int acomp_wakeup_stream_ch_disable(int chn);
/**
* @brief 获取RX流缓冲区
*
* @param chn[in] 通道索引
* @param len[out] 数据长度指针
* @param desc_idx[out] 描述符索引指针
*
* @return 缓冲区指针如果失败返回NULL
*
*/
extern void* acomp_wakeup_stream_rx_buffer_get(int chn, uint32_t* len, uint16_t* desc_idx);
/**
* @brief 释放RX流缓冲区
*
* @param chn[in] 通道索引
* @param desc_idx[in] 描述符索引
* @param len[in] 数据长度
* @param buffer[in] 缓冲区指针
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
*
*/
extern int acomp_wakeup_stream_rx_buffer_release(int chn, uint16_t desc_idx, uint32_t len, void* buffer);
/**
* @brief 分配TX流缓冲区用于向remote发送音频数据
*
* @param chn[in] 通道索引
* @param len[out] 可用缓冲区长度指针
* @param desc_idx[out] 描述符索引指针
*
* @return 缓冲区指针如果失败返回NULL
*
*/
extern void* acomp_wakeup_stream_tx_buffer_alloc(int chn, uint32_t* len, uint16_t* desc_idx);
/**
* @brief 提交TX流缓冲区发送音频数据到remote
*
* @param chn[in] 通道索引
* @param buffer[in] 缓冲区指针
* @param len[in] 数据长度
* @param desc_idx[in] 描述符索引
*
* @return ACOMP_ERR_OK : 成功
* @retval ACOMP_ERR_INVALID_STATE : 无效状态
* @retval ACOMP_ERR_INVALID_ARG : 错误参数
*
*/
extern int acomp_wakeup_stream_tx_buffer_submit(int chn, void* buffer, uint32_t len, uint16_t desc_idx);
/**
*
* @note 索引0必须是WAKEUP_INDEX_CAE_ESR_MLP资源
* @note 索引1必须是WAKEUP_INDEX_AI_WRAP资源
*/
int acomp_wakeup_prepare_with_config(acomp_ipc_prepare_t *prepare);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,94 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* AP -> CP notify result*/
typedef enum{
WAKEUP_IPC_NOTIFY_SUBCMD_PCM_DATA = 1,
WAKEUP_IPC_NOTIFY_SUBCMD_WAKEUP_TIMEOUT = 2,
WAKEUP_IPC_NOTIFY_SUBCMD_ANGLE = 3,
WAKEUP_IPC_NOTIFY_SUBCMD_MODE_SWITCH = 4,
}wakeup_ipc_notify_subcmd_e;
typedef struct{
wakeup_ipc_notify_subcmd_e cmd;
}__attribute__((packed)) wakeup_ipc_notify_subcmd_hdr_t;
typedef struct{
wakeup_ipc_notify_subcmd_hdr_t hdr;
uint32_t frame_idx;
uint32_t len;
uint8_t data[];
}__attribute__((packed,aligned(32))) wakeup_ipc_notify_subcmd_pcm_data_t;
typedef struct{
wakeup_ipc_notify_subcmd_hdr_t hdr;
}__attribute__((packed,aligned(32))) wakeup_ipc_notify_subcmd_wakeup_timeout_t;
typedef struct{
wakeup_ipc_notify_subcmd_hdr_t hdr;
uint32_t len;
uint32_t angle_cnt;
uint16_t angle_data[];
}__attribute__((packed,aligned(32))) wakeup_ipc_notify_subcmd_angle_t;
typedef struct{
wakeup_ipc_notify_subcmd_hdr_t hdr;
uint32_t mode;
}__attribute__((packed,aligned(32))) wakeup_ipc_notify_subcmd_switch_mode_t;
/* CP -> AP control subcmd */
typedef enum {
WAKEUP_IPC_CONTROL_SUBCMD_PARAMETER_SET = 1,
WAKEUP_IPC_CONTROL_SUDCMD_DEBUG_MODE_SET = 2,
WAKEUP_IPC_CONTROL_SUBCMD_ALGO_MODE_SET = 3,
WAKEUP_IPC_CONTROL_SUBCMD_THRESHOLD_SET = 4,
}wakeup_ipc_control_subcmd_e;
typedef struct {
uint32_t debug_enable;
}__attribute__((packed)) wakeup_ipc_control_subcmd_parameter_set_t;
typedef struct {
uint8_t enable;
}__attribute__((packed)) wakeup_ipc_control_subcmd_debug_mode_set_t;
typedef enum {
WAKEUP_ALGO_MODE_WAKEUP = 0, // 唤醒模式
WAKEUP_ALGO_MODE_ESR = 1, // ESR模式
}wakeup_algo_mode_e;
typedef struct {
uint8_t mode; // wakeup_algo_mode_e
}__attribute__((packed)) wakeup_ipc_control_subcmd_algo_mode_set_t;
typedef enum {
WAKEUP_THRESHOLD_LEVEL_1 = 1, // 门限等级1最低
WAKEUP_THRESHOLD_LEVEL_2 = 2, // 门限等级2
WAKEUP_THRESHOLD_LEVEL_3 = 3, // 门限等级3
WAKEUP_THRESHOLD_LEVEL_4 = 4, // 门限等级4
WAKEUP_THRESHOLD_LEVEL_5 = 5, // 门限等级5
WAKEUP_THRESHOLD_LEVEL_6 = 6, // 门限等级6最高
}wakeup_threshold_level_e;
typedef struct {
uint8_t level; // wakeup_threshold_level_e (1-6)
}__attribute__((packed)) wakeup_ipc_control_subcmd_threshold_set_t;
/* ipc end */
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,11 @@
if (CONFIG_ACOMP)
listenai_library_sources(
acomp_wsp.c
)
listenai_include_directories(
./
)
endif()

View File

@@ -0,0 +1,345 @@
#include <string.h>
#include "ipc/acomp_ipc.h"
#include "acomp_wsp.h"
#include "acomp_err.h"
#include "gcl_cb_list/gcl_cb_list.h"
#include "private/wsp_ipc.h"
#include "acomp_stream_ipc.h"
#define TAG "acomp_wsp"
#include "lisa_log.h"
#define ACOMP_WSP_DEV_NAME "acomp.wsp"
typedef struct {
uint32_t dev_index;
gcl_cb_list_t event_callbacks;
acomp_stream_t *stream;
} acomp_wsp_handle_t;
static acomp_wsp_handle_t *wsp_handle = NULL;
void event_callback(acomp_ipc_message_t *message, void *priv)
{
acomp_wsp_handle_t *handle = (acomp_wsp_handle_t *)priv;
if (handle == NULL) {
return;
}
if (message->hdr.hdr.cmd == ACOMP_CONTEXT_IPC_GLB_NOTIFY) {
if (message->acomp_cmd == ACOMP_IPC_CMD_NOTIFY_RESULT) {
if (((void *)message->address != NULL) && (message->len > 0)) {
acomp_ipc_notify_result_t *result = (acomp_ipc_notify_result_t *)message->address;
gcl_cb_event_dispatch(handle->event_callbacks, WSP_CB_EVENT_ENGINE_RLT, result->data, result->len);
}
} else if (message->acomp_cmd == ACOMP_IPC_CMD_NOTIFY_SUBCMD) {
if (((void *)message->address != NULL) && (message->len > 0)) {
acomp_ipc_notify_subcmd_t *subcmd = (acomp_ipc_notify_subcmd_t *)message->address;
wsp_ipc_notify_subcmd_hdr_t *hdr = (wsp_ipc_notify_subcmd_hdr_t *)subcmd->data;
if ((hdr->cmd == WSP_IPC_NOTIFY_SUBCMD_VAD_BEGIN) || (hdr->cmd == WSP_IPC_NOTIFY_SUBCMD_VAD_END)) {
wsp_ipc_notify_subcmd_vad_t *vad = (wsp_ipc_notify_subcmd_vad_t *)subcmd->data;
if (vad->hdr.cmd == WSP_IPC_NOTIFY_SUBCMD_VAD_BEGIN) {
gcl_cb_event_dispatch(handle->event_callbacks, WSP_CB_EVENT_ENGINE_VAD_BEGIN, &vad->frame_idx,
sizeof(vad->frame_idx));
} else if (vad->hdr.cmd == WSP_IPC_NOTIFY_SUBCMD_VAD_END) {
gcl_cb_event_dispatch(handle->event_callbacks, WSP_CB_EVENT_ENGINE_VAD_END, &vad->frame_idx,
sizeof(vad->frame_idx));
}
}
else{
LISA_LOGW(TAG,"unknow notify hdr cmd:%d",hdr->cmd);
}
}
}
else if(message->acomp_cmd == ACOMP_IPC_CMD_NOTIFY_STREAM_UPDATE){
if (((void *)message->address != NULL) && (message->len > 0)) {
acomp_ipc_stream_update_t *ipc_msg;
uint32_t chn;
ipc_msg = (acomp_ipc_stream_update_t *)message->address;
chn = ipc_msg->index;
if(chn < sizeof(handle->stream->ch)/sizeof(handle->stream->ch[0])){
gcl_cb_event_dispatch(handle->event_callbacks,
WSP_CB_EVENT_STREAM_UPDATE,
handle->stream->ch[chn],
sizeof(acomp_stream_channel_t));
}
}
}
}
}
int acomp_wsp_init(void)
{
int ret = 0;
if (wsp_handle != NULL) {
return ACOMP_ERR_INVALID_STATE;
}
wsp_handle = (acomp_wsp_handle_t *)psram_malloc(sizeof(acomp_wsp_handle_t));
if (wsp_handle == NULL) {
return ACOMP_ERR_NO_MEM;
}
memset(wsp_handle, 0, sizeof(acomp_wsp_handle_t));
wsp_handle->event_callbacks = gcl_cb_list_create();
wsp_handle->dev_index = acomp_ipc_get_dev_index(ACOMP_WSP_DEV_NAME);
if (wsp_handle->dev_index < 0) {
psram_free(wsp_handle);
wsp_handle = NULL;
LISA_LOGE(TAG, "acomp wsp dev index not found!");
return ACOMP_ERR_NOT_FOUND;
}
LISA_LOGI(TAG, "acomp wsp dev index %d,name:%s", wsp_handle->dev_index, ACOMP_WSP_DEV_NAME);
ret = acomp_ipc_add_callback(wsp_handle->dev_index, (ipc_event_cb_t)event_callback, wsp_handle);
if (ret != ACOMP_ERR_OK) {
return ret;
}
ret = acomp_ipc_build_frame_send_sync(wsp_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_NEW | IPC_HEADER_REQ_REPALY, 0,
0, NULL, 0);
if (ret != ACOMP_ERR_OK) {
return ret;
}
wsp_handle->stream = acomp_stream_create(wsp_handle->dev_index );
if(wsp_handle->stream == NULL){
return ACOMP_ERR_CREATE_STREAM_FAILED;
}
return 0;
}
int acomp_wsp_deinit(void)
{
/*TODO*/
return ACOMP_ERR_NOT_SUPPORTED;
}
int acomp_wsp_prepare(void)
{
#define ACOMP_WSP_RES_NUMBER (2)
#define WSP_INDEX_MLP_ENCODER (1)
#define WSP_INDEX_MLP_DECODER (2)
acomp_ipc_prepare_t *prepare;
uint32_t size;
int ret;
size = sizeof(acomp_ipc_prepare_t) + sizeof(acomp_res_item_t) * ACOMP_WSP_RES_NUMBER;
prepare = psram_malloc_align(IPC_ALIGN_SIZE, size);
if (prepare == NULL) {
return ACOMP_ERR_NO_MEM;
}
memset(prepare, 0, sizeof(acomp_ipc_prepare_t));
prepare->number = ACOMP_WSP_RES_NUMBER;
prepare->item[0].index = WSP_INDEX_MLP_ENCODER;
prepare->item[0].addr = CONFIG_ACOMP_WSP_RES_ENCODER_ADDRESS;
prepare->item[0].offset = 0;
prepare->item[0].size = CONFIG_ACOMP_WSP_RES_ENCODER_LENGTH;
prepare->item[1].index = WSP_INDEX_MLP_DECODER;
prepare->item[1].addr = CONFIG_ACOMP_WSP_RES_DECODER_ADDRESS;
prepare->item[1].offset = 0;
prepare->item[1].size = CONFIG_ACOMP_WSP_RES_DECODER_LENGTH;
ret = acomp_ipc_build_frame_send_sync(wsp_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_PREPARE, 0, prepare, size);
psram_free(prepare);
return ret;
}
int acomp_wsp_cleanup(void)
{
int ret;
ret = acomp_ipc_build_frame_send_sync(wsp_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_CLEANUP, 0, NULL, 0);
return ret;
}
int acomp_wsp_start(void)
{
int ret;
ret = acomp_ipc_build_frame_send_sync(wsp_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_START, 0, NULL, 0);
return ret;
}
int acomp_wsp_stop(void)
{
int ret;
ret = acomp_ipc_build_frame_send_sync(wsp_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_STOP, 0, NULL, 0);
return ret;
}
int acomp_wsp_params_set(acomp_wsp_params_t *params)
{
int ret;
ret = acomp_ipc_build_frame_send_sync(wsp_handle->dev_index, ACOMP_CONTEXT_IPC_GLB_CONTROL | IPC_HEADER_REQ_REPALY,
ACOMP_IPC_CMD_CONTROL, 0, params, sizeof(acomp_wsp_params_t));
return ret;
}
int acomp_wsp_add_callback(uint32_t events, wsp_event_cb_t cb, void *priv)
{
int ret;
if (wsp_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
ret = gcl_cb_list_add_callback(wsp_handle->event_callbacks, events, cb, priv);
return ret;
}
int acomp_wsp_remove_callback(wsp_event_cb_t cb)
{
int ret;
if (wsp_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
ret = gcl_cb_list_remove(wsp_handle->event_callbacks, cb);
return ret;
}
int acomp_wsp_stream_ch_enable(int chn,acomp_stream_chn_create_desc_t *desc){
int ret = 0;
if ((wsp_handle == NULL) || (wsp_handle->stream == NULL)) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
wsp_handle->stream->ch[chn] = acomp_stream_ipc_channel_create(wsp_handle->stream,chn,wsp_handle->dev_index,desc);
if(wsp_handle->stream->ch[chn] == NULL){
return ACOMP_ERR_CREATE_STREAM_FAILED;
}
LISA_LOGI(TAG,"acomp_wsp_stream_ch_enable chn(%s) index(%d),desc(%p)",desc->cname,chn,desc);
return ret;
}
int acomp_wsp_stream_ch_disable(int chn){
int ret;
if (wsp_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
ret = acomp_stream_ipc_channel_destroy(chn);
LISA_LOGI(TAG,"acomp_wsp_stream_ch_disable chn index(%d),ret(%d)",chn,ret);
wsp_handle->stream->ch[chn] = NULL;
return ret;
}
void* acomp_wsp_stream_rx_buffer_get(int chn, uint32_t* len, uint16_t* desc_idx){
uint8_t *ptr;
if (wsp_handle == NULL) {
return NULL;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return NULL;
}
if(wsp_handle->stream->ch[chn] == NULL){
return NULL;
}
ptr = wsp_handle->stream->ops.rx_buffer_get(wsp_handle->stream->ch[chn], len, desc_idx);
return ptr;
}
int acomp_wsp_stream_rx_buffer_release(int chn, uint16_t desc_idx, uint32_t len,void* buffer){
int ret;
if (wsp_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
ret = wsp_handle->stream->ops.rx_buffer_release(wsp_handle->stream->ch[chn],buffer, len, desc_idx);
return ret;
}
void* acomp_wsp_stream_tx_buffer_alloc(int chn, uint32_t* len, uint16_t* desc_idx){
uint8_t* buffer;
if (wsp_handle == NULL) {
return NULL;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return NULL;
}
if(wsp_handle->stream->ch[chn] == NULL){
return NULL;
}
buffer = wsp_handle->stream->ops.tx_buffer_alloc(wsp_handle->stream->ch[chn], len, desc_idx);
return buffer;
}
int acomp_wsp_stream_tx_buffer_submit(int chn, void* buffer, uint32_t len, uint16_t desc_idx){
int ret;
if (wsp_handle == NULL) {
return ACOMP_ERR_INVALID_STATE;
}
if(chn >= ACOMP_STREAM_MAX_CHANNEL){
return ACOMP_ERR_INVALID_ARG;
}
if(wsp_handle->stream->ch[chn] == NULL){
return ACOMP_ERR_INVALID_STATE;
}
ret = wsp_handle->stream->ops.tx_buffer_submit(wsp_handle->stream->ch[chn], buffer, len, desc_idx);
return ret;
}
int acomp_virtqueue_dump(int chn)
{
virtqueue_dump(wsp_handle->stream->ch[chn]->vq);
return 0;
}

Some files were not shown because too many files have changed in this diff Show More