dinghai: add hardware register access and PCI capability scanning

Implement PCI configuration space access, BAR mapping, capability
scanning (common/notify/device), and hardware queue register
definitions for DingHai PF device.

Signed-off-by: Junyang Han <han.junyang@zte.com.cn>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Junyang Han
2026-08-02 16:24:31 +08:00
committed by Jakub Kicinski
parent b8180f7977
commit 19df707eee
3 changed files with 377 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ZTE DingHai Ethernet driver - PCI capability definitions
* Copyright (c) 2022-2026, ZTE Corporation.
*/
#ifndef __DH_QUEUE_H__
#define __DH_QUEUE_H__
#include <linux/types.h>
/* This is the PCI capability header: */
struct zxdh_pf_pci_cap {
__u8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */
__u8 cap_next; /* Generic PCI field: next ptr. */
__u8 cap_len; /* Generic PCI field: capability length */
__u8 cfg_type; /* Identifies the structure. */
__u8 bar; /* Where to find it. */
__u8 id; /* Multiple capabilities of the same type */
__u8 padding[2]; /* Pad to full dword. */
__le32 offset; /* Offset within bar. */
__le32 length; /* Length of the structure, in bytes. */
};
/* Fields in ZXDH_PF_PCI_CAP_COMMON_CFG: */
struct zxdh_pf_pci_common_cfg {
/* About the whole device. */
__le32 device_feature_select; /* read-write */
__le32 device_feature; /* read-only */
__le32 guest_feature_select; /* read-write */
__le32 guest_feature; /* read-write */
__le16 msix_config; /* read-write */
__le16 num_queues; /* read-only */
__u8 device_status; /* read-write */
__u8 config_generation; /* read-only */
/* About a specific virtqueue. */
__le16 queue_select; /* read-write */
__le16 queue_size; /* read-write, power of 2. */
__le16 queue_msix_vector; /* read-write */
__le16 queue_enable; /* read-write */
__le16 queue_notify_off; /* read-only */
__le32 queue_desc_lo; /* read-write */
__le32 queue_desc_hi; /* read-write */
__le32 queue_avail_lo; /* read-write */
__le32 queue_avail_hi; /* read-write */
__le32 queue_used_lo; /* read-write */
__le32 queue_used_hi; /* read-write */
};
struct zxdh_pf_pci_notify_cap {
struct zxdh_pf_pci_cap cap;
__le32 notify_off_multiplier; /* Multiplier for queue_notify_off. */
};
#endif /* __DH_QUEUE_H__ */

View File

