summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/netronome/nfp/nfp_netvf_main.c
blob: 1145849ca7bac044195f0778db8d882e37b9b8ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/* Copyright (C) 2015-2018 Netronome Systems, Inc. */

/*
 * nfp_netvf_main.c
 * Netronome virtual function network device driver: Main entry point
 * Author: Jason McMullan <jason.mcmullan@netronome.com>
 *         Rolf Neugebauer <rolf.neugebauer@netronome.com>
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/etherdevice.h>

#include "nfp_net_ctrl.h"
#include "nfp_net.h"
#include "nfp_main.h"

/**
 * struct nfp_net_vf - NFP VF-specific device structure
 * @nn:		NFP Net structure for this device
 * @irq_entries: Pre-allocated array of MSI-X entries
 * @q_bar:	Pointer to mapped QC memory (NULL if TX/RX mapped directly)
 * @ddir:	Per-device debugfs directory
 */
struct nfp_net_vf {
	struct nfp_net *nn;

	struct msix_entry irq_entries[NFP_NET_NON_Q_VECTORS +
				      NFP_NET_MAX_TX_RINGS];
	u8 __iomem *q_bar;

	struct dentry *ddir;
};

static const char nfp_net_driver_name[] = "nfp_netvf";

#define PCI_DEVICE_NFP6000VF		0x6003
static const struct pci_device_id nfp_netvf_pci_device_ids[] = {
	{ PCI_VENDOR_ID_NETRONOME, PCI_DEVICE_NFP6000VF,
	  PCI_VENDOR_ID_NETRONOME, PCI_ANY_ID,
	  PCI_ANY_ID, 0,
	},
	{ 0, } /* Required last entry. */
};
MODULE_DEVICE_TABLE(pci, nfp_netvf_pci_device_ids);

static void nfp_netvf_get_mac_addr(struct nfp_net *nn)
{
	u8 mac_addr[ETH_ALEN];

	put_unaligned_be32(nn_readl(nn, NFP_NET_CFG_MACADDR + 0), &mac_addr[0]);
	put_unaligned_be16(nn_readw(nn, NFP_NET_CFG_MACADDR + 6), &mac_addr[4]);

	if (!is_valid_ether_addr(mac_addr)) {
		eth_hw_addr_random(nn->dp.netdev);
		return;
	}

	ether_addr_copy(nn->dp.netdev->dev_addr, mac_addr);
	ether_addr_copy(nn->dp.netdev->perm_addr, mac_addr);
}

