VxWorks 7 TSN協議棧實現

本文介紹了TSN協議的基本原理及其在VxWorks上的實現和使用方法。

0. 參考

1. 基本概念

  1. TSN:時間敏感網絡,基于802.1AVB演化而來,通過 IEEE 802 網絡提供確定性服務,即保證具有有限低延遲、低數據包延遲變化和低數據包丟失的數據包傳輸。
  2. IEEE1588v2/PTP: 一種精確時間協議PTP(Precision Time Protocol),包括IEEE1588v1和IEEE1588v2兩個版本。該標準適用于通過支持多播信息傳遞的局域網進行通信的系統,包括但不限于以太網。該協議支持亞微秒級(100納秒)的全系統同步精度,可以映射到UDP/IP、DeviceNet和第二層以太網即鏈路層。PTP基本原理包含頻率同步和相位同步:
    • 相位同步原理如下(以TwoStep模式為例,OneStep模式中sync報文包含t1,所以不需要Follow_up報文), 其中傳輸延時delay((t2 - t1) + (t4 - t3)) / 2,相位偏差offset(t2 - t1) -傳輸延時delay,即((t2 - t1) - (t4 - t3)) / 2
      image.png
      • 頻率同步原理如下所示,其中從設備時鐘頻率調整因數為(T(n+1) - Tn) / (T(n+1)' - Tn')
        image.png
  3. 802.1AS/gPTP Standard:IEEE1588協議的一個簡化版二層Profile又稱為gPTP,通過將大量不同的IEEE1588選項縮小到可管理的幾個關鍵選項,從而適用于汽車或工業自動化環境。注意, gPTP和PTP的同步原理類似,除了只支持TwoStep和P2P模式,具體可參考車載以太網AVB簡介—第二篇:gPTP簡介
  4. 802.1Qbv Standard:支持調度流量的增強,即通過規定時間感知隊列的消耗方式,為傳輸幀分配不同優先級的虛擬局域網(VLAN)標簽,使得網橋和終端能夠根據源自IEEE802.1AS時間同步幀對傳輸進行調度,從而允許同時支持計劃流量、基于信用的整型器流量和其他通過局域網(LAN)的橋接流量。
  5. 802.1Qbu Standard:規定IEEE802.1AS時間同步幀的搶占原則,允許橋接端口在傳輸一個或多個時間關鍵幀時暫停非時間關鍵幀的傳輸,并為網橋端口和終端設備提供發現、配置和控制搶占服務的功能,降低關鍵幀的傳輸延遲。

2. VxWorks7 TSN

2.1 概述

VxWorks 7通過TSN、PTP和GPTP等3個layer提供TSN支持,并依賴于END、IPNET_COREIP和UTILS_JANSSON等3個layer。其中,TSN棧位于net\tsn-2.0.2.2目錄,包括時鐘、Qav/Qbv、Stream和配置等4個部分以及對應的demo,同時通過_WRS_CONFIG_TSN_STREAM和_WRS_CONFIG_TSN_CONFIG在IPNET等layer中引入相關修改。

TSN設備驅動包括網口驅動和1588定時器驅動兩部分,其中1588定時驅動位于net\end-2.0.3.3\drv\src\1588目錄中,網口驅動位于PSL或者net\end-2.0.3.3\drv\src\end目錄中。

2.2 TSN配置

TSN使用JSON文件作為配置來源并通過tsnConfig接口或者shell命令對指定的TSN網口進行配置,示例如下:

{
    "schedule": { // Schedule timing is mandatory
        "cycle_time": 1000000, // Cycle time is 1000 usecs.(門控列表調度執行周期)
        "start_sec": 0, // Schedule starts at { start_sec, start_nsec }
        "start_nsec": 0
    },
    "preemption": { // Preemption enabled (optional)
        "tclass_mask": 0x01 // Traffic class mask. TC 0 is preemptible, other TCs are express.
    },
    "gate_control_list": [ // 802.1Qbv gate control list (optional).
        // Total gate time must be equal to cycle time.
        {
            "tclass_mask": 0x80, // Traffic class mask. Only TC 7 can transmit.
            "time": 100000 // Gate time 100 usec, opens at 0 usec for first gate.
        },
        {
            "tclass_mask": 0x40, // Only TC 6 can transmit.
            "time": 200000 // Gate time 200 us, opens at 100 usec.
        },
        {
            "tclass_mask": 0x3f, // TC 0-5 can transmit. Use for "best-effort" traffic.
            "time": 700000 // Gate time 700 usec, opens at 300 usec.
        }
    ],
    "stream_objects": [ // At least one stream object is mandatory
        {
            "stream": {
                "name": "flow1",
                "dst_mac": "01:c0:ff:ee:00:04", // Muticast MAC address
                "vid": 3000, // VLAN ID
                "pcp": 7, // normally pcp==tclass
                "tclass": 7, // Stream maps to TC 7.
                "tx_time": { // Transmission time based scheduling (optional), 
                             // usually no gate control list should be defined
                    "offset": 50000 // Stream transmits at 50 usecs.
                }
            }
        },
        {
            "stream": {
                "name": "flow2",
                "dst_mac": "01:c0:ff:ee:00:05",
                "vid": 3001,
                "pcp": 6,
                "tclass": 6 // Stream maps to TC 6, transmitting in 100-300 usec window.
            },
            "ip": { // IP interception (optional)
                "dst_ip": "192.168.1.11",
                "dst_mask": "255.255.255.255",
                "dst_port": 0, // available when proto == 6 or 17
                "src_ip": "192.168.1.10",
                "src_mask": "255.255.255.255",
                "src_port": 0, // available when proto == 6 or 17
                "proto": 0 // IP protocol number. e.g. UDP: 17, TCP: 6
            }
        }
    ]
}

2.3 TSN時鐘和定時器

TSN時鐘和定時器功能依賴于TSN、PTP和GPTP等3個layer,其核心文件包括:

  • net\tsn-2.0.2.2\src\clock\src\tsnClkLib.c
  • net\tsn-2.0.2.2\src\clock\h\tsnClkLib.h
  • net\end-2.0.3.3\drv\src\1588\vxbIeee1588.c

TSN 1588定時器驅動通過ioctl提供PTP硬件時鐘寄存器(PHC)的獲取/設置和1588 timer的分配、釋放、中斷回調掛接、中斷路由、周期/one-shot模式切換等功能,并通過TSN layer的tsnClkLib提供用戶接口。

每個TSN網口通常支持2個1588定時器。

2.3.1 PTP啟動

master和slave端都需要使用ptpdAdd()為相應網卡綁定PTP協議,并使用ptpSart()啟動PTP:

/******************************************************************************
*
* ptpdAdd - bind ptpd to Ethernet interface
*
* This function is used to bind ptpd to different Ethernet interfaces.
*    ethName       the Ethernet device name. For example, "gei0".
*    layer         transport layer PTP_DEVICE_LAYER_2 or PTP_DEVICE_LAYER_4
*    slaveMaster   0=SLAVE, 1=MASTER, 2 (others)= BMCA
*    delayMechanism 0=PTP_DEVICE_E2E  or 1=PTP_DEVICE_P2P
*    oneStepMode    0=PTP_DEVICE_TWO_STEP or 1=PTP_DEVICE_ONE_STEP
*    pri          PTP Priority When Configured as Master Clock
*    PdelayReq    PTP Pdelay Request Interval,reciprocal of N times of power of 2,
*                 (1 ~ -4) means (0.5PPS ~ 16PPS)
*    profile     Select ptp profile 1=PTP_802_1AS_PROFILE or 0=PTP_DEFAULT_PROFILE
* RETURNS: PTP_OK or PTP_ERROR
*
* ERRNO: N/A
*/

例如,

# ethName("gei1")、transport layer(2)、master mode(1)、delayMechanism(PTP_DEVICE_P2P)、
# oneStepMode(PTP_DEVICE_TWO_STEP)、PTP Priority(128)、PTP Pdelay Request Interval(8PPS)
# profile(PTP_802_1AS_PROFILE)
-> ptpdAdd "gei1",2,1,1,0,128,-3,1 # 

# ethName("gei1")、transport layer(2)、slave mode(0)、delayMechanism(PTP_DEVICE_P2P)、
# oneStepMode(PTP_DEVICE_TWO_STEP)、PTP Priority(128)、PTP Pdelay Request Interval(8PPS)
# profile(PTP_802_1AS_PROFILE)
-> ptpdAdd "gei1",2,0,1,0,128,-3,1

使用方法為在master和slave側運行如下命令:

# master
-> ptpdStop "gei0"
-> ptpdClearAll
-> ptpdAdd "gei0",2,1,1,0,128,-4,1
-> ptpdStart
-> ptpdSetDebugLevel "gei0", 1

# slave
-> ptpdStop "gei1"
-> ptpdClearAll
-> ptpdAdd "gei1",2,1,1,0,128,-4,1
-> ptpdStart
-> ptpdSetDebugLevel "gei1", 1

成功時打印如下信息:

AVnu AP Status : STATION_STATE_AVB_SYNC

命令中的核心參數組合如下所示:

模式 Master參數
layer-Master-P2P-onestep-pri-PPS-profile
Slave參數
layer-Master-P2P-onestep-pri-PPS-profile
結果
Master competition 2,1,1,0,128,-4,1 2,1,1,0,128,-4,1 MAC小的成為Best Master,另一個成為passive master
BMCA 2,2,1,0,128,-4,1 2,2,1,0,128,-4,1 1)MAC小的成為Best Master,另一個成為Slave
2)收斂時間小于7秒
BMCA Priority 2,2,1,0,1,-4,1 2,2,1,0,128,-4,1 1)優先級高(數字小)的成為 Master
2)收斂時間小于7秒
802.1AS with TwoStep 2,1,1,0,128,-4,1 2,0,1,0,1,-4,1 Offset from Master小于1微秒
802.1AS with OneStep 2,1,1,1,128,-4,1 2,0,1,1,128,-4,1 IEEE802.1AS只支持TwoStep
802.1AS with 4 layer 4,1,1,0,128,-4,1 4,0,1,0,128,-4,1 IEEE802.1AS只支持2 layer
802.1AS with E2E 2,1,0,0,128,0,1 2,0,0,0,128,0,1 IEEE802.1AS只支持P2P
no 802.1 AS,2 layer,P2P,TwoStep 2,1,1,0,128,-4,0 2,0,1,0,128,-4,0 1)Offset from Master小于1微秒
2)收斂時間小于30秒
no 802.1 AS,2 layer,E2E,TwoStep 2,1,0,0,128,0,0 2,0,0,0,128,0,0 1)Offset from Master小于1微秒
2)收斂時間小于35秒
no 802.1 AS,4 layer,P2P,TwoStep 4,1,1,0,128,-4,0 4,0,1,0,128,-4,0 1)Offset from Master小于1微秒
2)收斂時間小于35秒
no 802.1 AS,4 layer,E2E,TwoStep 4,1,0,0,128,0,0 4,0,0,0,128,0,0 1)Offset from Master小于1微秒
2)收斂時間小于35秒

2.3.2 gPTP啟動

master和slave端都需要使用daemon_cl()為相應網卡綁定gPTP協議并啟動gPTP dameon,格式如下所示:

void VxDaemonParam::print_usage( char *arg0 ) {
    printf( "%s <network interface> [-R <priority 1>] "
            "[-E] [-V] [-GM] [-INITSYNC <value>] "
            "\n",
            arg0 );
    printf
        ( "\t-R <priority 1> priority 1 value\n"
          "\t-E enable test mode (as defined in AVnu automotive profile)\n"
          "\t-V enable AVnu Automotive Profile\n"
          "\t-GM set grandmaster for Automotive Profile\n"
          "\t-INITSYNC <value> initial sync interval (Log base 2. 0 = 1 second)\n"
        );
}

其中:

  • gPTP支持BMCAAVnu Automotive兩種互斥的模式,通過-V參數選擇;
  • BMCA模式僅支持-R參數。

使用方法為在master和slave側運行如下命令:

# master
> daemon_cl("gei0 -V -E -GM")
# slave
> daemon_cl("gei1 -V -E")

成功時打印如下信息:

AVnu AP Status : STATION_STATE_AVB_SYNC

命令中的核心參數組合如下所示:

模式 msater參數 salve參數 結果
Normal Configuration for GM for automotive profile -V -E -GM -V -E OK
Normal configuration for GM without test mode -V -GM -V Fail
Normal configuration with -INITSYNC -V -E -INITSYNC -3 -GM -V -E -INITSYNC -3 OK
Normal configuration with -INITSYNC without test mode -V -INITSYNC -3 -GM -V -INITSYNC -3 Fail
Normal configuration with -INITSYNC without test mode -V -INITSYNC -3 -GM -V -INITSYNC -3 Fail
Normal configuration in BMCA mode -R 1 -R 248 Fail
All devices in BMCA mode with default priority1 NA NA Fail
All devices in BMCA mode, one with specified priority1, another with default priority1 -R 1 NA Fail
One device with fixed GM, one in BMCA mode -V -E -GM -R 248 Fail

