@[toc]
## 概要
` 提示:USB Descriptor、STM MCU`
本文以 Descriptor 層級實作為核心(Device/Configuration/BOS + Microsoft OS 2.0),說明如何讓裝置在使用 Windows/Linux 內建類別驅動(如 HID/WinUSB)時,也能安全、可預期地進入 USB Suspend/選擇性省電,並視需要啟用/禁止遠端喚醒。
### Microsoft OS 2.0參考資料
[官方網址](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specification)
[官方文檔](https://download.microsoft.com/download/3/5/6/3563ED4A-F318-4B66-A181-AB1D8F6FD42D/MS_OS_2_0_desc.docx)
## 整體架構流程
` 提示:BOS 是入口(宣告能力),MS OS 2.0 描述符才是「告訴 Windows 要建立哪些登錄屬性」的清單。`
1. 枚舉:主機讀取 Device/Configuration/Interface/Endpoint。
2. BOS:若啟用 `USBD_CLASS_BOS_ENABLED=1`,主機會再取 BOS Descriptor。
3. MS OS 2.0 要求:Windows 依 BOS 之 Platform Capability,透過 Vendor Control Request 讀取 `MS_OS_20_DESCRIPTOR_SET`。
4. Registry 層設定:若 `MS_OS_20_DESCRIPTOR_SET` 內含 Registry Property Descriptor(如 `SelectiveSuspendEnabled=1`、`EnhancedPowerManagementEnabled=1`),Windows 自動建立/更新登錄值。
5. 省電執行:在無 I/O 的情況下,主機依協議/登錄策略讓裝置進入 USB Suspend(總線無活動 ≥3ms) 或對應的 D-State(對 OS 而言)。
### 示意圖
` 提示:[]屬於USB Device動作。沒有[]屬於電腦Host端的動作`
```
[Device Descriptor] → [Configuration] → [Interface]
↓
[BOS Descriptor]
↓
[Platform Capability (MS OS 2.0 GUID)]
↓
Windows 傳送 Vendor Request → 要 MS_OS_20_DESCRIPTOR_SET
↓
[裝置回傳 Registry Property Descriptor]
↓
Windows 自動建立/更新機碼
```
## 技術名詞解釋
- Suspend(USB 層):USB 匯流排無活動達閾值(USB2 約≥3ms)後,裝置進入低功耗。USB需降至 ≤2.5mA(總線供電時)。
- Remote Wakeup:裝置可向主機發出喚醒訊號;是否允許由 Configuration Descriptor 的 bmAttributes bit5 與主機 `SET_FEATURE/CLEAR_FEATURE(REMOTE_WAKEUP)` 決定。
- BOS (Binary Object Store):集中宣告裝置「進階能力」的容器(如 USB 2.0 Extension、SuperSpeed、Platform Capability)。
- USB 2.0 Extension(LPM):提供 Link Power Management 能力,縮短進入低功耗的延遲、提升相容性與效率。
- MS OS 2.0 Descriptor:Microsoft 定義的描述符集合。Windows 讀取後可自動建立 Registry 屬性(如 `SelectiveSuspendEnabled`、`EnhancedPowerManagementEnabled`、`DeviceInterfaceGUIDs`)。
- D-State(OS 抽象):作業系統對裝置電源狀態的抽象(D0/D2/D3hot 等),與 USB 總線層 Suspend 互相關聯但非一個一個對應。
## 技術細節
#### 1.Configuration Descriptor 關鍵欄位
在USBD_FS_BOSDesc中提供Microsoft OS 2.0 Platform Capability Descriptor Header

#### 2.BOS 與兩個常見開關
##### 1️⃣ `USBD_LPM_ENABLED`
- **LPM = Link Power Management**
- 這是 **USB 2.0 Extension** 的一部分(屬於 BOS 裡的一個 Capability)。
- 當你要在 BOS 裡放 **USB 2.0 Extension Descriptor**(例如支援 LPM、Remote Wakeup 增強),就必須把 `USBD_LPM_ENABLED = 1`。
- 如果沒開,裝置的 BOS 可能會少掉 LPM 相關 Capability,導致 Windows 判斷「裝置不支援 LPM」。
```
/*---------- -----------*/
#define USBD_MAX_NUM_INTERFACES 1U
/*---------- -----------*/
#define USBD_MAX_NUM_CONFIGURATION 1U
/*---------- -----------*/
#define USBD_MAX_STR_DESC_SIZ 2048U
/*---------- -----------*/
#define USBD_DEBUG_LEVEL 0U
/*---------- -----------*/
#define USBD_LPM_ENABLED 1U
/*---------- -----------*/
#define USBD_SELF_POWERED 1U
/*---------- -----------*/
```
##### 2️⃣ `USBD_CLASS_BOS_ENABLED`
- **BOS = Binary Object Store**
- 這是一個 USB 2.1 引入的描述符,用來集中宣告裝置的「進階能力」,例如:USB 2.0 Extension(LPM)、USB 3.0 SuperSpeed、Platform Capability (MS OS 2.0 / WebUSB)。
- `USBD_CLASS_BOS_ENABLED = 1` 代表韌體會在主機請求 BOS 描述符時回應正確的 BOS 結構。
- 如果沒開,裝置就完全不會回 BOS,主機也無法得知裝置的進階能力,像 MS OS 2.0 或 WebUSB 等功能就不會被觸發。
#### 3.MS OS 2.0 描述符
usbd_msos20.h內容
```
#ifndef __USBD_MSOS20_H__
#define __USBD_MSOS20_H__
#include "usbd_def.h"
extern const uint8_t MS_OS_20_DESCRIPTOR_SET[];
extern const uint16_t MS_OS_20_DESCRIPTOR_SET_TOTAL_LENGTH;
#endif // __USBD_MSOS20_H__
```
usbd_msos20.c內容
```
//usbd_msos20.c
#include "usbd_def.h"
#include "usbd_msos20.h"
__ALIGN_BEGIN const uint8_t MS_OS_20_DESCRIPTOR_SET[] __ALIGN_END =
{
//--------------------------------------------
// MS OS 2.0 Set Header Descriptor (10 bytes)
//--------------------------------------------
0x0A, 0x00, // wLength - 10 bytes
0x00, 0x00, // wDescriptorType = MS_OS_20_SET_HEADER (0)
0x00, 0x00, 0x03, 0x06, // dwWindowsVersion = 0x06030000 (Windows 8.1+)
0x48, 0x00, // wTotalLength = 72 bytes
//
// Microsoft OS 2.0 Registry Value Feature Descriptor
//
0x3E, 0x00, // wLength - 62 bytes
0x04, 0x00, // wDescriptorType – 4 for Registry Property
0x04, 0x00, // wPropertyDataType - 4 for REG_DWORD
0x30, 0x00, // wPropertyNameLength – 48 bytes
0x53, 0x00, 0x65, 0x00, // Property Name - “SelectiveSuspendEnabled”
0x6C, 0x00, 0x65, 0x00,
0x63, 0x00, 0x74, 0x00,
0x69, 0x00, 0x76, 0x00,
0x65, 0x00, 0x53, 0x00,
0x75, 0x00, 0x73, 0x00,
0x70, 0x00, 0x65, 0x00,
0x6E, 0x00, 0x64, 0x00,
0x45, 0x00, 0x6E, 0x00,
0x61, 0x00, 0x62, 0x00,
0x6C, 0x00, 0x65, 0x00,
0x64, 0x00, 0x00, 0x00,
0x04, 0x00, // wPropertyDataLength – 4 bytes
0x01, 0x00, 0x00, 0x00 // PropertyData - 0x00000001
};
const uint16_t MS_OS_20_DESCRIPTOR_SET_TOTAL_LENGTH = sizeof(MS_OS_20_DESCRIPTOR_SET);
```
#### 4.在Setup中攔截0x20
快速測試的作法,建議還是在`HAL_PCD_SetupStageCallback()`中進行更完整的實作。

```
extern USBD_HandleTypeDef hUsbDeviceFS;
int8_t USBD_CUSTOM_HID_VendorRequest(uint8_t cmd, uint8_t* pbuf, uint16_t length)
{
USBD_CtlSendData(&hUsbDeviceFS, (uint8_t*)MS_OS_20_DESCRIPTOR_SET, MS_OS_20_DESCRIPTOR_SET_TOTAL_LENGTH);
return (USBD_OK);
}
```
### 技術細節改善建議
#### 當前作法
在 `HAL_PCD_SetupStageCallback()` 只攔截「標準 + 裝置收件」的 `SET_FEATURE/CLEAR_FEATURE(REMOTE_WAKEUP)`,處理完就 `USBD_CtlSendStatus()` 後 return
其他請求一律丟回 ST 既有流程(`USBD_LL_SetupStage`)。
同時,在 `USBD_CUSTOM_HID_Setup()` 裡,凡是 `USB_REQ_TYPE_VENDOR` 且 `bRequest==0x20`,就直接呼叫自訂的 `USBD_CUSTOM_HID_VendorRequest()`,未檢查方向(IN/OUT)、收件者(Device/Interface/Endpoint)、也未檢查 `wIndex==0x0007` 與長度邊界,由外部 handler 來回應資料。
#### 優點
最小侵入、結構乾淨:標準請求只處理 Remote-Wakeup 的最小集,其他交還內建流程,降低與 ST Stack 的耦合度與回歸風險。
相容度高:因沒有「吃掉」大多數請求,Windows 仍能順利透過預設路徑完成 BOS/MS OS 2.0 的查詢。
易於除錯:Remote-Wakeup 與 Vendor-Request 的路徑清楚分離,問題定位較直覺。
#### 缺點與風險
規範檢查不嚴謹:在 Class Setup 端攔 Vendor 請求但未檢查 `bmRequestType==0xC0`(Device→Host | Vendor | Device)與 `wIndex==0x0007`,容易「看似可用」但不符合 MS OS 2.0 規範。
可移植性與未來相容性風險:主機堆疊若更新得更嚴格,或加入其他 Vendor 路徑(例如 OS 2.0 sub-set、OS 1.0 相容路徑)時,未檢查 `wIndex` 可能導致誤回應。
資料階段處理不完整:未在這段邏輯中明確使用 `USBD_CtlSendData()` 依 `wLength` 進行長度裁剪與分段,若外部 handler 也未處理,可能出現短傳/過長/分段錯誤。
收件者混淆的可能:未限制 **Device recipient**(有些主機會對 Interface/Endpoint 發 Vendor 請求),若誤接管,可能干擾其它介面或類別行為。
職責邊界模糊:在 **Class(HID)Setup** 內處理「全裝置層」的 OS 2.0 請求,對多介面或非 HID 介面(如 WinUSB/CDC)來說,權責不清,後續擴充較不利。
#### 改善建議
MS OS 2.0 的處理邏輯應統一放在 **EP0 通用層**(例如 `usbd_ctlreq.c` 或在 `HAL_PCD_SetupStageCallback()` 嚴格過濾),僅在 `bmRequestType=0xC0`(Device-to-Host|Vendor|Device recipient)、`bRequest=MS_OS20_VENDOR_CODE`、`wIndex=0x0007` 時回傳 `MS_OS_20_DESCRIPTOR_SET`(資料長度以 `min(wLength, sizeof(set))` 送出);若 `bRequest=MS_OS20_VENDOR_CODE` 但參數不符合(例如 `wIndex` 非 0x0007),則回 `USBD_CtlError()`(STALL);其它所有請求一律交回預設流程處理,以確保合規且不誤攔。
所有控制相關的巨集建議集中管理,例如在 `usbd_conf.h` 中定義 `USBD_CLASS_BOS_ENABLED`、`USBD_LPM_ENABLED` 與 `MS_OS20_VENDOR_ENABLED`,並確保 BOS 的 Platform Capability 之 `wMSVendorCode` 與 `MS_OS20_VENDOR_CODE` 一致,方便專案層設定且確保 BOS/LPM 確實啟用。
BOS 與 MS OS 2.0 描述符建議分檔維護:`usbd_desc.c` 可承載 BOS(包含 USB 2.0 Extension 與 MS OS 2.0 Platform Capability),`usbd_msos20.c/.h` 專責 MS OS 2.0 Descriptor Set,並務必驗證各 `wLength/wTotalLength` 與 UTF-16LE 編碼正確。
Class 層(例如 HID Setup)不應再攔截 MS OS 2.0 的請求,該路徑交由通用層處理;Class 僅保留與該介面自身相關的其他 Vendor 請求,以避免重複或衝突。
最後,建議加入防呆與紀錄機制:檢查 `bmRequestType/bRequest/wIndex/wLength` 合理性,在 Debug 模式下輸出事件計數與錯誤碼,以利除錯與追蹤;若裝置同時有 FS/HS 兩個 PCD 例項,兩邊均應套用相同過濾規則。
## AI Prompt
` 提示:針對STM MCU,下列的描述與本文的技術細節有一些不同,本文提供的技術細節並沒有針對wMSVendorCode 0x20、==*wIndex 0x07*==於HAL_PCD_SetupStageCallback進行辨識,而是再USBD_CUSTOM_HID_Setup前段中,直接攔截0x20進行處理。關於MS_OS_20_DESCRIPTOR_SET技術實作中只要SelectiveSuspendEnabled設為1便能達到效果。 `
```
請幫我在 STM32 專案(使用 STM32Cube HAL 與 STM32 USB Device Library)中加入「不需自訂驅動也能自動進入 USB Suspend」的功能。實作方式必須透過 Descriptor 來驅動低功耗機制,也就是在 BOS 描述符中開啟 USB 2.0 Extension 以支援 LPM,同時啟用 BOS 本身的回應,並且在 BOS 內新增一個 Microsoft OS 2.0 Platform Capability,其 GUID 為 {D8DD60DF-4589-4CC7-9CD2-659D9E648A9F},其中 dwWindowsVersion 設定為 0x06030000,wMSVendorCode 設定為 0x20,AltEnumCode 設為 0。接著請建立一份完整的 MS_OS_20_DESCRIPTOR_SET,並且在其中至少包含兩個 Registry Property Descriptor,分別為 SelectiveSuspendEnabled 與 EnhancedPowerManagementEnabled,兩者的值都必須是 REG_DWORD little-endian 形式的 1,名稱要以 UTF-16LE 編碼正確填寫。韌體必須能處理主機透過 EP0 傳送的 Vendor Request,當 bRequest 為 0x20 且 wIndex 為 0x07 時,要能正確回傳整個 MS_OS_20_DESCRIPTOR_SET,並且根據主機的 wLength 做長度檢查,必要時允許短傳或送出 STALL。Configuration Descriptor 中的 bmAttributes bit5 需要能依據巨集切換是否支援 Remote Wakeup,預設情況為關閉,巨集設為 1 時才允許。所有新增的常數請集中定義在 usbd_conf.h,例如 USBD_CLASS_BOS_ENABLED、USBD_LPM_ENABLED 與 MS_OS20_VENDOR_ENABLED。若原始專案沒有 BOS 檔案,請新增 usbd_desc.c/.h 來定義 BOS 描述符,並新增 usbd_msos20.c/.h(或在 usbd_desc.c 中)放置 MS_OS_20_DESCRIPTOR_SET 的內容。最後請提供完整可編譯的檔案內容與 unified diff,並附上一份 README_USB_Power.md,清楚描述巨集的使用方式、Vendor Request 的流程。
```
## 結論
只要在 **Descriptor 層**把基礎功課做好(**Configuration 的 Remote Wakeup 與電力宣告**、**BOS 的 LPM 與 Platform Capability**、**MS OS 2.0 的 Registry 屬性**),即使**不撰寫自訂驅動**,也能讓裝置在 Windows/Linux 內建驅動下,可靠地進入 **USB Suspend/選擇性省電**。BOS 負責「讓主機看見能力」,MS OS 2.0 負責「讓 Windows 形成正確的電源管理機碼」,兩者相輔相成,構成一條 **描述符驅動電源管理** 的最佳實踐路徑。