static int nfp_netvf_pci_probe(struct pci_dev *pdev,
			       const struct pci_device_id *pci_id)
{
	struct nfp_net_fw_version fw_ver;
	int max_tx_rings, max_rx_rings;
	u32 tx_bar_off, rx_bar_off;
	u32 tx_bar_sz, rx_bar_sz;
	int tx_bar_no, rx_bar_no;
	struct nfp_net_vf *vf;
	unsigned int num_irqs;
	u8 __iomem *ctrl_bar;
	struct nfp_net *nn;
	u32 startq;
	int stride;
	int err;

	vf = kzalloc(sizeof(*vf), GFP_KERNEL);
	if (!vf)
		return -ENOMEM;
	pci_set_drvdata(pdev, vf);

	err = pci_enable_device_mem(pdev);
	if (err)
		goto err_free_vf;

	err = pci_request_regions(pdev, nfp_net_driver_name);
	if (err) {
		dev_err(&pdev->dev, "Unable to allocate device memory.\n");
		goto err_pci_disable;
	}

	pci_set_master(pdev);

	err = dma_set_mask_and_coherent(&pdev->dev,
					DMA_BIT_MASK(NFP_NET_MAX_DMA_BITS));
	if (err)
		goto err_pci_regions;

	/* Map the Control BAR.
	 *
	 * Irrespective of the advertised BAR size we only map the
	 * first NFP_NET_CFG_BAR_SZ of the BAR.  This keeps the code
	 * the identical for PF and VF drivers.
	 */
	ctrl_bar = ioremap_nocache(pci_resource_start(pdev, NFP_NET_CTRL_BAR),
				   NFP_NET_CFG_BAR_SZ);
	if (!ctrl_bar) {
		dev_err(&pdev->dev,
			"Failed to map resource %d\n", NFP_NET_CTRL_BAR);
		err = -EIO;
		goto err_pci_regions;
	}

	nfp_net_get_fw_version(&fw_ver, ctrl_bar);
	if (fw_ver.resv || fw_ver.class != NFP_NET_CFG_VERSION_CLASS_GENERIC) {
		dev_err(&pdev->dev, "Unknown Firmware ABI %d.%d.%d.%d\n",
			fw_ver.resv, fw_ver.class, fw_ver.major, fw_ver.minor);
		err = -EINVAL;
		goto err_ctrl_unmap;
	}

	/* Determine stride */
	if (nfp_net_fw_ver_eq(&fw_ver, 0, 0, 0, 1)) {
		stride = 2;
		tx_bar_no = NFP_NET_Q0_BAR;
		rx_bar_no = NFP_NET_Q1_BAR;
		dev_warn(&pdev->dev, "OBSOLETE Firmware detected - VF isolation not available\n");
	} else {
		switch (fw_ver.major) {
		case 1 ... 5:
			stride = 4;
			tx_bar_no = NFP_NET_Q0_BAR;
			rx_bar_no = tx_bar_no;
			break;
		default:
			dev_err(&pdev->dev, "Unsupported Firmware ABI %d.%d.%d.%d\n",
				fw_ver.resv, fw_ver.class,
				fw_ver.major, fw_ver.minor);
			err = -EINVAL;
			goto err_ctrl_unmap;
		}
	}

	/* Find out how many rings are supported */
	max_tx_rings = readl(ctrl_bar + NFP_NET_CFG_MAX_TXRINGS);
	max_rx_rings = readl(ctrl_bar + NFP_NET_CFG_MAX_RXRINGS);

	tx_bar_sz = NFP_QCP_QUEUE_ADDR_SZ * max_tx_rings * stride;
	rx_bar_sz = NFP_QCP_QUEUE_ADDR_SZ * max_rx_rings * stride;

	/* Sanity checks */
	if (tx_bar_sz > pci_resource_len(pdev, tx_bar_no)) {
		dev_err(&pdev->dev,
			"TX BAR too small for number of TX rings. Adjusting\n");
		tx_bar_sz = pci_resource_len(pdev, tx_bar_no);
		max_tx_rings = (tx_bar_sz / NFP_QCP_QUEUE_ADDR_SZ) / 2;
	}
	if (rx_bar_sz > pci_resource_len(pdev, rx_bar_no)) {
		dev_err(&pdev->dev,
			"RX BAR too small for number of RX rings. Adjusting\n");
		rx_bar_sz = pci_resource_len(pdev, rx_bar_no);
		max_rx_rings = (rx_bar_sz / NFP_QCP_QUEUE_ADDR_SZ) / 2;
	}

	startq = readl(ctrl_bar + NFP_NET_CFG_START_TXQ);
	tx_bar_off = NFP_PCIE_QUEUE(startq);
	startq = readl(ctrl_bar + NFP_NET_CFG_START_RXQ);
	rx_bar_off = NFP_PCIE_QUEUE(startq);

	/* Allocate and initialise the netdev */
	nn = nfp_net_alloc(pdev, ctrl_bar, true, max_tx_rings, max_rx_rings);
	if (IS_ERR(nn)) {
		err = PTR_ERR(nn);
		goto err_ctrl_unmap;
	}
	vf->nn = nn;

	nn->fw_ver = fw_ver;
	nn->dp.is_vf = 1;
	nn->stride_tx = stride;
	nn->stride_rx = stride;

	if (rx_bar_no == tx_bar_no) {
		u32 bar_off, bar_sz;
		resource_size_t map_addr;

		/* Make a single overlapping BAR mapping */
		if (tx_bar_off < rx_bar_off)
			bar_off = tx_bar_off;
		else
			bar_off = rx_bar_off;

		if ((tx_bar_off + tx_bar_sz) > (rx_bar_off + rx_bar_sz))
			bar_sz = (tx_bar_off + tx_bar_sz) - bar_off;
		else
			bar_sz = (rx_bar_off + rx_bar_sz) - bar_off;

		map_addr = pci_resource_start(pdev, tx_bar_no) + bar_off;
		vf->q_bar = ioremap_nocache(map_addr, bar_sz);
		if (!vf->q_bar) {
			nn_err(nn, "Failed to map resource %d\n", tx_bar_no);
			err = -EIO;
			goto err_netdev_free;
		}

		/* TX queues */
		nn->tx_bar = vf->q_bar + (tx_bar_off - bar_off);
		/* RX queues */
		nn->rx_bar = vf->q_bar + (rx_bar_off - bar_off);
	} else {
		resource_size_t map_addr;

		/* TX queues */
		map_addr = pci_resource_start(pdev, tx_bar_no) + tx_bar_off;
		nn->tx_bar = ioremap_nocache(map_addr, tx_bar_sz);
		if (!nn->tx_bar) {
			nn_err(nn, "Failed to map resource %d\n", tx_bar_no);
			err = -EIO;
			goto err_netdev_free;
		}

		/* RX queues */
		map_addr = pci_resource_start(pdev, rx_bar_no) + rx_bar_off;
		nn->rx_bar = ioremap_nocache(map_addr, rx_bar_sz);
		if (!nn->rx_bar) {
			nn_err(nn, "Failed to map resource %d\n", rx_bar_no);
			err = -EIO;
			goto err_unmap_tx;
		}
	}

	nfp_netvf_get_mac_addr(nn);

	num_irqs = nfp_net_irqs_alloc(pdev, vf->irq_entries,
				      NFP_NET_MIN_VNIC_IRQS,
				      NFP_NET_NON_Q_VECTORS +
				      nn->dp.num_r_vecs);
	if (!num_irqs) {
		nn_warn(nn, "Unable to allocate MSI-X Vectors. Exiting\n");
		err = -EIO;
		goto err_unmap_rx;
	}
	nfp_net_irqs_assign(nn, vf->irq_entries, num_irqs);

	err = nfp_net_init(nn);
	if (err)
		goto err_irqs_disable;

	nfp_net_info(nn);
	vf->ddir = nfp_net_debugfs_device_add(pdev);
	nfp_net_debugfs_vnic_add(nn, vf->ddir);

	return 0;

err_irqs_disable:
	nfp_net_irqs_disable(pdev);
err_unmap_rx:
	if (!vf->q_bar)
		iounmap(nn->rx_bar);
err_unmap_tx:
	if (!vf->q_bar)
		iounmap(nn->tx_bar);
	else
		iounmap(vf->q_bar);
err_netdev_free:
	nfp_net_free(nn);
err_ctrl_unmap:
	iounmap(ctrl_bar);
err_pci_regions:
	pci_release_regions(pdev);
err_pci_disable:
	pci_disable_device(pdev);
err_free_vf:
	pci_set_drvdata(pdev, NULL);
	kfree(vf);
	return err;
}

static void nfp_netvf_pci_remove(struct pci_dev *pdev)
{
	struct nfp_net_vf *vf = pci_get_drvdata(pdev);
	struct nfp_net *nn = vf->nn;

	/* Note, the order is slightly different from above as we need
	 * to keep the nn pointer around till we have freed everything.
	 */
	nfp_net_debugfs_dir_clean(&nn->debugfs_dir);
	nfp_net_debugfs_dir_clean(&vf->ddir);

	nfp_net_clean(nn);

	nfp_net_irqs_disable(pdev);

	if (!vf->q_bar) {
		iounmap(nn->rx_bar);
		iounmap(nn->tx_bar);
	} else {
		iounmap(vf->q_bar);
	}
	iounmap(nn->dp.ctrl_bar);

	nfp_net_free(nn);

	pci_release_regions(pdev);
	pci_disable_device(pdev);

	pci_set_drvdata(pdev, NULL);
	kfree(vf);
}

struct pci_driver nfp_netvf_pci_driver = {
	.name        = nfp_net_driver_name,
	.id_table    = nfp_netvf_pci_device_ids,
	.probe       = nfp_netvf_pci_probe,
	.remove      = nfp_netvf_pci_remove,
};