2020年5月12日 星期二

Ethernet PHY Abstraction Layer 與驅動程式的徹底了解

PHY的抽象層

大多數網路裝置包含MAC與PHY,MAC透過一組暫存器與系統連接,MAC層通過PHY與網路的實體層連接進行通信。

PHY負責與Ethernet連接,並提供暫存器接口以允許Linux驅動程式來查詢網路的設置。PHY通常是一顆晶片,系統存取他的方式是透過MDIO BUS。下面先看一下MDIO BUS的驅動方式:

MDIO BUS

系統也許會有兩個以上的PHY,所以會有不同的MDIO BUS來連接PHY。

1. MDIO BUS必須要有的基本讀寫功能:

int write(struct mii_bus *bus, int mii_id, int regnum, u16 value);
int read(struct mii_bus *bus, int mii_id, int regnum);

mii_id是PHY BUS的地址,regnum 是暫存器編號。這兩個API都必須保證,系統處理中斷的時間,不會調用這些函數,因此要安全地BLOCK它們,等待中斷完成之後,才會被調用。

2. RESET功能是可選的。這用於使NUS回到初始化的狀態。

3. Probe功能是必要的。這函數應設置BUS驅動程序所需的任何資料結構,設定 mii_bus 資料結構,並使用 mdiobus_register向PAL註冊。同樣,mdiobus_unregister可以註銷所有功能(使用)。

4. 像任何驅動程序一樣,必須配置device_driver資料結構,並使用init exit 來註冊驅動程序。

5. BUS也必須在某處宣告為設備,並進行註冊。

mdio bus driver 範例位於 drivers/net/phy/realtek.c

RGMII 電氣特性注意事項:

RGMII 是12針的電信號接口,使用同步125 MHz 時鐘信號和幾條數據線。由於此設計決定,必須在Clock 時鐘線 (RXC 或TXC )和Data Lines 之間添加 1.5ns 至 2ns 的延遲,以使PHY(時鐘接收器)具有足夠大的建立和保持時間以正確採樣 Data Lines。

PHY Library 提供了不同類型的PHY_INTERFACE_MODE_RGMII 值,以使PHY驅動程序以及可選的MAC驅動程序實現所需的延遲。必須從PHY設備本身的角度理解 phy_interface_t 的值,這導致:

PHY_INTERFACE_MODE_RGMII:PHY本身不負責插入任何內部延遲,它假設Ethernet MAC或PCB走線會插入正確的 1.5-2 ns 延遲。

PHY_INTERFACE_MODE_RGMII_TXID:PHY應該為PHY設備處理的發送數據線(TXD [3:0])插入內部延遲。

PHY_INTERFACE_MODE_RGMII_RXID:PHY應該為由PHY設備處理的接收數據線(RXD [3:0])插入內部延遲。

PHY_INTERFACE_MODE_RGMII_ID:PHY應該為來自/去往PHY設備的發送和接收數據線插入內部延遲

出於以下原因,請盡可能使用PHY端RGMII延遲:

1. PHY可以提供 sub-nanosecond 等級的延遲(例如:0.5、1.0、1.5ns)。如果使用PCB走線,因為長度的差異,可能能以達到這種精度。

2. PHY設備可以在溫度/壓力/電壓範圍內提供恆定且可靠的延遲。

PHYLIB中的PHY設備驅動程序本質上是可重用的,能夠正確配置指定的延遲,從而使更多具有類似延遲要求的設計能夠正確運行

對於PHY無法提供此延遲但以太網MAC驅動程序能夠提供此延遲的情況,正確的phy_interface_t值應為PHY_INTERFACE_MODE_RGMII,並且應正確配置以太網MAC驅動程序以提供所需的發送和/或從PHY設備的角度接收側延遲。相反,如果以太網MAC驅動程序查看phy_interface_t值,則對於除PHY_INTERFACE_MODE_RGMII之外的任何其他模式,都應確保禁用了MAC級延遲。

