Commit ccbc6dac authored by David S. Miller's avatar David S. Miller

Merge branch 'enetc-Add-adaptive-interrupt-coalescing'

Claudiu Manoil says:

====================
enetc: Add adaptive interrupt coalescing

Apart from some related cleanup patches, this set
introduces in a straightforward way the support needed
to enable and configure interrupt coalescing for ENETC.

Patch 5 introduces the support needed for configuring the
interrupt coalescing parameters and for switching between
moderated (int. coalescing) and per-packet interrupt modes.
When interrupt coalescing is enabled the Rx/Tx time
thresholds are configurable, packet thresholds are fixed.
To make this work reliably, patch 5 uses the traffic
pause procedure introduced in patch 2.

Patch 6 adds DIM (Dynamic Interrupt Moderation) to implement
adaptive coalescing based on time thresholds, for the Rx 'channel'.
On the Tx side a default optimal value is used instead, optimized for
TCP traffic over 1G and 2.5G links.  This default 'optimal' value can
be overridden anytime via 'ethtool -C tx-usecs'.

netperf -t TCP_MAERTS measurements show a significant CPU load
reduction correlated w/ reduced interrupt rates. For the
measurement results refer to the comments in patch 6.

v2: Replaced Tx DIM with predefined optimal value, giving
better results. This was also suggested by Jakub (cc).
Switched order of patches 4 and 5, for better grouping.