@@ -9,6 +9,7 @@
#include <net/devlink.h>
#include <linux/dma-mapping.h>
#include "en_pf.h"
#include "dh_queue.h"
MODULE_AUTHOR("Junyang Han <han.junyang@zte.com.cn>");
MODULE_DESCRIPTION("ZTE DingHai series Ethernet driver");
@@ -103,6 +104,271 @@ void zxdh_pf_pci_close(struct zxdh_core_dev *zxdh_dev)
pci_disable_device(zxdh_dev->pdev);
}
int zxdh_pf_pci_find_capability(struct pci_dev *pdev, u8 cfg_type,
u32 ioresource_types, int *bars)
{
int pos;
u8 type;
u8 bar;
for (pos = pci_find_capability(pdev, PCI_CAP_ID_VNDR); pos > 0;
pos = pci_find_next_capability(pdev, pos, PCI_CAP_ID_VNDR)) {
pci_read_config_byte(pdev,
pos + offsetof(struct zxdh_pf_pci_cap,
cfg_type), &type);
pci_read_config_byte(pdev,
pos + offsetof(struct zxdh_pf_pci_cap, bar), &bar);
/* ignore structures with reserved BAR values */
if (bar > ZXDH_PF_MAX_BAR_VAL)
continue;
if (type == cfg_type) {
if (pci_resource_len(pdev, bar) &&
pci_resource_flags(pdev, bar) & ioresource_types) {
*bars |= (1 << bar);
return pos;
}
}
}
return 0;
}
void __iomem *zxdh_pf_map_capability(struct zxdh_core_dev *zxdh_dev, int off,
size_t minlen, u32 align,
u32 start, u32 size,
size_t *len, resource_size_t *pa,
u32 *bar_off)
{
struct pci_dev *pdev = zxdh_dev->pdev;
void __iomem *p;
u32 offset;
u32 length;
u8 bar;
pci_read_config_byte(pdev,
off + offsetof(struct zxdh_pf_pci_cap, bar), &bar);
if (bar > ZXDH_PF_MAX_BAR_VAL) {
dev_err(zxdh_dev->device, "invalid bar %u\n", bar);
return NULL;
}
pci_read_config_dword(pdev,
off + offsetof(struct zxdh_pf_pci_cap,
offset), &offset);
pci_read_config_dword(pdev,
off + offsetof(struct zxdh_pf_pci_cap,
length), &length);
if (bar_off)
*bar_off = offset;
if (length <= start) {
dev_err(zxdh_dev->device, "bad capability len %u (>%u expected)\n",
length, start);
return NULL;
}
if (length - start < minlen) {
dev_err(zxdh_dev->device, "bad capability len %u (>=%zu expected)\n",
length, minlen);
return NULL;
}
length -= start;
if (start + offset < offset) {
dev_err(zxdh_dev->device, "map wrap-around %u+%u\n", start, offset);
return NULL;
}
offset += start;
if (offset & (align - 1)) {
dev_err(zxdh_dev->device, "offset %u not aligned to %u\n", offset, align);
return NULL;
}
if (length > size)
length = size;
if (len)
*len = length;
if (length + offset < offset ||
length + offset > pci_resource_len(pdev, bar)) {
dev_err(zxdh_dev->device,
"map %u@%u out of range on bar %u length %lu\n",
length, offset, bar,
(unsigned long)pci_resource_len(pdev, bar));
return NULL;
}
p = pci_iomap_range(pdev, bar, offset, length);
if (!p) {
dev_err(zxdh_dev->device, "unable to map custom queue %u@%u on bar %u\n",
length, offset, bar);
} else if (pa) {
*pa = pci_resource_start(pdev, bar) + offset;
}
return p;
}
int zxdh_pf_common_cfg_init(struct zxdh_core_dev *zxdh_dev)
{
struct zxdh_pf_dev *pf_dev = zxdh_dev->priv;
struct pci_dev *pdev = zxdh_dev->pdev;
int common;
/* check for a common config: if not, use legacy mode (bar 0). */
common = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_COMMON_CFG,
IORESOURCE_MEM,
&pf_dev->modern_bars);
if (!common) {
dev_err(zxdh_dev->device,
"missing capabilities, leaving for legacy driver\n");
return -ENODEV;
}
pf_dev->common = zxdh_pf_map_capability(zxdh_dev, common,
sizeof(struct zxdh_pf_pci_common_cfg),
ZXDH_PF_ALIGN4, 0,
sizeof(struct zxdh_pf_pci_common_cfg),
NULL, NULL, NULL);
if (!pf_dev->common) {
dev_err(zxdh_dev->device, "pf_dev->common is null\n");
return -EINVAL;
}
return 0;
}
int zxdh_pf_notify_cfg_init(struct zxdh_core_dev *zxdh_dev)
{
struct zxdh_pf_dev *pf_dev = zxdh_dev->priv;
struct pci_dev *pdev = zxdh_dev->pdev;
u32 notify_length;
u32 notify_offset;
int notify;
/* If common is there, these should be too... */
notify = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_NOTIFY_CFG,
IORESOURCE_MEM,
&pf_dev->modern_bars);
if (!notify) {
dev_err(zxdh_dev->device, "missing notify cfg capability\n");
return -EINVAL;
}
pci_read_config_dword(pdev,
notify + offsetof(struct zxdh_pf_pci_notify_cap,
notify_off_multiplier),
&pf_dev->notify_offset_multiplier);
pci_read_config_dword(pdev,
notify + offsetof(struct zxdh_pf_pci_notify_cap,
cap.length), &notify_length);
pci_read_config_dword(pdev,
notify + offsetof(struct zxdh_pf_pci_notify_cap,
cap.offset), &notify_offset);
/* We don't know how many VQs we'll map, ahead of the time.
* If notify length is small, map it all now. Otherwise,
* map each VQ individually later.
*/
if (notify_length <= PAGE_SIZE - (notify_offset % PAGE_SIZE)) {
pf_dev->notify_base = zxdh_pf_map_capability(zxdh_dev, notify,
ZXDH_PF_MAP_MINLEN2,
ZXDH_PF_ALIGN2, 0,
notify_length,
&pf_dev->notify_len,
&pf_dev->notify_pa, NULL);
if (!pf_dev->notify_base) {
dev_err(zxdh_dev->device, "pf_dev->notify_base is null\n");
return -EINVAL;
}
} else {
pf_dev->notify_map_cap = notify;
}
return 0;
}
int zxdh_pf_device_cfg_init(struct zxdh_core_dev *zxdh_dev)
{
struct zxdh_pf_dev *pf_dev = zxdh_dev->priv;
struct pci_dev *pdev = zxdh_dev->pdev;
int device;
/* Device capability is only mandatory for
* devices that have device-specific configuration.
*/
device = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_DEVICE_CFG,
IORESOURCE_MEM,
&pf_dev->modern_bars);
/* we don't know how much we should map,
* but PAGE_SIZE is more than enough for all existing devices.
*/
if (device) {
pf_dev->device = zxdh_pf_map_capability(zxdh_dev, device, 0,
ZXDH_PF_ALIGN4, 0, PAGE_SIZE,
&pf_dev->device_len, NULL,
&pf_dev->dev_cfg_bar_off);
if (!pf_dev->device) {
dev_err(zxdh_dev->device, "pf_dev->device is null\n");
return -EINVAL;
}
}
return 0;
}
void zxdh_pf_modern_cfg_uninit(struct zxdh_core_dev *zxdh_dev)
{
struct zxdh_pf_dev *pf_dev = zxdh_dev->priv;
struct pci_dev *pdev = zxdh_dev->pdev;
if (pf_dev->device)
pci_iounmap(pdev, pf_dev->device);
if (pf_dev->notify_base)
pci_iounmap(pdev, pf_dev->notify_base);
pci_iounmap(pdev, pf_dev->common);
}
int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev)
{
struct zxdh_pf_dev *pf_dev = zxdh_dev->priv;
struct pci_dev *pdev = zxdh_dev->pdev;
int ret;
ret = zxdh_pf_common_cfg_init(zxdh_dev);
if (ret) {
dev_err(zxdh_dev->device, "zxdh_pf_common_cfg_init failed: %d\n", ret);
return ret;
}
ret = zxdh_pf_notify_cfg_init(zxdh_dev);
if (ret) {
dev_err(zxdh_dev->device, "zxdh_pf_notify_cfg_init failed: %d\n", ret);
goto err_map_notify;
}
ret = zxdh_pf_device_cfg_init(zxdh_dev);
if (ret) {
dev_err(zxdh_dev->device, "zxdh_pf_device_cfg_init failed: %d\n", ret);
goto err_map_device;
}
return 0;
err_map_device:
if (pf_dev->notify_base)
pci_iounmap(pdev, pf_dev->notify_base);
err_map_notify:
pci_iounmap(pdev, pf_dev->common);
return ret;
}
static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct zxdh_core_dev *zxdh_dev;
@@ -133,10 +399,18 @@ static int zxdh_pf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto err_pci_init;
}
ret = zxdh_pf_modern_cfg_init(zxdh_dev);
if (ret) {
dev_err(&pdev->dev, "zxdh_pf_modern_cfg_init failed: %d\n", ret);
goto err_cfg_init;
}
devlink_register(devlink);
return 0;
err_cfg_init:
zxdh_pf_pci_close(zxdh_dev);
err_pci_init:
zxdh_core_free_priv(zxdh_dev);
err_pf_dev:
@@ -150,6 +424,7 @@ static void zxdh_pf_remove(struct pci_dev *pdev)
struct devlink *devlink = priv_to_devlink(zxdh_dev);
devlink_unregister(devlink);
zxdh_pf_modern_cfg_uninit(zxdh_dev);
zxdh_pf_pci_close(zxdh_dev);
zxdh_core_free_priv(zxdh_dev);
devlink_free(devlink);