綜上所述,只有兩種參數組合能夠在VxWorks下通過測試:

  • 包含測試模式的自動化Profile:-V -E -GM/-V -E
  • 包含測試模式和初始同步間隔的自動化Profile:-V -E -INITSYNC -3 -GM/-V -E -INITSYNC -3

2.3.3 獲取PTP時間

用戶可以通過tsnClockIdGet() 獲得1588定時器的描述符,然后通過tsnClockTimeGet() 獲得PTP時間:

clockid_t cid;
struct timespec tm;
cid = tsnClockIdGet("gei",0,0);
if (cid != 0)
    {
    ret = tsnClockTimeGet (cid, &tm);
    }

2.3.3 定時器操作

用戶可以對PTP定時器采用普通定時器類似的操作,包括:

tsnTimerAllocate()
tsnTimerRelease()
tsnClockConnect()
tsnClockDisable()
tsnClockEnable()
tsnClockRateGet()
tsnClockRateSet()
tsnClockIntReroute()

2.4 802.1Qbv

VxWorks通過如下兩種方式來使能802.1Qbv:

  • Time Specific Departure (TSD)
  • Time Gated - IEEE 802.1Qbv

2.4.1 TSD

TSD使用戶能夠指定何時可以傳輸一個幀。當這種能力被啟用時,設備將延遲幀的傳輸,以便它可以 在精確指定的時間內傳輸。TSD是在具有最高優先級的隊列7上啟用的。目前,TSN流量將通過隊列7,其他流量將通過隊列0。

與此同時,多隊列和VLAN也被實現。對于多隊列,有8個Tx 隊列(0~7)和一個Rx隊列(0)。對于VLAN,并網口支持的所有VLAN功能都被啟用。只有TSN流所要求的功能被啟用。

Queue No Priority Traffic Class (TC)
0 0 0
1 1 1
... ... ...
7 7 7

TSD存在如下限制:

  • 周期和時隙是由TSN流庫維護的。由于軟件延遲,一個數據包有可能錯過它的時隙。
  • 盡管網卡可能支持多達16個Tx隊列和8個流量類別(TC),但為了保證TSD的性能,只有最高優先級的隊列(具有TC7的隊列7)才被使用。所以多個TSN流共享同一個Tx隊列。
  • 有必要使用ETF(Earliest TxTime First)來確保所有來自不同數據流(通常來自不同Task)的報文按照Tx時間的先后順序存在于Tx隊列中。
  • TSD只允許在一個周期的時隙內,從一個流中發送一個數據包。如果希望在一個時間段內發送多個數據包,需要將多個流與一個應用程序綁定在一起。

注意:ETF依賴于IPNET_QOS

2.4.2 Time Gated - IEEE 802.1Qbv

IEEE 802.1 Qbv中規定的時間感知整形器(TAS),也被稱為時間門控,允許每個流量類別的傳輸相對于已知的時間尺度進行規劃。允許每個流量類別的傳輸相對于一個已知的時間顆粒來安排。為了 為了實現這一目標,每個流量類別都有一個傳輸門,傳輸門的狀態決定是否排隊和可以選擇排隊的幀進行傳輸。傳輸門門可以處于如下兩種狀態之一:

  • 打開:根據與流量類別相關的傳輸選擇算法的定義,排隊的幀被選擇用于傳輸。
  • 關閉:排隊的幀不被選擇用于傳輸。
    門控列表如下所示:
0 1 2 3 4
持續時間(ns) 100000 200000 100000 300000 300000
TC0 1(開放) 0(關閉) 0(關閉) 0(關閉) 1(開放)
TC7 0(關閉) 1(開放) 0(關閉) 0(關閉) 0(關閉)
TC6 0(關閉) 0(關閉) 1(開放) 0(關閉) 0(關閉)
TC5 0(關閉) 0(關閉) 0(關閉) 1(開放) 1(開放)
  • 與一個端口相關的門控列表(GCL)包含一個有序的門操作列表。
  • 每個門操作都指定了所有門的門控狀態和這些狀態的持續時間,以及可以在開放期間傳輸的TC。
  • 如果沒有配置新的GCL,當前的GCL將從每個周期的第一個門開始循環。因此,門的時間之和必須小于或等于周期時間。
  • 如果配置了一個新的GCL,它不會立即生效,而會等到舊GCL的當前周期結束。

如上所述,使用IEEE 802.1Qbv,可以消除TSD的許多限制:

  • 通過硬件控制周期和閘門,消除軟件造成的延遲,從而提供最佳的性能。
  • 8個Tx隊列和相應的8個流量類別得到了充分的利用。
  • 每個流可以有其專用的Tx隊列、優先級和流量類別,從而消除了順序錯誤的數據包的問題。
  • 只要門的周期足夠長,在一個門中為一個流發送數據包就沒有限制。

2.4.2.1 配置

配置可以通過2.2 TSN配置提到的JSON配置文件方式配置,也可以組合使用如下接口進行配置:

tsnStreamCreate()
tsnStreamDeviceSet()
tsnStreamCycleStartSet()
tsnStreamPeriodSet()
tsnStreamOffsetSet()
tsnStreamVidSet()
tsnStreamPcpSet()
tsnStreamDstSet()

配置完成之后,可以通過如下3種方式在TSN流上進行數據收發:

  • 套接字綁定,即創建一個通用UD/TCP套接字并綁定到TSN流。
  • IP截獲,即通過配置到每個TSN流中的一組IP參數過濾所有UD/TCP套接字。
  • 鏈路層載荷,使用TSN流直接發送以太網數據包的載荷。

2.4.2.3 套接字綁定方式數據收發

發送端套接字采用如下方式與TSN流綁定后,發出的報文即可自動添加TSN需要的報文頭,例如VLAN頭,同時按照配置好的規則發送出去:

setsockopt(socketFd, SOL_SOCKET, SO_X_QBV, (void*)streamName, TSN_STREAMNAMSIZ)

注意SO_X_QBV相關功能在RTNET中不支持,需要1~3天的移植工作量。

接收端不需要進行綁定,一般只需要添加VLAN對應的多播地址和設置VALN接口,并使用setsockopt()設置SO_TIMESTAMPING/SOF_TIMESTAMPING_RX_HARDWARE控制。與此同時,還需要支持對應的SO_TIMESTAMP報文類型。 注意SO_TIMESTAMP相關功能在RTNET中不支持,需要3~5天的移植工作量。

2.4.2.4 IP截獲方式數據收發

IP截獲是動態可改變的,比套接字綁定方式更靈活:

  • 當套接字處于運行狀態時,IP截獲可以被啟用、禁用或者替換為新的過濾器。
  • IP截獲可以提前于套接字建立。

發送端可以通過2.2 TSN配置提到的JSON配置文件方式或者如下接口來設置過濾器:

tsnStreamIpDstSet()
tsnStreamIpSrcSet()
tsnStreamIpPortSet()
tsnStreamIpProtoSet()
tsnStreamIpAccessEnable()

接收端不需要使能IP截獲,一般只需要添加VLAN對應的多播地址和設置VALN接口,并使用setsockopt()設置SO_TIMESTAMPING/SOF_TIMESTAMPING_RX_HARDWARE控制。與此同時,還需要支持對應的SO_TIMESTAMP報文類型。

2.4.2.5 鏈路層載荷方式數據收發

發送端只需要調用endPoolTupleGet()從END驅動中獲取mBlk,拷貝數據,填寫以太網報文類型,并將mBlkPktHdr.streamCookie指向TSN流,就可以通過muxSend()發送,TSN需要的報文頭會自動添加。

接收端只需要添加VLAN對應的多播地址,并使用muxBind()綁定接收回調函數即可。

2.5 Frame pre-emption - 802.1Qbu

幀搶占是為了中斷長幀的傳輸以支持高優先級的 幀。一旦高優先級的流量通過,被中斷的幀的剩余部分就可以傳輸。

發送端接收端可以通過如下方式設置指定端口的優先級向量,為每個TC選擇使用高優先級還是低優先級隊列發送:

muxIoctl (pEnd, EIOCSQBU, (caddr_t)(unsigned long)tcVec) 
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容