如果按照RGMII標准定義,以太網MAC和PHY均不能提供所需的延遲,則可能有幾種選擇:

某些SoC可能會提供一個引腳墊/多路復用器/控制器,能夠配置一組給定的引腳強度,延遲和電壓。並且插入預期的2ns RGMII延遲可能是合適的選擇。

修改PCB設計以包括固定的延遲(例如:使用專門設計的蛇形),這可能根本不需要軟件配置。

與RGMII延遲失配的常見問題

當以太網MAC和PHY之間存在RGMII延遲不匹配時,當PHY或MAC對這些信號進行快照以將它們轉換為邏輯1或0狀態時,這很可能導致時鐘和數據線信號不穩定並重建正在發送/接收的數據。典型症狀包括:

傳輸/接收部分起作用,並且觀察到頻繁或偶發的數據包丟失。

以太網MAC可能報告一些或所有進入FCS / CRC錯誤的數據包,或者只是丟棄所有這些數據包。

切換到較低的速度(例如10 / 100Mbits /秒)可以解決問題(因為在這種情況下有足夠的建立/保持時間)。

連接到在啟動過程中的某個時候,網路驅動程序需要在PHY設備和網路設備之間建立連接。這時,PHY的總線和驅動程序都已加載完畢,因此可以進行連接了。此時,有幾種方法可以連接到PHY:

  • PHY處理所有事情,並且僅在鏈接狀態更改時才調用網路驅動程序,因此它可以做出反應。
  • PHY處理除中斷外的所有內容(通常是因為控制器具有中斷暫存器)。
  • PHY處理所有事情,但每秒檢查一次驅動程序,從而允許網路驅動程序在PAL之前先對所有更改做出反應。
  • PHY僅用做為功能庫,網路設備手動調用功能以更新狀態並配置PHY

讓PHY抽象層做所有事情

如果選擇選項1(希望每個驅動程序都可以,但對於不能驅動的驅動程序仍然有用),則連接到PHY很簡單:

首先,您需要一個函數來響應鏈接狀態的變化。此功能遵循以下協議:

static void adjust_link(struct net_device *dev);
接下來,您需要知道連接到該設備的PHY的設備名稱。名稱將看起來像“ 0:00”,其中第一個數字是總線ID,第二個數字是該總線上PHY的地址。通常,總線負責使其ID唯一。

現在,要連接,只需調用此函數:

phydev = phy_connect(dev, phy_name, &adjust_link, interface);

phydev是指向表示PHY的phy_device結構的指針。如果phy_connect成功,它將返回指針。dev在這裡是指向net_device的指針。一旦完成,該功能將啟動PHY的軟件狀態機,並為PHY的中斷註冊(如果有)。PHYdev結構將填充有關當前狀態的信息,儘管此時PHY尚未真正運行。

在調用之前,應在phydev-> dev_flags中設置PHY特定的標誌phy_connect(),以便底層PHY驅動程式可以檢查標誌並基於它們執行特定的操作。如果系統對PHY /控制器施加了硬件限制,而PHY需要了解這些限制,則這將很有用。

接口是一個u32,它指定控制器和PHY之間使用的連接類型。實例是GMII,MII,RGMII和SGMII。請參見下面的“ PHY接口模式”。有關完整列表,請參見include / linux / phy.h

現在只需確保phydev-> supported和phydev-> advertising都從它們中刪除了對您的控制器沒有意義的任何值 (10/100控制器可能已連接到具有千兆位功能的PHY,因此您需要屏蔽關閉SUPPORTED_1000baseT *)。

有關這些位域的定義,請參見 include / linux / ethtool.h。請注意,除了SUPPORTED_Pause和SUPPORTED_AsymPause位(請參見下文)外,您不應該設置任何位,否則PHY可能會進入不受支持的狀態。

最後,一旦控制器準備好處理網路流量,就可以調用phy_start(phydev)。這告訴PAL您已準備就緒,並配置PHY以連接到網路。如果網路驅動程式的MAC中斷也處理PHY狀態更改,則只需在將phydev-> irq設置為PHY_IGNORE_INTERRUPT,然後再phy_mac_interrupt()從網路驅動程式調用phy_start並使用它即可。如果不想使用中斷,請將phydev-> irq設置為PHY_POLL。 phy_start()啟用PHY中斷(如果適用)並啟動phylib狀態機。

如果要斷開網路連接(即使只是短暫斷開連接),請調用 phy_stop(phydev)。此功能還可以停止phylib狀態機並禁用PHY中斷。

PHY接口模式

phy_connect()功能係列中提供的PHY接口模式定義了PHY接口的初始操作模式。這不能保證保持恆定。有些PHY會根據協商結果動態更改其接口模式,而無需軟件交互。

一些接口模式如下所述:

PHY_INTERFACE_MODE_1000BASEX

這定義了802.3標準第36條所定義的1000BASE-X單通道Serdes鏈接。該鏈接使用10B / 8B編碼方案以1.25Gbaud的固定位速率運行,從而產生1Gbps的基礎數據速率。數據流中嵌入了一個16位控製字,用於與遠端協商雙工和暫停模式。這不包括“超頻”變體,例如2.5Gbps速度(請參閱下文)。

PHY_INTERFACE_MODE_2500BASEX

這定義了1000BASE-X的變體,其時鐘頻率比802.3標準快2.5倍,後者提供了3.125Gbaud的固定比特率。

PHY_INTERFACE_MODE_SGMII

這用於Cisco SGMII,它是對802.3標准定義的1000BASE-X的修改。SGMII鏈路由一條串行SERDES通道組成,該通道以1.25Gbaud的固定比特率運行,具有10B / 8B編碼。基本數據速率為1Gbps,通過複製每個數據符號可實現100Mbps和10Mbps的較慢速度。802.3控製字的用途是重新發送協商的速度和雙工信息到MAC,並讓MAC確認接收。這不包括“超頻”變體,例如2.5Gbps速度。

注意:在某些情況下,鏈路上的SGMII與1000BASE-X配置不匹配可以成功傳遞數據,但是16位控製字將無法正確解釋,這可能導致雙工,暫停或其他設置不匹配。這取決於MAC和/或PHY行為。

PHY_INTERFACE_MODE_10GBASER

這是IEEE 802.3條款49定義的10GBASE-R協議,可用於各種不同的介質。有關此定義,請參考IEEE標準。

注意:10GBASE-R只是可以與XFI和SFI一起使用的一種協議。XFI和SFI允許在單個SERDES通道上使用多種協議,並且通過將主機兼容板插入主機XFP / SFP連接器來定義信號的電氣特性。因此,XFI和SFI本身並不是PHY接口類型。

PHY_INTERFACE_MODE_10GKR

這是IEEE 802.3條款49定義的帶有條款73自動協商的10GBASE-R。請參考IEEE標準以獲取更多信息。

暫停Bit/流量控制

PHY不會直接參與流控制/暫停Bit,除非確保在MII_ADVERTISE中將SUPPORTED_Pause和SUPPORTED_AsymPause位設置為向鏈路夥伴,表明以太網MAC控制器支持這種情況。由於流控制/暫停幀的生成涉及以太網MAC驅動程式,因此建議該驅動程式通過相應地設置SUPPORTED_Pause和SUPPORTED_AsymPause位來適當地指示廣告和對此類功能的支持。可以在 phy_connect() 實現 ethtool :: set_pauseparam功能之前或之後和/或作為結果。

在PAL上保持關閉選項卡

PAL的內置狀態機可能需要一點幫助,以使您的網路設備和PHY正確同步。如果是這樣,您可以在連接到PHY時註冊一個輔助函數,該函數將在狀態機對任何更改做出反應之前每秒調用一次。為此,您需要手動調用phy_attach()和phy_prepare_link(),然後phy_start_machine()在第二個參數設置為指向您的特殊處理程序的情況下進行調用 。