View File

@@ -7,10 +7,28 @@
#ifndef __ZXDH_EN_PF_H__
#define __ZXDH_EN_PF_H__
#include <linux/types.h>
#define ZXDH_PF_VENDOR_ID 0x1cf2
#define ZXDH_PF_DEVICE_ID 0x8040
#define ZXDH_VF_DEVICE_ID 0x8041
/* Common configuration */
#define ZXDH_PCI_CAP_COMMON_CFG 1
/* Notifications */
#define ZXDH_PCI_CAP_NOTIFY_CFG 2
/* ISR access */
#define ZXDH_PCI_CAP_ISR_CFG 3
/* Device specific configuration */
#define ZXDH_PCI_CAP_DEVICE_CFG 4
/* PCI configuration access */
#define ZXDH_PCI_CAP_PCI_CFG 5
#define ZXDH_PF_MAX_BAR_VAL 0x5
#define ZXDH_PF_ALIGN4 4
#define ZXDH_PF_ALIGN2 2
#define ZXDH_PF_MAP_MINLEN2 2
struct zxdh_core_dev {
struct device *device;
struct pci_dev *pdev;
@@ -19,11 +37,39 @@ struct zxdh_core_dev {
};
struct zxdh_pf_dev {
struct zxdh_pf_pci_common_cfg __iomem *common;
/* Device-specific data (non-legacy mode) */
/* Base of vq notifications (non-legacy mode). */
void __iomem *device;
void __iomem *notify_base;
/* Physical base of vq notifications */
resource_size_t notify_pa;
/* So we can sanity-check accesses. */
size_t notify_len;
size_t device_len;
/* Capability for when we need to map notifications per-vq. */
s32 notify_map_cap;
u32 notify_offset_multiplier;
/* Multiply queue_notify_off by this value. (non-legacy mode). */
s32 modern_bars;
void __iomem *pci_ioremap_addr[6];
u32 dev_cfg_bar_off;
};
void *zxdh_core_alloc_priv(struct zxdh_core_dev *zxdh_dev, size_t size);
void zxdh_core_free_priv(struct zxdh_core_dev *zxdh_dev);
void zxdh_pf_pci_close(struct zxdh_core_dev *zxdh_dev);
int zxdh_pf_pci_find_capability(struct pci_dev *pdev, u8 cfg_type,
u32 ioresource_types, int *bars);
void __iomem *zxdh_pf_map_capability(struct zxdh_core_dev *zxdh_dev, int off,
size_t minlen, u32 align,
u32 start, u32 size,
size_t *len, resource_size_t *pa,
u32 *bar_off);
int zxdh_pf_common_cfg_init(struct zxdh_core_dev *zxdh_dev);
int zxdh_pf_notify_cfg_init(struct zxdh_core_dev *zxdh_dev);
int zxdh_pf_device_cfg_init(struct zxdh_core_dev *zxdh_dev);
void zxdh_pf_modern_cfg_uninit(struct zxdh_core_dev *zxdh_dev);
int zxdh_pf_modern_cfg_init(struct zxdh_core_dev *zxdh_dev);
#endif /* __ZXDH_EN_PF_H__ */