相关开发命令
kldstat
kldload ./xxx.ko
kldunload xxx
pciconf -l
dmesg
ifconfig
//查看CPU核
cat /var/run/dmesg.boot |grep CPU
重要结构体间的关系(2022/7/13)
struct device_method {
const char *desc;
void *const func;
};
struct driver {
const char *name;
const struct device_method *methods;
uint32_t size;
};
struct if_shared_ctx {
//...
driver_t *isc_driver;
//...
};
重要结构体的内存申请:
(1)if_shared_ctx 变量定义的形式。
(2)device_t dev 上电时枚举
(3)if_ctx_t ctx iflib_device_register函数里malloc申请
(4)if_t ifp iflib_register函数里if_alloc申请(类似于netdevice)
(5)if_softc_ctx 这个是一个变量,在if_ctx_t 成员里,而bnxt_softc里面的成员if_softc_ctx是一个指针,指向的是if_ctx_t 里面的if_softc_ctx。。。。。有点绕。
(6)bnxt_softc iflib_device_register函数里sc = malloc(sctx->isc_driver->size, M_IFLIB, M_WAITOK|M_ZERO);
申请的,注意size。
这几个结构体之间的关系是,ifp作为ctx(if_ctx_t)的成员,而if_ctx_t,if_shared_ctx,device_t,if_softc_ctx,又作为bnxt_softc的成员。
if_shared_ctx,dev,ifp,bnxt_softc都作为ctx的成员。
而从if_ctx_t 的角度看,应该是所有的成员都是包含在if_ctx_t 里面的。
关于static device_method_t bnxt_iflib_methods结构里成员函数的调用问题(2022/7/13)
static int
iflib_register(if_ctx_t ctx)
{
//...
driver_t *driver = sctx->isc_driver;
/*
* Initialize our context's device specific methods
*/
kobj_init((kobj_t) ctx, (kobj_class_t) driver);
kobj_class_compile((kobj_class_t) driver);
//...
}
应该所有的成员都会调用一遍,但是调用顺序和定义顺序不是一致的,具体怎么调用的要对kobj这个结构分析。
pci_mapping与pci_mapping_free(2022/7/13)
在attach_pre函数里,从设备的配置空间的bar寄存器当中获取资源,用于后面写寄存器。
这里涉及的写或者读寄存器主要有
(1)doorbell(写生产者的index)
(2)hwrm(用于给硬件发消息)
它们都是
struct bnxt_bar_info {
struct resource *res;
bus_space_tag_t tag;
bus_space_handle_t handle;
bus_size_t size;
int rid;
};
类型,具体的成员含义不太清楚。
主要涉及的api:
bus_alloc_resource_any
bus_release_resource
bar->tag = rman_get_bustag(bar->res);
bar->handle = rman_get_bushandle(bar->res);
bar->size = rman_get_size(bar->res);
bus_space_write_4
//...
alloc_hwrm_dma_mem接口(2022/7/13)
接口申请dma内存,用于给硬件发送信息,然后硬件回复信息。
iflib_dma_alloc(softc->ctx, PAGE_SIZE, &softc->hwrm_cmd_resp, BUS_DMA_NOWAIT);
linux bnxt open函数里队列内存申请相关步骤(2022/7/14)
bnxt_open
__bnxt_open_nic
bnxt_alloc_mem
bnxt_init_napi
bnxt_request_irq
bnxt_init_nic
bnxt_init_cp_rings
bnxt_init_rx_rings
bnxt_init_tx_rings
bnxt_init_ring_grps
bnxt_init_vnics
bnxt_init_chip
bnxt_hwrm_stat_ctx_alloc
bnxt_hwrm_ring_grp_alloc
bnxt_hwrm_vnic_alloc
bnxt_setup_vnic
bnxt_hwrm_set_vnic_filter
bnxt_hwrm_ring_alloc
bnxt_attch_pre初始化函数里hwrm相关的api总结(2022/7/14)
bnxt_hwrm_ver_get函数:
按照一定的消息格式给硬件发送消息,然后得到硬件的回复消息,将回复的结果给
softc->ver_info结构体的成员赋值。
/* All the version information for the part */
#define BNXT_VERSTR_SIZE (3*3+2+1) /* ie: "255.255.255\0" */
#define BNXT_NAME_SIZE 17
struct bnxt_ver_info {
uint8_t hwrm_if_major;
uint8_t hwrm_if_minor;
uint8_t hwrm_if_update;
char hwrm_if_ver[BNXT_VERSTR_SIZE];
char driver_hwrm_if_ver[BNXT_VERSTR_SIZE];
char hwrm_fw_ver[BNXT_VERSTR_SIZE];
char mgmt_fw_ver[BNXT_VERSTR_SIZE];
char netctrl_fw_ver[BNXT_VERSTR_SIZE];
char roce_fw_ver[BNXT_VERSTR_SIZE];
char phy_ver[BNXT_VERSTR_SIZE];
char pkg_ver[64];
char hwrm_fw_name[BNXT_NAME_SIZE];
char mgmt_fw_name[BNXT_NAME_SIZE];
char netctrl_fw_name[BNXT_NAME_SIZE];
char roce_fw_name[BNXT_NAME_SIZE];
char phy_vendor[BNXT_NAME_SIZE];
char phy_partnumber[BNXT_NAME_SIZE];
uint16_t chip_num;
uint8_t chip_rev;
uint8_t chip_metal;
uint8_t chip_bond_id;
uint8_t chip_type;
uint8_t hwrm_min_major;
uint8_t hwrm_min_minor;
uint8_t hwrm_min_update;
struct sysctl_ctx_list ver_ctx;
struct sysctl_oid *ver_oid;
};
其余的还有:
int vb_hwrm_ver_get(struct vb_softc *softc);
int vb_hwrm_nvm_get_dev_info(struct vb_softc * softc);
int vb_hwrm_func_drv_register(struct vb_softc *softc);
int vb_hwrm_func_rgtr_async_events(struct vb_softc *softc, unsigned long *bmap, int bmap_size);
int vb_hwrm_func_qcaps(struct vb_softc * softc);
int vb_hwrm_func_qcfg(struct vb_softc * softc);
int vb_hwrm_queue_qportcfg(struct vb_softc * softc);
int vb_hwrm_func_reset(struct vb_softc * softc);
这些函数先做的事情都是询问硬件,比如询问硬件支持几个队列,硬件回复以后,我们就可以把值赋给软件,然后开始后续的工作了。
struct bnxt_softc结构体里面重要的成员(结构体汇总)(2022/7/15)
重要结构体:
第一档:
1:device_t dev; //pci设备
2:if_ctx_t ctx; //iflib
3:if_softc_ctx_t scctx; //iflib
4:if_shared_ctx_t sctx; //类似于linux的ops操作
5:struct ifmedia *media;
第二档:
1:struct bnxt_hw_lro; //1 分片功能
2:struct bnxt_ver_info; //1 获取硬件参数
3:struct bnxt_nvram_info; //1 获取硬件参数
4:struct bnxt_cp_ring; //3
5:struct bnxt_bar_info; //2 获取bar空间资源,用于后面写寄存器,例如doorbell
6:struct bnxt_link_info //1
7:struct bnxt_func_info; //1
8:struct bnxt_func_qcfg; //1
9:struct bnxt_pf_info pf; //1
10:struct bnxt_vf_info vf; //1
11:struct iflib_dma_info //7 dma数据传输
12:struct bnxt_cos_queue //1
13:struct rx_port_stats //1
14:struct tx_port_stats //1
15:struct bnxt_ring //3
16:struct bnxt_vnic_info //1
17:struct bnxt_grp_info //1
其中有这些结构体变量是指针形式:
struct rx_port_stats *rx_port_stats;
struct tx_port_stats *tx_port_stats;
struct bnxt_ring *tx_rings;
struct bnxt_ring *ag_rings;
struct bnxt_ring *rx_rings;
struct bnxt_cp_ring *tx_cp_rings;
struct bnxt_cp_ring *rx_cp_rings;
struct bnxt_grp_info *grp_info;
//这两个是在attch_pre初始化函数里通过malloc申请的内存,通过hwrm接口询问硬件,获取到相关值以后,给它们的成员变量赋值。
struct bnxt_ver_info *ver_info;
struct bnxt_nvram_info *nvm_info;
需要重点分析其他的结构体指针变量是如何申请内存的,申请多大内存,由什么决定的?
在bnxt_tx_queues_alloc函数里,
softc->tx_cp_rings = malloc(sizeof(struct bnxt_cp_ring) * ntxqsets, M_DEVBUF, M_NOWAIT | M_ZERO);
softc->tx_rings = malloc(sizeof(struct bnxt_ring) * ntxqsets, M_DEVBUF, M_NOWAIT | M_ZERO);
tx_stats....变量的dma映射也是在这个函数完成的。
在bnxt_rx_queues_alloc函数里,
softc->rx_cp_rings = malloc(sizeof(struct bnxt_cp_ring) * nrxqsets, M_DEVBUF, M_NOWAIT | M_ZERO);
softc->rx_rings = malloc(sizeof(struct bnxt_ring) * nrxqsets, M_DEVBUF, M_NOWAIT | M_ZERO);
softc->ag_rings = malloc(sizeof(struct bnxt_ring) * nrxqsets, M_DEVBUF, M_NOWAIT | M_ZERO);
softc->grp_info = malloc(sizeof(struct bnxt_grp_info) * nrxqsets, M_DEVBUF, M_NOWAIT | M_ZERO);
//其中这两个idi_vaddr是dma的虚拟地址
softc->rx_port_stats = (void *) softc->hw_rx_port_stats.idi_vaddr;
softc->tx_port_stats = (void *) softc->hw_tx_port_stats.idi_vaddr;
所以:这些有关与队列相关的结构体申请多大的内存空间是由ntxqsets决定的,再接着追踪传入的ntxqsets值是多少。
从打印来看是4.。。。。明天在追踪为什么是4.。
ntxqsets值继续追踪(2022/7/18)
在iflib.c文件里,
iflib_queues_alloc(if_ctx_t ctx)函数里面,
if ((err = IFDI_TX_QUEUES_ALLOC(ctx, vaddrs, paddrs, ntxqs, ntxqsets)) != 0)
其中,IFDI_TX_QUEUES_ALLOC调用的就是bnxt_tx_queues_alloc函数,所以ntxqsets就是传入的实参,这个实参的值,得到的结果,从代码逻辑上分析还是比较复杂的,应该有它内部的一套逻辑,但是从这套逻辑来看,与硬件的cpu核数有关。
其他的类似于bnxt开头的一系列函数也都是在iflib这边调用的,调用形式类似于IFDI_XXX_XXX…
static device_method_t bnxt_iflib_methods[] = {
DEVMETHOD(ifdi_tx_queues_alloc, bnxt_tx_queues_alloc),
DEVMETHOD(ifdi_rx_queues_alloc, bnxt_rx_queues_alloc),
DEVMETHOD(ifdi_queues_free, bnxt_queues_free),
DEVMETHOD(ifdi_attach_pre, bnxt_attach_pre),
DEVMETHOD(ifdi_attach_post, bnxt_attach_post),
DEVMETHOD(ifdi_detach, bnxt_detach),
DEVMETHOD(ifdi_init, bnxt_init),
DEVMETHOD(ifdi_stop, bnxt_stop),
DEVMETHOD(ifdi_multi_set, bnxt_multi_set),
DEVMETHOD(ifdi_mtu_set, bnxt_mtu_set),
DEVMETHOD(ifdi_media_status, bnxt_media_status),
DEVMETHOD(ifdi_media_change, bnxt_media_change),
DEVMETHOD(ifdi_promisc_set, bnxt_promisc_set),
DEVMETHOD(ifdi_get_counter, bnxt_get_counter),
DEVMETHOD(ifdi_update_admin_status, bnxt_update_admin_status),
DEVMETHOD(ifdi_timer, bnxt_if_timer),
DEVMETHOD(ifdi_intr_enable, bnxt_intr_enable),
DEVMETHOD(ifdi_tx_queue_intr_enable, bnxt_tx_queue_intr_enable),
DEVMETHOD(ifdi_rx_queue_intr_enable, bnxt_rx_queue_intr_enable),
DEVMETHOD(ifdi_intr_disable, bnxt_disable_intr),
DEVMETHOD(ifdi_msix_intr_assign, bnxt_msix_intr_assign),
DEVMETHOD(ifdi_vlan_register, bnxt_vlan_register),
DEVMETHOD(ifdi_vlan_unregister, bnxt_vlan_unregister),
DEVMETHOD(ifdi_priv_ioctl, bnxt_priv_ioctl),
DEVMETHOD(ifdi_suspend, bnxt_suspend),
DEVMETHOD(ifdi_shutdown, bnxt_shutdown),
DEVMETHOD(ifdi_resume, bnxt_resume),
DEVMETHOD_END
};
bnxt_softc结构体里nrxqsets与ntxqsets赋值时机(2022/7/18)
softc->ntxqsets = ntxqsets;
softc->nrxqsets = nrxqsets;
这两个值都是在queue的alloc函数里面赋值的,后面大多数的操作都是利用的这两个值进行循环。
写DB时index的格式(2022/7/18)
tx和rx:
32bit:
4bit(key)+4bit(reserved)+idx(24)
其中,tx的key是0,rx的key是1。
bnxt_softc结构体里各个结构体成员间的关系分析(与队列有关的)(2022/7/18)
感觉这点比较难,要想实现最简单的发包或者收包功能,感觉需要分析清楚,iflib库做了什么,而我们的驱动程序又要做什么,这是一点,要搞清楚这一点,必须要分析清楚这几个结构体之间的关系。。。。。
好吧,接着分析。。
//....
FreeBSD-MSI相关API(2022/7/19)
#include<dev/pci/pcivar.h>
int pci_msix_count(device_t dev);
int pci_msi_count(device_t dev);
返回值为设备支持的msi/msix消息个数。
int pci_alloc_msix(device_t dev, int *count);
int pci_alloc_msi(device_t dev, int *count);
向设备申请count个消息,如果设备没有那么多,返回值可能实际会小于count个。
int pci_release_msi(device_t dev);
释放掉由上面两个alloc申请的msi/msix消息个数。
iflib_device_register函数里调用bnxt驱动函数汇总(2022/7/20)
昨天改了一些代码,驱动挂在了iflib_device_register函数的最后一步,修改了一个地方,iflib_device_register函数的流程应该走完了(但是不太确定,可能是挂在iflib_add_device_sysctl_post函数里面了),程序又挂了,这下不知道挂在那里了。。
这里汇总下,iflib_device_register函数里面会调用到bnxt驱动这边的那些函数。
IFDI_ATTACH_PRE
IFDI_INTR_DISABLE
IFDI_MSIX_INTR_ASSIGN
IFDI_ATTACH_POST
除了这些,间接调用的还有许多,例如bnxt_init这些。。。。
应该就这么多了,但是这些函数都跑玩了。
bnxt里面中断函数分析(2022/7/20)
涉及的有这些。。
DEVMETHOD(ifdi_intr_enable, bnxt_intr_enable),
DEVMETHOD(ifdi_tx_queue_intr_enable, bnxt_tx_queue_intr_enable),
DEVMETHOD(ifdi_rx_queue_intr_enable, bnxt_rx_queue_intr_enable),
DEVMETHOD(ifdi_intr_disable, bnxt_disable_intr),
DEVMETHOD(ifdi_msix_intr_assign, bnxt_msix_intr_assign),
也就是:
bnxt_intr_enable
bnxt_tx_queue_intr_enable
bnxt_rx_queue_intr_enable
bnxt_disable_intr
bnxt_msix_intr_assign
这些函数和队列关系比较大,也都会在iflib.c里面调用。。。
先分析下函数的实现吧。
bnxt_intr_enable (从描述来看是使能所有的中断的,上面没有tx队列相关的。。因为tx时不需要中断。。):
/* Enable all interrupts */
static void
bnxt_intr_enable(if_ctx_t ctx)
{
struct bnxt_softc *softc = iflib_get_softc(ctx);
int i;
bnxt_do_enable_intr(&softc->def_cp_ring);
for (i = 0; i < softc->nrxqsets; i++)
bnxt_do_enable_intr(&softc->rx_cp_rings[i]);
return;
}
bnxt_disable_intr(禁止中断):
/* Disable all interrupts */
static void
bnxt_disable_intr(if_ctx_t ctx)
{
struct bnxt_softc *softc = iflib_get_softc(ctx);
int i;
/*
* NOTE: These TX interrupts should never get enabled, so don't
* update the index
*/
for (i = 0; i < softc->ntxqsets; i++)
bnxt_do_disable_intr(&softc->tx_cp_rings[i]);
for (i = 0; i < softc->nrxqsets; i++)
bnxt_do_disable_intr(&softc->rx_cp_rings[i]);
return;
}
bnxt_tx_queue_intr_enable:
/* Enable interrupt for a single queue */
static int
bnxt_tx_queue_intr_enable(if_ctx_t ctx, uint16_t qid)
{
struct bnxt_softc *softc = iflib_get_softc(ctx);
bnxt_do_enable_intr(&softc->tx_cp_rings[qid]);
return 0;
}
bnxt_rx_queue_intr_enable:
static int
bnxt_rx_queue_intr_enable(if_ctx_t ctx, uint16_t qid)
{
struct bnxt_softc *softc = iflib_get_softc(ctx);
bnxt_do_enable_intr(&softc->rx_cp_rings[qid]);
return 0;
}
这两个函数都是针对单个队列的enable。。。tx也需要enable吗?
且这几个函数都是调用的bnxt_do_enable_intr函数做的操作。。。为了揭开谜底,来看看bnxt_do_enable_intr做了些什么吧。
static void inline
bnxt_do_enable_intr(struct bnxt_cp_ring *cpr)
{
if (cpr->ring.phys_id != (uint16_t)HWRM_NA_SIGNATURE) {
/* First time enabling, do not set index */
if (cpr->cons == UINT32_MAX)
BNXT_CP_ENABLE_DB(&cpr->ring);
else
BNXT_CP_IDX_ENABLE_DB(&cpr->ring, cpr->cons);
}
}
这里又涉及BNXT_CP_ENABLE_DB和BNXT_CP_IDX_ENABLE_DB,其实这些都是写寄存器操作了。。接着分析。。
/*
* If we update the index, a write barrier is needed after the write to ensure
* the completion ring has space before the RX/TX ring does. Since we can't
* make the RX and AG doorbells covered by the same barrier without remapping
* MSI-X vectors, we create the barrier over the enture doorbell bar.
* TODO: Remap the MSI-X vectors to allow a barrier to only cover the doorbells
* for a single ring group.
*
* A barrier of just the size of the write is used to ensure the ordering
* remains correct and no writes are lost.
*/
//CMP
#define BNXT_CP_DISABLE_DB(ring) do { \
bus_space_barrier((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, (ring)->doorbell, 4, \
BUS_SPACE_BARRIER_WRITE); \
bus_space_barrier((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, 0, \
(ring)->softc->doorbell_bar.size, BUS_SPACE_BARRIER_WRITE); \
bus_space_write_4((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, (ring)->doorbell, \
htole32(CMPL_DOORBELL_KEY_CMPL | CMPL_DOORBELL_MASK)); \
} while (0)
#define BNXT_CP_ENABLE_DB(ring) do { \
bus_space_barrier((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, (ring)->doorbell, 4, \
BUS_SPACE_BARRIER_WRITE); \
bus_space_barrier((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, 0, \
(ring)->softc->doorbell_bar.size, BUS_SPACE_BARRIER_WRITE); \
bus_space_write_4((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, (ring)->doorbell, \
htole32(CMPL_DOORBELL_KEY_CMPL)); \
} while (0)
#define BNXT_CP_IDX_ENABLE_DB(ring, cons) do { \
bus_space_barrier((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, (ring)->doorbell, 4, \
BUS_SPACE_BARRIER_WRITE); \
bus_space_write_4((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, (ring)->doorbell, \
htole32(CMPL_DOORBELL_KEY_CMPL | CMPL_DOORBELL_IDX_VALID | \
(cons))); \
bus_space_barrier((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, 0, \
(ring)->softc->doorbell_bar.size, BUS_SPACE_BARRIER_WRITE); \
} while (0)
#define BNXT_CP_IDX_DISABLE_DB(ring, cons) do { \
bus_space_barrier((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, (ring)->doorbell, 4, \
BUS_SPACE_BARRIER_WRITE); \
bus_space_write_4((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, (ring)->doorbell, \
htole32(CMPL_DOORBELL_KEY_CMPL | CMPL_DOORBELL_IDX_VALID | \
CMPL_DOORBELL_MASK | (cons))); \
bus_space_barrier((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, 0, \
(ring)->softc->doorbell_bar.size, BUS_SPACE_BARRIER_WRITE); \
} while (0)
//TX
#define BNXT_TX_DB(ring, idx) do { \
bus_space_barrier((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, (ring)->doorbell, 4, \
BUS_SPACE_BARRIER_WRITE); \
bus_space_write_4( \
(ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, \
(ring)->doorbell, htole32(TX_DOORBELL_KEY_TX | (idx))); \
} while (0)
//RX
#define BNXT_RX_DB(ring, idx) do { \
bus_space_barrier((ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, (ring)->doorbell, 4, \
BUS_SPACE_BARRIER_WRITE); \
bus_space_write_4( \
(ring)->softc->doorbell_bar.tag, \
(ring)->softc->doorbell_bar.handle, \
(ring)->doorbell, htole32(RX_DOORBELL_KEY_RX | (idx))); \
} while (0)
这里看下来其实是按照cmp doorbell的格式写寄存器操作。。
hwrm_ring_alloc接口总结(2022/7/20)
这个接口填充了一堆消息给硬件,最后硬件回复了一个环相关的物理编号。。。
接口的主要作用就是预分配许多与环相关的缓冲区(物理内存),我把这些缓冲区的指针(指向这个缓冲区的首地址)等一系列信息,
其实就是许多个BD。。。。有数据过来时,我把当前BD的index写到寄存器里面。。。。网卡,有数据到了,你去相应的BD里把数据取走。。。。
编程手册指导阅读(2022/7/21)
4.1主机和设备进行交流。
利用BAR空间里面的资源,bar0给hwrm下发命令,bar2用作写doorbell,bar4用作MSI-X vector。。。
4.2硬件资源管理规格。
4.2.2主机软件接口模型
4.2.2.1doorbell格式
最后我们都是向doorbell寄存器写操作,doorbell寄存器需要按照它的格式写入。不能随便写一个数值进去。
4.2.2.2BD环结构
BD Ring Structures由驱动程序写入 tx Rings 和 RX Rings,
以向芯片指示主机中有更多缓冲区空间需要传输或可用于接收数据。。。
也就是先申请一大片缓冲区,然后告诉硬件,,后面我写index就可以了。
4.2.2.3完成环的结构
4.3驱动初始化
4.3.1向操作系统请求资源,这一步和bar空间哪里寻求资源的映射。
4.3.2做一些初始值,比如硬件是否支持硬件卸载等,
(1)队列数,环的数量设置
(2)环里面的BD数量设置
(3)coalescing parameters设置
(4)off-load
4.3.3通过hwrm接口做一些配置
涉及,hwrm_ver_get,hwrm_fw_set_time…
4.4打开设备
(1)从主机内存当中分配数据结构
1:TX/RX/completing rings
2:NIC attribute associated structures
3:Forward group IDs
4:Unicast Lists
5:Multicast List
6:RSS
(2)初始化所有的环
1:初始化所有的环和BDS
2:分配RX data 缓冲区和聚合环
3:通过hwrm接口向NIC请求资源
涉及,hwrm_ring_alloc,hwrm_ring_grp_alloc
hwrm_vnic_alloc,hwrm_vnic_rss_cos_lb_ctx_alloc
(3)配置默认的VNIC0和ring group 0
hwrm_vnic_cfg
(4)使能RSS 哈希在vnic 0
hwrm_vnic_rss_cfg
(5)使能features
hwrm_vnic_plcmodes_cfg
hwrm_vnic_tpa_cf
(6)设置中断的coalescing parameters
hwrm_ring_cmpl_ring_cfg_aggint_params
(7)使能中断与TX
4.5关闭设备
涉及一些资源的释放
4.6数据包的传输
4.7数据包的接收
4.8中断处理
4.9统计和计数
通过相关的hwrm_xxx_ctx_xxx命令向硬件
询问,得到统计的信息。。
4.10
4.11NVRAM(non-volatile memory) 配置
4.12驱动更新
4.13驱动加载
使用几个环,以及每个环有几个DB是由主机决定的。。。
bnxt_txrx系列的填充函数分析(2022/7/22)
总共有这些:
struct if_txrx bnxt_txrx = {
.ift_txd_encap = bnxt_isc_txd_encap,
.ift_txd_flush = bnxt_isc_txd_flush,
.ift_txd_credits_update = bnxt_isc_txd_credits_update,
.ift_rxd_available = bnxt_isc_rxd_available,
.ift_rxd_pkt_get = bnxt_isc_rxd_pkt_get,
.ift_rxd_refill = bnxt_isc_rxd_refill,
.ift_rxd_flush = bnxt_isc_rxd_flush,
.ift_legacy_intr = bnxt_intr
};
其中tx:
.ift_txd_encap = bnxt_isc_txd_encap,
.ift_txd_flush = bnxt_isc_txd_flush,
.ift_txd_credits_update = bnxt_isc_txd_credits_update,
rx:
.ift_rxd_available = bnxt_isc_rxd_available,
.ift_rxd_pkt_get = bnxt_isc_rxd_pkt_get,
.ift_rxd_refill = bnxt_isc_rxd_refill,
.ift_rxd_flush = bnxt_isc_rxd_flush,
bnxt_intr函数体的内容只有一些打印,看起来没什么大作用。
static int
bnxt_intr(void *sc)
{
struct bnxt_softc *softc = (struct bnxt_softc *)sc;
device_printf(softc->dev, "STUB: %s @ %s:%d\n", __func__, __FILE__, __LINE__);
return ENOSYS;
}
先重点分析下有关tx的这3个函数做了什么:
bnxt_isc_txd_encap:
这个函数也不难,其实就是给BD字段的每个部分的字节赋值,其中,coal_now,no_cmpl在函数里面没有赋值,根据说明一般情况下我们不需要去设置这两个字段,所以函数里面没看到设置,也属于正常。
总体来说就是根据发送一个数据包,是否需要分片,如果需要分片,那么就需要使用多个BD了,如果数据包里面带vlan信息,那就需要更多的BD,然后就是根据需要的BD的个数,给它的成员赋值,赋值的地方主要有type和flag(packet_end,bd_cnt,ihint)。
static int
bnxt_isc_txd_encap(void *sc, if_pkt_info_t pi)
{
struct bnxt_softc *softc = (struct bnxt_softc *)sc;
//取到特定的环
struct bnxt_ring *txr = &softc->tx_rings[pi->ipi_qsidx];
//BD
struct tx_bd_long *tbd; //
struct tx_bd_long_hi *tbdh;
bool need_hi = false;
//
uint16_t flags_type;
uint16_t lflags;
uint32_t cfa_meta;
int seg = 0;
/* If we have offloads enabled, we need to use two BDs. */
// if ((pi->ipi_csum_flags & (CSUM_OFFLOAD | CSUM_TSO | CSUM_IP)) || pi->ipi_mflags & M_VLANTAG)
// need_hi = true;
/* TODO: Devices before Cu+B1 need to not mix long and short BDs */
need_hi = true;
// need_hi = false;
//得到特定的BD -> tdb
pi->ipi_new_pidx = pi->ipi_pidx;
tbd = &((struct tx_bd_long *)txr->vaddr)[pi->ipi_new_pidx];
pi->ipi_ndescs = 0;
/* No need to byte-swap the opaque value */
tbd->opaque = ((pi->ipi_nsegs + need_hi) << 24) | pi->ipi_new_pidx; //该字段必须在数据包的第一个 BD 上有效
tbd->len = htole16(pi->ipi_segs[seg].ds_len);
tbd->addr = htole64(pi->ipi_segs[seg++].ds_addr);
//bd_cnt setup
flags_type = ((pi->ipi_nsegs + need_hi) << TX_BD_SHORT_FLAGS_BD_CNT_SFT) & TX_BD_SHORT_FLAGS_BD_CNT_MASK;
//ihint setup
if (pi->ipi_len >= 2048)
flags_type |= TX_BD_SHORT_FLAGS_LHINT_GTE2K;
else
flags_type |= bnxt_tx_lhint[pi->ipi_len >> 9];
/*
if (need_hi) {
flags_type |= TX_BD_LONG_TYPE_TX_BD_LONG;
pi->ipi_new_pidx = RING_NEXT(txr, pi->ipi_new_pidx);
tbdh = &((struct tx_bd_long_hi *)txr->vaddr)[pi->ipi_new_pidx];
tbdh->mss = htole16(pi->ipi_tso_segsz);
tbdh->hdr_size = htole16((pi->ipi_ehdrlen + pi->ipi_ip_hlen + pi->ipi_tcp_hlen) >> 1);
tbdh->cfa_action = 0;
lflags = 0;
cfa_meta = 0;
if (pi->ipi_mflags & M_VLANTAG) {
//TODO: Do we need to byte-swap the vtag here?
cfa_meta = TX_BD_LONG_CFA_META_KEY_VLAN_TAG | pi->ipi_vtag;
cfa_meta |= TX_BD_LONG_CFA_META_VLAN_TPID_TPID8100;
}
tbdh->cfa_meta = htole32(cfa_meta);
if (pi->ipi_csum_flags & CSUM_TSO) {
lflags |= TX_BD_LONG_LFLAGS_LSO | TX_BD_LONG_LFLAGS_T_IPID;
}
else if(pi->ipi_csum_flags & CSUM_OFFLOAD) {
lflags |= TX_BD_LONG_LFLAGS_TCP_UDP_CHKSUM | TX_BD_LONG_LFLAGS_IP_CHKSUM;
}
else if(pi->ipi_csum_flags & CSUM_IP) {
lflags |= TX_BD_LONG_LFLAGS_IP_CHKSUM;
}
tbdh->lflags = htole16(lflags);
}
else {
flags_type |= TX_BD_SHORT_TYPE_TX_BD_SHORT; //
}
*/
//flags_type |= TX_BD_SHORT_TYPE_TX_BD_SHORT;
for (; seg < pi->ipi_nsegs; seg++) {
tbd->flags_type = htole16(flags_type);
pi->ipi_new_pidx = RING_NEXT(txr, pi->ipi_new_pidx);
tbd = &((struct tx_bd_long *)txr->vaddr)[pi->ipi_new_pidx];
tbd->len = htole16(pi->ipi_segs[seg].ds_len);
tbd->addr = htole64(pi->ipi_segs[seg].ds_addr);
flags_type = TX_BD_SHORT_TYPE_TX_BD_SHORT;
}
flags_type |= TX_BD_SHORT_FLAGS_PACKET_END;
tbd->flags_type = htole16(flags_type);
pi->ipi_new_pidx = RING_NEXT(txr, pi->ipi_new_pidx);
return 0;
}
bnxt_isc_txd_credits_update:
static int
bnxt_isc_txd_credits_update(void *sc, uint16_t txqid, bool clear)
{
struct bnxt_softc *softc = (struct bnxt_softc *)sc;
//取特定的环
struct bnxt_cp_ring *cpr = &softc->tx_cp_rings[txqid];
//转cmpl,这里应该是更新完pidx,由硬件把cmpl环的信息以cmpl环的结构
//更新到这个vaddr地址里。。。。
struct tx_cmpl *cmpl = (struct tx_cmpl *)cpr->ring.vaddr;
/*
tx_cmpl
type(0-5,value,tx_l2) + flags(6-15) + unused1(16-31) + opaque(32-63)
v(0) + errors(1-15) + unused2(16-31) + unused3(32-63)
*/
/*
cmp doorbell format:
idx(0-23) + reserved(24-25) + idx_vaild(26,此次写操作是否有效,1有效) + mask(27,与中断有关) + key(31-28,写doorbell的操作类型,2是cmpl ring)
htole32(CMPL_DOORBELL_KEY_CMPL | CMPL_DOORBELL_IDX_VALID | CMPL_DOORBELL_MASK | (cons)));
enable 没有mask这一位。
*/
htole32(CMPL_DOORBELL_KEY_CMPL | CMPL_DOORBELL_IDX_VALID | CMPL_DOORBELL_MASK | (cons)));
int avail = 0;
uint32_t cons = cpr->cons;
bool v_bit = cpr->v_bit; //初值是1
bool last_v_bit;
uint32_t last_cons;
uint16_t type;
uint16_t err;
for (;;) {
//这里应该是把值取出来,先保存,后面还是要赋回去的。
last_cons = cons;
last_v_bit = v_bit;
//根据读到的cons,然后进行++操作并判断是否超出了范围,如果超出了范围,就把cons更新为0?
//v_bit变量,我感觉是为了判断当前的cons值是否是有效值如果是true,那就是有效的,false就是无效的。。
NEXT_CP_CONS_V(&cpr->ring, cons, v_bit);
//先把值放到缓存里,
/*__builtin_prefetch(&((struct cmpl_base *)(cpr)->ring.vaddr)
[((cons) + (CACHE_LINE_SIZE / sizeof(struct cmpl_base))) & ((cpr)->ring.ring_size - 1)])*/
CMPL_PREFETCH_NEXT(cpr, cons);
//读取值,由于之前已经已经通过地址把值放到缓存里面了,所以这里可以快速把值取出来,
//利用__builtin_prefetch可以提高cpu的性能(需要特定cpu的支持)
/*((!!(((struct cmpl_base *)(cmp))->info3_v & htole32(CMPL_BASE_V))) == !!(v_bit) )*/
//这里看样子值是1(传输成功了),也就是说info3_v应该等于v_bit
if (!CMP_VALID(&cmpl[cons], v_bit))
goto done;
//0x3f,判断是什么类型的mpl,,这里肯定是TX_CMPL_TYPE_TX_L2了。
type = cmpl[cons].flags_type & TX_CMPL_TYPE_MASK;
switch (type) {
case TX_CMPL_TYPE_TX_L2: //0
//主要是根据这个字段,判断是否传输错误
err = (le16toh(cmpl[cons].errors_v) & TX_CMPL_ERRORS_BUFFER_ERROR_MASK) >> TX_CMPL_ERRORS_BUFFER_ERROR_SFT;
if (err)
device_printf(softc->dev, "TX completion error %u\n", err);
//cmpl[cons].opaque这个字段是从与之关联的TX BD的opaque复制过来的。
avail += cmpl[cons].opaque >> 24;
if (!clear)
goto done;
break;
default:
//这个是什么操作????
if (type & 1) {
NEXT_CP_CONS_V(&cpr->ring, cons, v_bit);
if (!CMP_VALID(&cmpl[cons], v_bit))
goto done;
}
device_printf(softc->dev, "Unhandled TX completion type %u\n", type);
break;
}
}
done:
//这个avail的值感觉不是0,因为调用的地方进行判断时,看上去是要大于0的
if (clear && avail) {
cpr->cons = last_cons;
cpr->v_bit = last_v_bit;
BNXT_CP_IDX_DISABLE_DB(&cpr->ring, cpr->cons);
}
return avail;
}
bnxt_isc_txd_flush:这个函数主要就是把生产者的pindex写入doorbell寄存器,有疑问的地方就是,
为什么要写两次???
这里可能是,第一次写是传输BD,第二次是传输BD里面指向协议栈传输下来的数据,所以需要写两次
static void
bnxt_isc_txd_flush(void *sc, uint16_t txqid, qidx_t pidx)
{
struct bnxt_softc *softc = (struct bnxt_softc *)sc;
struct bnxt_ring *tx_ring = &softc->tx_rings[txqid];
/* pidx is what we last set ipi_new_pidx to */
BNXT_TX_DB(tx_ring, pidx);
/* TODO: Cumulus+ doesn't need the double doorbell */
BNXT_TX_DB(tx_ring, pidx);
return;
}