當前尚無有關如何使用此功能的示例,並且由於作者沒有使用該功能的驅動程式(它們全部使用選項1),因此對其進行的測試受到限制。因此,警告皇帝。

自己動手做

PAL的內置狀態機極有可能無法跟踪PHY與您的網路設備之間的複雜交互。如果是這樣,您可以直接調用phy_attach(),而不能調用phy_start_machine或 phy_prepare_link()。這將意味著phydev-> state完全由您處理(phy_start和phy_stop在某些狀態之間切換,因此您可能需要避免它們)。

已經做出努力以確保可以在不運行狀態機的情況下訪問有用的功能,並且這些功能中的大多數是未與復雜狀態機交互的功能的後繼功能。但是,到目前為止,在沒有狀態機的情況下,到目前為止尚未做出任何努力來測試運行情況,因此請謹慎嘗試。

以下是這些功能的簡要概述:

int phy_read(struct phy_device *phydev, u16 regnum);
int phy_write(struct phy_device *phydev, u16 regnum, u16 val);

簡單的讀/寫原語。它們調用總線的讀/寫功能指針。

void phy_print_status(struct phy_device *phydev);

方便的功能可以整齊地打印PHY狀態。

void phy_request_interrupt(struct phy_device *phydev);

請求PHY中斷的IRQ。

struct phy_device * phy_attach(struct net_device *dev, const char *phy_id,
                               phy_interface_t interface);

將網路設備連接到特定的PHY,如果在總線初始化期間未找到PHY,則將PHY綁定到通用驅動程式。

int phy_start_aneg(struct phy_device *phydev);

使用phydev結構內部的變量,可以配置廣告並重置自動協商,或者禁用自動協商並配置強制設置。

static inline int phy_read_status(struct phy_device *phydev);

用有關PHY中當前設置的最新信息填充phydev結構。

int phy_ethtool_ksettings_set(struct phy_device *phydev,
                                                 const struct ethtool_link_ksettings *cmd);

Ethtool便利功能。

int phy_mii_ioctl(struct phy_device *phydev,
                  struct mii_ioctl_data *mii_data, int cmd);

MII ioctl。請注意,如果您寫入諸如BMCR,BMSR,ADVERTISE等暫存器,則此功能將完全破壞狀態機。最好僅使用此功能來寫入非標準的暫存器,並且不要啟動重新協商。

PHY Device Driver (設備驅動程序)

借助PHY抽象層,添加對新PHY的支持非常容易。有時候,根本沒有工作要做。

Generic PHY driver

這是最常用的驅動程式。

編寫PHY驅動程式

如果確實需要編寫PHY驅動程式,可以參考一下的說明。

首先要在BUS初始化的時候,先讀取裝置的UID (儲存在暫存器2和3之中),然後通過將其與每個驅動程式的 phy_id 字段與每個驅動程式的phy_id_mask字段,進行“與”操作進行比較來完成的。另外,它需要一個名稱。以下是一個例子:

static struct phy_driver dm9161_driver = {
      .phy_id              =  0x0181b880,
      .name                 = "Davicom DM9161E",
      .phy_id_mask    = 0x0ffffff0,
      ...
}

接下來,您需要指定PHY設備和驅動程式支持哪些功能 ( speed, duplex, autoreg )。大多數PHY支持 PHY_BASIC_FEATURES,但是您可以在 #include/mii.h中查找其他功能。

每個驅動程式由許多函數 pointer 組成,這些point定義於  #include/linux/phy.h 檔案中struct phy_driver。