v3: minor cleanup/improvements
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents c17e3178 ae0e6a5d
......@@ -4,6 +4,7 @@ config FSL_ENETC
depends on PCI && PCI_MSI
select FSL_ENETC_MDIO
select PHYLIB
select DIMLIB
help
This driver supports NXP ENETC gigabit ethernet controller PCIe
physical function (PF) devices, managing ENETC Ports at a privileged
......@@ -15,6 +16,7 @@ config FSL_ENETC_VF
tristate "ENETC VF driver"
depends on PCI && PCI_MSI
select PHYLIB
select DIMLIB
help
This driver supports NXP ENETC gigabit ethernet controller PCIe
virtual function (VF) devices enabled by the ENETC PF driver.
......
......@@ -265,6 +265,7 @@ static irqreturn_t enetc_msix(int irq, void *data)
/* disable interrupts */
enetc_wr_reg(v->rbier, 0);
enetc_wr_reg(v->ricr1, v->rx_ictt);
for_each_set_bit(i, &v->tx_rings_map, ENETC_MAX_NUM_TXQS)
enetc_wr_reg(v->tbier_base + ENETC_BDR_OFF(i), 0);
......@@ -278,6 +279,34 @@ static bool enetc_clean_tx_ring(struct enetc_bdr *tx_ring, int napi_budget);
static int enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
struct napi_struct *napi, int work_limit);
static void enetc_rx_dim_work(struct work_struct *w)
{
struct dim *dim = container_of(w, struct dim, work);
struct dim_cq_moder moder =
net_dim_get_rx_moderation(dim->mode, dim->profile_ix);
struct enetc_int_vector *v =
container_of(dim, struct enetc_int_vector, rx_dim);
v->rx_ictt = enetc_usecs_to_cycles(moder.usec);
dim->state = DIM_START_MEASURE;
}
static void enetc_rx_net_dim(struct enetc_int_vector *v)
{
struct dim_sample dim_sample;
v->comp_cnt++;
if (!v->rx_napi_work)
return;
dim_update_sample(v->comp_cnt,
v->rx_ring.stats.packets,
v->rx_ring.stats.bytes,
&dim_sample);
net_dim(&v->rx_dim, dim_sample);
}
static int enetc_poll(struct napi_struct *napi, int budget)
{
struct enetc_int_vector
......@@ -293,12 +322,19 @@ static int enetc_poll(struct napi_struct *napi, int budget)
work_done = enetc_clean_rx_ring(&v->rx_ring, napi, budget);
if (work_done == budget)
complete = false;
if (work_done)
v->rx_napi_work = true;
if (!complete)
return budget;
napi_complete_done(napi, work_done);
if (likely(v->rx_dim_en))
enetc_rx_net_dim(v);
v->rx_napi_work = false;
/* enable interrupts */
enetc_wr_reg(v->rbier, ENETC_RBIER_RXTIE);
......@@ -1064,8 +1100,8 @@ void enetc_init_si_rings_params(struct enetc_ndev_priv *priv)
struct enetc_si *si = priv->si;
int cpus = num_online_cpus();
priv->tx_bd_count = ENETC_BDR_DEFAULT_SIZE;
priv->rx_bd_count = ENETC_BDR_DEFAULT_SIZE;
priv->tx_bd_count = ENETC_TX_RING_DEFAULT_SIZE;
priv->rx_bd_count = ENETC_RX_RING_DEFAULT_SIZE;
/* Enable all available TX rings in order to configure as many
* priorities as possible, when needed.
......@@ -1074,6 +1110,8 @@ void enetc_init_si_rings_params(struct enetc_ndev_priv *priv)
priv->num_rx_rings = min_t(int, cpus, si->num_rx_rings);
priv->num_tx_rings = si->num_tx_rings;
priv->bdr_int_num = cpus;
priv->ic_mode = ENETC_IC_RX_ADAPTIVE | ENETC_IC_TX_MANUAL;
priv->tx_ictt = ENETC_TXIC_TIMETHR;
/* SI specific */
si->cbd_ring.bd_count = ENETC_CBDR_DEFAULT_SIZE;
......@@ -1140,7 +1178,7 @@ static void enetc_setup_txbdr(struct enetc_hw *hw, struct enetc_bdr *tx_ring)
tx_ring->next_to_clean = enetc_txbdr_rd(hw, idx, ENETC_TBCIR);
/* enable Tx ints by setting pkt thr to 1 */
enetc_txbdr_wr(hw, idx, ENETC_TBICIR0, ENETC_TBICIR0_ICEN | 0x1);
enetc_txbdr_wr(hw, idx, ENETC_TBICR0, ENETC_TBICR0_ICEN | 0x1);
tbmr = ENETC_TBMR_EN;
if (tx_ring->ndev->features & NETIF_F_HW_VLAN_CTAG_TX)
......@@ -1174,7 +1212,7 @@ static void enetc_setup_rxbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring)
enetc_rxbdr_wr(hw, idx, ENETC_RBPIR, 0);
/* enable Rx ints by setting pkt thr to 1 */
enetc_rxbdr_wr(hw, idx, ENETC_RBICIR0, ENETC_RBICIR0_ICEN | 0x1);
enetc_rxbdr_wr(hw, idx, ENETC_RBICR0, ENETC_RBICR0_ICEN | 0x1);
rbmr = ENETC_RBMR_EN;
......@@ -1264,9 +1302,11 @@ static int enetc_setup_irqs(struct enetc_ndev_priv *priv)
dev_err(priv->dev, "request_irq() failed!\n");
goto irq_err;
}
disable_irq(irq);
v->tbier_base = hw->reg + ENETC_BDR(TX, 0, ENETC_TBIER);
v->rbier = hw->reg + ENETC_BDR(RX, i, ENETC_RBIER);
v->ricr1 = hw->reg + ENETC_BDR(RX, i, ENETC_RBICR1);
enetc_wr(hw, ENETC_SIMSIRRV(i), entry);
......@@ -1306,23 +1346,42 @@ static void enetc_free_irqs(struct enetc_ndev_priv *priv)
}
}
static void enetc_enable_interrupts(struct enetc_ndev_priv *priv)
static void enetc_setup_interrupts(struct enetc_ndev_priv *priv)
{
struct enetc_hw *hw = &priv->si->hw;
u32 icpt, ictt;
int i;
/* enable Tx & Rx event indication */
if (priv->ic_mode &
(ENETC_IC_RX_MANUAL | ENETC_IC_RX_ADAPTIVE)) {
icpt = ENETC_RBICR0_SET_ICPT(ENETC_RXIC_PKTTHR);
/* init to non-0 minimum, will be adjusted later */
ictt = 0x1;
} else {
icpt = 0x1; /* enable Rx ints by setting pkt thr to 1 */
ictt = 0;
}
for (i = 0; i < priv->num_rx_rings; i++) {
enetc_rxbdr_wr(&priv->si->hw, i,
ENETC_RBIER, ENETC_RBIER_RXTIE);
enetc_rxbdr_wr(hw, i, ENETC_RBICR1, ictt);
enetc_rxbdr_wr(hw, i, ENETC_RBICR0, ENETC_RBICR0_ICEN | icpt);
enetc_rxbdr_wr(hw, i, ENETC_RBIER, ENETC_RBIER_RXTIE);
}
if (priv->ic_mode & ENETC_IC_TX_MANUAL)
icpt = ENETC_TBICR0_SET_ICPT(ENETC_TXIC_PKTTHR);
else
icpt = 0x1; /* enable Tx ints by setting pkt thr to 1 */
for (i = 0; i < priv->num_tx_rings; i++) {
enetc_txbdr_wr(&priv->si->hw, i,
ENETC_TBIER, ENETC_TBIER_TXTIE);
enetc_txbdr_wr(hw, i, ENETC_TBICR1, priv->tx_ictt);
enetc_txbdr_wr(hw, i, ENETC_TBICR0, ENETC_TBICR0_ICEN | icpt);
enetc_txbdr_wr(hw, i, ENETC_TBIER, ENETC_TBIER_TXTIE);
}
}
static void enetc_disable_interrupts(struct enetc_ndev_priv *priv)
static void enetc_clear_interrupts(struct enetc_ndev_priv *priv)
{
int i;
......@@ -1369,10 +1428,33 @@ static int enetc_phy_connect(struct net_device *ndev)
return 0;
}
void enetc_start(struct net_device *ndev)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
int i;
enetc_setup_interrupts(priv);
for (i = 0; i < priv->bdr_int_num; i++) {
int irq = pci_irq_vector(priv->si->pdev,
ENETC_BDR_INT_BASE_IDX + i);
napi_enable(&priv->int_vector[i]->napi);
enable_irq(irq);
}
if (ndev->phydev)
phy_start(ndev->phydev);
else
netif_carrier_on(ndev);
netif_tx_start_all_queues(ndev);
}
int enetc_open(struct net_device *ndev)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
int i, err;
int err;
err = enetc_setup_irqs(priv);
if (err)
......@@ -1390,8 +1472,6 @@ int enetc_open(struct net_device *ndev)
if (err)
goto err_alloc_rx;
enetc_setup_bdrs(priv);
err = netif_set_real_num_tx_queues(ndev, priv->num_tx_rings);
if (err)
goto err_set_queues;
......@@ -1400,17 +1480,8 @@ int enetc_open(struct net_device *ndev)
if (err)
goto err_set_queues;
for (i = 0; i < priv->bdr_int_num; i++)
napi_enable(&priv->int_vector[i]->napi);
enetc_enable_interrupts(priv);
if (ndev->phydev)
phy_start(ndev->phydev);
else
netif_carrier_on(ndev);
netif_tx_start_all_queues(ndev);
enetc_setup_bdrs(priv);
enetc_start(ndev);
return 0;
......@@ -1427,28 +1498,39 @@ int enetc_open(struct net_device *ndev)
return err;
}
int enetc_close(struct net_device *ndev)
void enetc_stop(struct net_device *ndev)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
int i;
netif_tx_stop_all_queues(ndev);
if (ndev->phydev) {
phy_stop(ndev->phydev);
phy_disconnect(ndev->phydev);
} else {
netif_carrier_off(ndev);
}
for (i = 0; i < priv->bdr_int_num; i++) {
int irq = pci_irq_vector(priv->si->pdev,
ENETC_BDR_INT_BASE_IDX + i);
disable_irq(irq);
napi_synchronize(&priv->int_vector[i]->napi);
napi_disable(&priv->int_vector[i]->napi);
}
enetc_disable_interrupts(priv);
if (ndev->phydev)
phy_stop(ndev->phydev);
else
netif_carrier_off(ndev);
enetc_clear_interrupts(priv);
}
int enetc_close(struct net_device *ndev)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
enetc_stop(ndev);
enetc_clear_bdrs(priv);
if (ndev->phydev)
phy_disconnect(ndev->phydev);
enetc_free_rxtx_rings(priv);
enetc_free_rx_resources(priv);
enetc_free_tx_resources(priv);
......@@ -1742,6 +1824,12 @@ int enetc_alloc_msix(struct enetc_ndev_priv *priv)
priv->int_vector[i] = v;
/* init defaults for adaptive IC */
if (priv->ic_mode & ENETC_IC_RX_ADAPTIVE) {
v->rx_ictt = 0x1;
v->rx_dim_en = true;
}
INIT_WORK(&v->rx_dim.work, enetc_rx_dim_work);
netif_napi_add(priv->ndev, &v->napi, enetc_poll,
NAPI_POLL_WEIGHT);
v->count_tx_rings = v_tx_rings;
......@@ -1777,6 +1865,7 @@ int enetc_alloc_msix(struct enetc_ndev_priv *priv)
fail:
while (i--) {
netif_napi_del(&priv->int_vector[i]->napi);
cancel_work_sync(&priv->int_vector[i]->rx_dim.work);
kfree(priv->int_vector[i]);
}
......@@ -1793,6 +1882,7 @@ void enetc_free_msix(struct enetc_ndev_priv *priv)
struct enetc_int_vector *v = priv->int_vector[i];
netif_napi_del(&v->napi);
cancel_work_sync(&v->rx_dim.work);
}
for (i = 0; i < priv->num_rx_rings; i++)
......
......@@ -10,6 +10,7 @@
#include <linux/ethtool.h>
#include <linux/if_vlan.h>
#include <linux/phy.h>
#include <linux/dim.h>
#include "enetc_hw.h"
......@@ -44,8 +45,9 @@ struct enetc_ring_stats {
unsigned int rx_alloc_errs;
};
#define ENETC_BDR_DEFAULT_SIZE 1024
#define ENETC_DEFAULT_TX_WORK 256
#define ENETC_RX_RING_DEFAULT_SIZE 512
#define ENETC_TX_RING_DEFAULT_SIZE 256
#define ENETC_DEFAULT_TX_WORK (ENETC_TX_RING_DEFAULT_SIZE / 2)
struct enetc_bdr {
struct device *dev; /* for DMA mapping */
......@@ -189,14 +191,19 @@ static inline bool enetc_si_is_pf(struct enetc_si *si)
struct enetc_int_vector {
void __iomem *rbier;
void __iomem *tbier_base;
void __iomem *ricr1;
unsigned long tx_rings_map;
int count_tx_rings;
struct napi_struct napi;
u32 rx_ictt;
u16 comp_cnt;
bool rx_dim_en, rx_napi_work;
struct napi_struct napi ____cacheline_aligned_in_smp;
struct dim rx_dim ____cacheline_aligned_in_smp;
char name[ENETC_INT_NAME_MAX];
struct enetc_bdr rx_ring ____cacheline_aligned_in_smp;
struct enetc_bdr rx_ring;
struct enetc_bdr tx_ring[];
};
} ____cacheline_aligned_in_smp;
struct enetc_cls_rule {
struct ethtool_rx_flow_spec fs;
......@@ -220,6 +227,21 @@ enum enetc_active_offloads {
ENETC_F_QCI = BIT(3),
};
/* interrupt coalescing modes */
enum enetc_ic_mode {
/* one interrupt per frame */
ENETC_IC_NONE = 0,
/* activated when int coalescing time is set to a non-0 value */
ENETC_IC_RX_MANUAL = BIT(0),
ENETC_IC_TX_MANUAL = BIT(1),
/* use dynamic interrupt moderation */
ENETC_IC_RX_ADAPTIVE = BIT(2),
};
#define ENETC_RXIC_PKTTHR min_t(u32, 256, ENETC_RX_RING_DEFAULT_SIZE / 2)
#define ENETC_TXIC_PKTTHR min_t(u32, 128, ENETC_TX_RING_DEFAULT_SIZE / 2)
#define ENETC_TXIC_TIMETHR enetc_usecs_to_cycles(600)
struct enetc_ndev_priv {
struct net_device *ndev;
struct device *dev; /* dma-mapping device */
......@@ -244,6 +266,8 @@ struct enetc_ndev_priv {
struct device_node *phy_node;
phy_interface_t if_mode;
int ic_mode;
u32 tx_ictt;
};
/* Messaging */
......@@ -273,6 +297,8 @@ void enetc_free_si_resources(struct enetc_ndev_priv *priv);
int enetc_open(struct net_device *ndev);
int enetc_close(struct net_device *ndev);
void enetc_start(struct net_device *ndev);
void enetc_stop(struct net_device *ndev);
netdev_tx_t enetc_xmit(struct sk_buff *skb, struct net_device *ndev);
struct net_device_stats *enetc_get_stats(struct net_device *ndev);
int enetc_set_features(struct net_device *ndev,
......
......@@ -14,12 +14,14 @@ static const u32 enetc_si_regs[] = {
static const u32 enetc_txbdr_regs[] = {
ENETC_TBMR, ENETC_TBSR, ENETC_TBBAR0, ENETC_TBBAR1,
ENETC_TBPIR, ENETC_TBCIR, ENETC_TBLENR, ENETC_TBIER
ENETC_TBPIR, ENETC_TBCIR, ENETC_TBLENR, ENETC_TBIER, ENETC_TBICR0,
ENETC_TBICR1
};
static const u32 enetc_rxbdr_regs[] = {
ENETC_RBMR, ENETC_RBSR, ENETC_RBBSR, ENETC_RBCIR, ENETC_RBBAR0,
ENETC_RBBAR1, ENETC_RBPIR, ENETC_RBLENR, ENETC_RBICIR0, ENETC_RBIER
ENETC_RBBAR1, ENETC_RBPIR, ENETC_RBLENR, ENETC_RBIER, ENETC_RBICR0,
ENETC_RBICR1
};
static const u32 enetc_port_regs[] = {
......@@ -561,6 +563,74 @@ static void enetc_get_ringparam(struct net_device *ndev,
}
}
static int enetc_get_coalesce(struct net_device *ndev,
struct ethtool_coalesce *ic)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
struct enetc_int_vector *v = priv->int_vector[0];
ic->tx_coalesce_usecs = enetc_cycles_to_usecs(priv->tx_ictt);
ic->rx_coalesce_usecs = enetc_cycles_to_usecs(v->rx_ictt);
ic->tx_max_coalesced_frames = ENETC_TXIC_PKTTHR;
ic->rx_max_coalesced_frames = ENETC_RXIC_PKTTHR;
ic->use_adaptive_rx_coalesce = priv->ic_mode & ENETC_IC_RX_ADAPTIVE;
return 0;
}
static int enetc_set_coalesce(struct net_device *ndev,
struct ethtool_coalesce *ic)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
u32 rx_ictt, tx_ictt;
int i, ic_mode;
bool changed;
tx_ictt = enetc_usecs_to_cycles(ic->tx_coalesce_usecs);
rx_ictt = enetc_usecs_to_cycles(ic->rx_coalesce_usecs);
if (ic->rx_max_coalesced_frames != ENETC_RXIC_PKTTHR)
return -EOPNOTSUPP;
if (ic->tx_max_coalesced_frames != ENETC_TXIC_PKTTHR)
return -EOPNOTSUPP;
ic_mode = ENETC_IC_NONE;
if (ic->use_adaptive_rx_coalesce) {
ic_mode |= ENETC_IC_RX_ADAPTIVE;
rx_ictt = 0x1;
} else {
ic_mode |= rx_ictt ? ENETC_IC_RX_MANUAL : 0;
}
ic_mode |= tx_ictt ? ENETC_IC_TX_MANUAL : 0;
/* commit the settings */
changed = (ic_mode != priv->ic_mode) || (priv->tx_ictt != tx_ictt);
priv->ic_mode = ic_mode;
priv->tx_ictt = tx_ictt;
for (i = 0; i < priv->bdr_int_num; i++) {
struct enetc_int_vector *v = priv->int_vector[i];
v->rx_ictt = rx_ictt;
v->rx_dim_en = !!(ic_mode & ENETC_IC_RX_ADAPTIVE);
}
if (netif_running(ndev) && changed) {
/* reconfigure the operation mode of h/w interrupts,
* traffic needs to be paused in the process
*/
enetc_stop(ndev);
enetc_start(ndev);
}
return 0;
}
static int enetc_get_ts_info(struct net_device *ndev,
struct ethtool_ts_info *info)
{
......@@ -617,6 +687,9 @@ static int enetc_set_wol(struct net_device *dev,
}
static const struct ethtool_ops enetc_pf_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES |
ETHTOOL_COALESCE_USE_ADAPTIVE_RX,
.get_regs_len = enetc_get_reglen,
.get_regs = enetc_get_regs,
.get_sset_count = enetc_get_sset_count,
......@@ -629,6 +702,8 @@ static const struct ethtool_ops enetc_pf_ethtool_ops = {
.get_rxfh = enetc_get_rxfh,
.set_rxfh = enetc_set_rxfh,
.get_ringparam = enetc_get_ringparam,
.get_coalesce = enetc_get_coalesce,
.set_coalesce = enetc_set_coalesce,
.get_link_ksettings = phy_ethtool_get_link_ksettings,
.set_link_ksettings = phy_ethtool_set_link_ksettings,
.get_link = ethtool_op_get_link,
......@@ -638,6 +713,9 @@ static const struct ethtool_ops enetc_pf_ethtool_ops = {
};
static const struct ethtool_ops enetc_vf_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES |
ETHTOOL_COALESCE_USE_ADAPTIVE_RX,
.get_regs_len = enetc_get_reglen,
.get_regs = enetc_get_regs,
.get_sset_count = enetc_get_sset_count,
......@@ -649,6 +727,8 @@ static const struct ethtool_ops enetc_vf_ethtool_ops = {
.get_rxfh = enetc_get_rxfh,
.set_rxfh = enetc_set_rxfh,
.get_ringparam = enetc_get_ringparam,
.get_coalesce = enetc_get_coalesce,
.set_coalesce = enetc_set_coalesce,
.get_link = ethtool_op_get_link,
.get_ts_info = enetc_get_ts_info,
};
......
......@@ -121,8 +121,11 @@ enum enetc_bdr_type {TX, RX};
#define ENETC_RBIER 0xa0
#define ENETC_RBIER_RXTIE BIT(0)
#define ENETC_RBIDR 0xa4
#define ENETC_RBICIR0 0xa8
#define ENETC_RBICIR0_ICEN BIT(31)
#define ENETC_RBICR0 0xa8
#define ENETC_RBICR0_ICEN BIT(31)
#define ENETC_RBICR0_ICPT_MASK 0x1ff
#define ENETC_RBICR0_SET_ICPT(n) ((n) & ENETC_RBICR0_ICPT_MASK)
#define ENETC_RBICR1 0xac
/* TX BDR reg offsets */
#define ENETC_TBMR 0
......@@ -141,8 +144,11 @@ enum enetc_bdr_type {TX, RX};
#define ENETC_TBIER 0xa0
#define ENETC_TBIER_TXTIE BIT(0)
#define ENETC_TBIDR 0xa4
#define ENETC_TBICIR0 0xa8
#define ENETC_TBICIR0_ICEN BIT(31)
#define ENETC_TBICR0 0xa8
#define ENETC_TBICR0_ICEN BIT(31)
#define ENETC_TBICR0_ICPT_MASK 0xf
#define ENETC_TBICR0_SET_ICPT(n) ((ilog2(n) + 1) & ENETC_TBICR0_ICPT_MASK)
#define ENETC_TBICR1 0xac
#define ENETC_RTBLENR_LEN(n) ((n) & ~0x7)
......@@ -787,6 +793,15 @@ struct enetc_cbd {
};
#define ENETC_CLK 400000000ULL
static inline u32 enetc_cycles_to_usecs(u32 cycles)
{
return (u32)div_u64(cycles * 1000000ULL, ENETC_CLK);
}
static inline u32 enetc_usecs_to_cycles(u32 usecs)
{
return (u32)div_u64(usecs * ENETC_CLK, 1000000ULL);
}
/* port time gating control register */
#define ENETC_QBV_PTGCR_OFFSET 0x11a00
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment