xref: /illumos-gate/usr/src/uts/common/io/bnxe/bnxe_illumos.c (revision fc910014e8a32a65612105835a10995f2c13d942)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright (c) 2017, Joyent, Inc.
14  * Copyright 2023 Oxide Computer Company
15  */
16 
17 /*
18  * illumos specific bnxe related functions.
19  */
20 
21 #include "bnxe.h"
22 
23 /*
24  * Try to figure out which phy we should be using at this time based on the
25  * requested transceiver.
26  */
27 static uint_t
28 bnxe_get_phy_id(um_device_t *um)
29 {
30 	if (um->lm_dev.params.link.num_phys <= 1)
31 		return (ELINK_INT_PHY);
32 
33 	if (um->lm_dev.vars.link.link_up) {
34 		if ((um->lm_dev.vars.link.link_status &
35 		    LINK_STATUS_SERDES_LINK) &&
36 		    (um->lm_dev.params.link.phy[ELINK_EXT_PHY2].supported &
37 		    ELINK_SUPPORTED_FIBRE))
38 			return (ELINK_EXT_PHY2);
39 		return (ELINK_EXT_PHY1);
40 	} else {
41 		switch (elink_phy_selection(&um->lm_dev.params.link)) {
42 		case PORT_HW_CFG_PHY_SELECTION_HARDWARE_DEFAULT:
43 		case PORT_HW_CFG_PHY_SELECTION_FIRST_PHY:
44 		case PORT_HW_CFG_PHY_SELECTION_FIRST_PHY_PRIORITY:
45 			return (ELINK_EXT_PHY1);
46 		case PORT_HW_CFG_PHY_SELECTION_SECOND_PHY:
47 		case PORT_HW_CFG_PHY_SELECTION_SECOND_PHY_PRIORITY:
48 			return (ELINK_EXT_PHY2);
49 		/*
50 		 * The above hardware types are the only ones currently defined
51 		 * by the specification and common code. If we end up with an
52 		 * unknown value, then we default to what the hardware considers
53 		 * the default, which is PHY1.
54 		 */
55 		default:
56 			return (ELINK_EXT_PHY1);
57 		}
58 	}
59 }
60 
61 /*
62  * This media map table and structure is shared across the different pluggable
63  * modules. The driver doesn't really look carefully at the difference between
64  * the various multi-speed modules which is why for 10G based pieces we also
65  * have lower speed based checks.
66  */
67 typedef struct {
68 	uint32_t bmm_sfp;
69 	uint32_t bmm_speed;
70 	mac_ether_media_t bmm_media;
71 } bnxe_media_map_t;
72 
73 static const bnxe_media_map_t bnxe_media_map[] = {
74 	{ ELINK_ETH_SFP_10GBASE_SR, 10000, ETHER_MEDIA_10GBASE_SR },
75 	{ ELINK_ETH_SFP_10GBASE_SR, 1000, ETHER_MEDIA_1000BASE_SX },
76 	{ ELINK_ETH_SFP_1GBASE_SX, 1000, ETHER_MEDIA_1000BASE_SX },
77 	{ ELINK_ETH_SFP_10GBASE_LR, 10000, ETHER_MEDIA_10GBASE_LR },
78 	{ ELINK_ETH_SFP_10GBASE_LR, 1000, ETHER_MEDIA_1000BASE_LX },
79 	{ ELINK_ETH_SFP_1GBASE_LX, 1000, ETHER_MEDIA_1000BASE_LX },
80 	{ ELINK_ETH_SFP_10GBASE_LRM, 10000, ETHER_MEDIA_10GBASE_LRM },
81 	{ ELINK_ETH_SFP_10GBASE_ER, 10000, ETHER_MEDIA_10GBASE_ER },
82 	{ ELINK_ETH_SFP_1GBASE_T, 1000, ETHER_MEDIA_1000BASE_T },
83 	{ ELINK_ETH_SFP_1GBASE_CX, 1000, ETHER_MEDIA_1000BASE_CX },
84 	{ ELINK_ETH_SFP_DAC, 10000, ETHER_MEDIA_10GBASE_CR },
85 	{ ELINK_ETH_SFP_ACC, 10000, ETHER_MEDIA_10GBASE_ACC },
86 };
87 
88 mac_ether_media_t
89 bnxe_phy_to_media(um_device_t *um)
90 {
91 	uint_t phyid;
92 	struct elink_params *params;
93 	struct elink_phy *phy;
94 	mac_ether_media_t media = ETHER_MEDIA_UNKNOWN;
95 
96 	BNXE_LOCK_ENTER_PHY(um);
97 	phyid = bnxe_get_phy_id(um);
98 	params = &um->lm_dev.params.link;
99 	phy = &params->phy[phyid];
100 
101 	switch (phy->media_type) {
102 	/*
103 	 * Right now the driver does not ask the XFP i2c entity to determine the
104 	 * media information. If we encounter someone with an XFP device then we
105 	 * can add logic to the driver to cover proper detection, but otherwise
106 	 * it would fit into this same set of modes.
107 	 */
108 	case ELINK_ETH_PHY_SFPP_10G_FIBER:
109 	case ELINK_ETH_PHY_XFP_FIBER:
110 	case ELINK_ETH_PHY_DA_TWINAX:
111 	case ELINK_ETH_PHY_SFP_1G_FIBER:
112 		for (size_t i = 0; i < ARRAY_SIZE(bnxe_media_map); i++) {
113 			const bnxe_media_map_t *map = &bnxe_media_map[i];
114 			if (phy->sfp_media == map->bmm_sfp &&
115 			    um->props.link_speed == map->bmm_speed) {
116 				media = map->bmm_media;
117 				break;
118 			}
119 		}
120 		break;
121 	case ELINK_ETH_PHY_BASE_T:
122 		switch (um->props.link_speed) {
123 		case 10:
124 			media = ETHER_MEDIA_10BASE_T;
125 			break;
126 		case 100:
127 			media = ETHER_MEDIA_100BASE_TX;
128 			break;
129 		case 1000:
130 			media = ETHER_MEDIA_1000BASE_T;
131 			break;
132 		case 10000:
133 			media = ETHER_MEDIA_10GBASE_T;
134 			break;
135 		default:
136 			break;
137 		}
138 		break;
139 	case ELINK_ETH_PHY_KR:
140 		switch (um->props.link_speed) {
141 		case 1000:
142 			media = ETHER_MEDIA_1000BASE_KX;
143 			break;
144 		case 10000:
145 			media = ETHER_MEDIA_10GBASE_KR;
146 			break;
147 		default:
148 			break;
149 		}
150 		break;
151 	case ELINK_ETH_PHY_CX4:
152 		if (um->props.link_speed == 10000) {
153 			media = ETHER_MEDIA_10GBASE_CX4;
154 		}
155 		break;
156 	case ELINK_ETH_PHY_NOT_PRESENT:
157 		media = ETHER_MEDIA_NONE;
158 		break;
159 	case ELINK_ETH_PHY_UNSPECIFIED:
160 	default:
161 		media = ETHER_MEDIA_UNKNOWN;
162 		break;
163 	}
164 
165 	BNXE_LOCK_EXIT_PHY(um);
166 	return (media);
167 }
168 
169 
170 static int
171 bnxe_transceiver_info(void *arg, uint_t id, mac_transceiver_info_t *infop)
172 {
173 	uint_t phyid;
174 	um_device_t *um = arg;
175 	struct elink_params *params;
176 	struct elink_phy *phy;
177 	boolean_t present = B_FALSE, usable = B_FALSE;
178 	elink_status_t ret;
179 	uint8_t buf;
180 
181 	if (id != 0 || arg == NULL || infop == NULL)
182 		return (EINVAL);
183 
184 	BNXE_LOCK_ENTER_PHY(um);
185 	phyid = bnxe_get_phy_id(um);
186 	params = &um->lm_dev.params.link;
187 	phy = &params->phy[phyid];
188 
189 	switch (phy->media_type) {
190 	case ELINK_ETH_PHY_SFPP_10G_FIBER:
191 	case ELINK_ETH_PHY_DA_TWINAX:
192 	case ELINK_ETH_PHY_SFP_1G_FIBER:
193 		break;
194 	default:
195 		BNXE_LOCK_EXIT_PHY(um);
196 		return (ENOTSUP);
197 	}
198 
199 	/*
200 	 * Right now, the core OS-independent code from QLogic doesn't quite
201 	 * track whether or not the phy is plugged in, though it easily could.
202 	 * As such, the best way to determine whether or not the phy is present
203 	 * is to see if we can read the first byte from page 0xa0. We expect to
204 	 * get an explicit timeout if the device isn't present. We'll propagate
205 	 * EIO on any other error as we're not in a good state to understand
206 	 * what happened.
207 	 */
208 	PHY_HW_LOCK(&um->lm_dev);
209 	ret = elink_read_sfp_module_eeprom(phy, params, 0xa0, 0, sizeof (buf),
210 	    &buf);
211 	PHY_HW_UNLOCK(&um->lm_dev);
212 	if (ret != ELINK_STATUS_OK && ret != ELINK_STATUS_TIMEOUT) {
213 		BNXE_LOCK_EXIT_PHY(um);
214 		return (EIO);
215 	}
216 	if (ret == ELINK_STATUS_OK) {
217 		present = B_TRUE;
218 		if ((phy->flags & ELINK_FLAGS_SFP_NOT_APPROVED) == 0)
219 			usable = B_TRUE;
220 	}
221 	BNXE_LOCK_EXIT_PHY(um);
222 
223 	mac_transceiver_info_set_present(infop, present);
224 	mac_transceiver_info_set_usable(infop, usable);
225 
226 	return (0);
227 }
228 
229 static int
230 bnxe_transceiver_read(void *arg, uint_t id, uint_t page, void *bp,
231     size_t nbytes, off_t offset, size_t *nread)
232 {
233 	uint_t phyid;
234 	um_device_t *um = arg;
235 	struct elink_phy *phy;
236 	struct elink_params *params;
237 	elink_status_t ret;
238 
239 	if (id != 0 || bp == NULL || nbytes == 0 || nread == NULL ||
240 	    (page != 0xa0 && page != 0xa2) || offset < 0)
241 		return (EINVAL);
242 
243 	/*
244 	 * Sanity check length params.
245 	 */
246 	if (nbytes > 256 || offset >= 256 || (offset + nbytes > 256)) {
247 		return (EINVAL);
248 	}
249 
250 	BNXE_LOCK_ENTER_PHY(um);
251 	phyid = bnxe_get_phy_id(um);
252 	params = &um->lm_dev.params.link;
253 	phy = &um->lm_dev.params.link.phy[phyid];
254 
255 	switch (phy->media_type) {
256 	case ELINK_ETH_PHY_SFPP_10G_FIBER:
257 	case ELINK_ETH_PHY_DA_TWINAX:
258 	case ELINK_ETH_PHY_SFP_1G_FIBER:
259 		break;
260 	default:
261 		BNXE_LOCK_EXIT_PHY(um);
262 		return (ENOTSUP);
263 	}
264 
265 	PHY_HW_LOCK(&um->lm_dev);
266 	ret = elink_read_sfp_module_eeprom(phy, params, (uint8_t)page,
267 	    (uint16_t)offset, (uint16_t)nbytes, bp);
268 	PHY_HW_UNLOCK(&um->lm_dev);
269 
270 	BNXE_LOCK_EXIT_PHY(um);
271 
272 	switch (ret) {
273 	case ELINK_STATUS_OK:
274 		*nread = nbytes;
275 		return (0);
276 	case ELINK_OP_NOT_SUPPORTED:
277 		return (ENOTSUP);
278 	default:
279 		return (EIO);
280 	}
281 }
282 
283 boolean_t
284 bnxe_fill_transceiver(um_device_t *um, void *arg)
285 {
286 	uint_t ntran = 1;
287 	mac_capab_transceiver_t *mct = arg;
288 
289 	mct->mct_flags = 0;
290 	/*
291 	 * While there is nominally a dual-phy version of bnxe out there (see
292 	 * ELINK_DUAL_MEDIA and related macros), these haven't been seen in the
293 	 * wild. For now, only assume that we have a single phy.
294 	 */
295 	mct->mct_ntransceivers = 1;
296 	mct->mct_info = bnxe_transceiver_info;
297 	mct->mct_read = bnxe_transceiver_read;
298 
299 	return (B_TRUE);
300 }
301