/*
struct phy_driver: Driver structure for a particular PHY type driver_data: static driver data
phy_id: The result of reading the UID registers of this PHY type, and ANDing them with the phy_id_mask.  This driver only works for PHYs with IDs which match this field name: The friendly name of this PHY type phy_id_mask: Defines the important bits of the phy_id features:
A mandatory list of features (speed, duplex, etc) supported by this PHY flags: A bitfield defining certain other features this PHY supports (like interrupts)

All functions are optional. If config_aneg or read_status are not implemented, the phy core uses the genphy versions. Note that none of these functions should be called from interrupt time. The goal is for the bus read/write functions to be able to block when the bus transaction is happening, and be freed up by an interrupt (The MPC85xx has this ability, though it is not currently supported in the driver).
 */

struct phy_driver {
struct mdio_driver_common mdiodrv;
u32                                         phy_id;
char                                        *name;
u32                                         phy_id_mask;
const unsigned long               * const features;
u32                                         flags;
const void                              *driver_data;

/*
  * Called to issue a PHY software reset
*/
int (*soft_reset)(struct phy_device *phydev);

/*
  * Called to initialize the PHY, including after a reset
*/
int (*config_init)(struct phy_device *phydev);

/*
* Called during discovery.  Used to set up device-specific structures, if any
*/
int (*probe)(struct phy_device *phydev);

/*
* Probe the hardware to determine what abilities it has.  Should only set phydev->supported.
*/
int (*get_features)(struct phy_device *phydev);

/* PHY Power Management */
int (*suspend)(struct phy_device *phydev);
int (*resume)(struct phy_device *phydev);

/*
* Configures the advertisement and resets autonegotiation if phydev->autoneg is on,
* forces the speed to the current settings in phydev,  if phydev->autoneg is off
*/
int (*config_aneg)(struct phy_device *phydev);

/* Determines the auto negotiation result */
int (*aneg_done)(struct phy_device *phydev);

/* Determines the negotiated speed and duplex */
int (*read_status)(struct phy_device *phydev);

/* Clears any pending interrupts */
int (*ack_interrupt)(struct phy_device *phydev);

/* Enables or disables interrupts */
int (*config_intr)(struct phy_device *phydev);

/*
* Checks if the PHY generated an interrupt. For multi-PHY devices with shared PHY
         * interrupt pin. Set interrupt bits have to be cleared.
*/
int (*did_interrupt)(struct phy_device *phydev);

/* Override default interrupt handling */
int (*handle_interrupt)(struct phy_device *phydev);

/* Clears up any memory if needed */
void (*remove)(struct phy_device *phydev);

/* Returns true if this is a suitable driver for the given phydev.
            If NULL, matching is based on phy_id and phy_id_mask.
*/
int (*match_phy_device)(struct phy_device *phydev);

/* Some devices (e.g. qnap TS-119P II) require PHY register changes to
* enable Wake on LAN, so set_wol is provided to be called in the
* ethernet driver's set_wol function.
        */
int (*set_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);

/* See set_wol, but for checking whether Wake on LAN is enabled. */
void (*get_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);

/*
* Called to inform a PHY device driver when the core is about to
* change the link state. This callback is supposed to be used as
* fixup hook for drivers that need to take action when the link
* state changes. Drivers are by no means allowed to mess with the
* PHY device structure in their implementations.
*/
void (*link_change_notify)(struct phy_device *dev);

/*
* Phy specific driver override for reading a MMD register.
* This function is optional for PHY specific drivers.  When
* not provided, the default MMD read function will be used
* by phy_read_mmd(), which will use either a direct read for
* Clause 45 PHYs or an indirect read for Clause 22 PHYs.
*  devnum is the MMD device number within the PHY device,
*  regnum is the register within the selected MMD device.
*/
int (*read_mmd)(struct phy_device *dev, int devnum, u16 regnum);

/*
* Phy specific driver override for writing a MMD register.
* This function is optional for PHY specific drivers.  When
* not provided, the default MMD write function will be used
* by phy_write_mmd(), which will use either a direct write for
* Clause 45 PHYs, or an indirect write for Clause 22 PHYs.
*  devnum is the MMD device number within the PHY device,
*  regnum is the register within the selected MMD device.
*  val is the value to be written.
*/
int (*write_mmd)(struct phy_device *dev, int devnum, u16 regnum,
u16 val);

int (*read_page)(struct phy_device *dev);
int (*write_page)(struct phy_device *dev, int page);

/* Get the size and type of the eeprom contained within a plug-in
* module */
int (*module_info)(struct phy_device *dev,
   struct ethtool_modinfo *modinfo);

/* Get the eeprom information from the plug-in module */
int (*module_eeprom)(struct phy_device *dev,
     struct ethtool_eeprom *ee, u8 *data);

/* Get statistics from the phy using ethtool */
int (*get_sset_count)(struct phy_device *dev);
void (*get_strings)(struct phy_device *dev, u8 *data);
void (*get_stats)(struct phy_device *dev,
  struct ethtool_stats *stats, u64 *data);

/* Get and Set PHY tunables */
int (*get_tunable)(struct phy_device *dev,
   struct ethtool_tunable *tuna, void *data);
int (*set_tunable)(struct phy_device *dev,
    struct ethtool_tunable *tuna,
    const void *data);
int (*set_loopback)(struct phy_device *dev, bool enable);
};

其中,僅config_aneg 和 read_status 一定要寫,其餘都是Option的。另外,如果可能的話,最好使用這兩個函數的通用 PHY Driver ( genphy_read_status() 和 genphy_config_aneg()。

如果無法做到這一點,則可能只需要在調用這些函數之前和之後執行一些操作,因此您的函數將包裝通用函數。

/net /phy/目錄當中,有一堆已經寫好的例子,基本上已將涵蓋市面上的主流產品。

PHY的MMD暫存器訪問,在預設情況下由PAL框架處理,但是如果需要,可以由特定的PHY驅動程式取代。事實上MMD PHY暫存器的定義已經由IEEE加以標準化準化了。

板子硬體的配合修復程序

有時後,主晶片與與PHY之間由些特殊狀況需要處理。

例如,更改PHY時鐘輸入的位置,或添加延遲以解決數據路徑中的延遲問題。為了支持這種意外情況,PHY層允許平台代碼註冊在啟動PHY (或隨後重置)時運行的修補程序。

當PHY層啟動PHY時,它會檢查是否有為其註冊的修復程序,基於UID(包含在PHY設備的phy_id字段中)和總線標識符(包含在phydev-> dev.bus_id中)進行匹配。兩者必須匹配,但是兩個常量PHY_ANY_ID和PHY_ANY_UID作為通配符分別提供給總線ID和UID。

找到匹配項後,PHY層將調用與修復程序關聯的運行功能。該函數被傳遞一個指向感興趣的phy_device的指針。因此,它應僅在該PHY上運行。

平台代碼可以使用以下方法註冊修正phy_register_fixup():

int phy_register_fixup(const char *phy_id,
                                     u32 phy_uid, u32 phy_uid_mask,
                                     int (*run)(struct phy_device *));

或使用以下兩個Register之一:phy_register_fixup_for_uid()和phy_register_fixup_for_id();

int phy_register_fixup_for_uid (u32 phy_uid, u32 phy_uid_mask,
                                                    int (*run)(struct phy_device *));

int phy_register_fixup_for_id (const char *phy_id,
                                                  int (*run)(struct phy_device *));

Register設置兩個匹配條件之一,並設置另一個匹配任何條件。

當phy_register_fixup()在Module加載時,調用 phy_register_fixup_for_uid() / phy_register_fixup_for_id()時,Module需要取消註冊修正,並在卸載時釋放已分配的記憶體。

在卸載Module之前,會調用以下功能之一:

int phy_unregister_fixup(const char *phy_id, u32 phy_uid, u32 phy_uid_mask);
int phy_unregister_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask);
int phy_register_fixup_for_id(const char *phy_id);


沒有留言:

張貼留言