1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <sys/stat.h>
27#include <sys/types.h>
28#include <netinet/in.h>
29#include <sys/inttypes.h>
30#include <sys/strsun.h>
31#include <sys/mac_client.h>
32
33/*
34 * FCoE header files
35 */
36#include <sys/fcoe/fcoeio.h>
37#include <sys/fcoe/fcoe_common.h>
38
39/*
40 * Driver's own header files
41 */
42#include <fcoe.h>
43#include <fcoe_eth.h>
44#include <fcoe_fc.h>
45
46static void fcoe_rx(void *arg, mac_resource_handle_t mrh,
47    mblk_t *mp, boolean_t loopback);
48static void fcoe_mac_notify(void *arg, mac_notify_type_t type);
49
50/*
51 * Global variable definitions
52 */
53
54/*
55 * Internal tunable, used to enable p2p mode
56 */
57volatile uint32_t	fcoe_enable_p2pmode = 0;
58
59int
60fcoe_open_mac(fcoe_mac_t *mac, int force_promisc, fcoeio_stat_t *err_detail)
61{
62	int		ret;
63	int		fcoe_ret;
64	char		cli_name[MAXNAMELEN];
65	mac_diag_t	diag;
66	uint16_t	fm_open_flag = 0;
67
68	*err_detail = 0;
69
70	/*
71	 * Open MAC interface
72	 */
73	ret = mac_open_by_linkid(mac->fm_linkid, &mac->fm_handle);
74	if (ret != 0) {
75		FCOE_LOG("fcoe", "mac_open_by_linkname %d failed %x",
76		    mac->fm_linkid, ret);
77		return (FCOE_FAILURE);
78	}
79
80	(void) sprintf(cli_name, "%s-%d", "fcoe", mac->fm_linkid);
81
82	ret = mac_client_open(mac->fm_handle,
83	    &mac->fm_cli_handle, cli_name, fm_open_flag);
84	if (ret != 0) {
85		(void) fcoe_close_mac(mac);
86		return (FCOE_FAILURE);
87	}
88	/*
89	 * Cache the pointer of the immutable MAC inforamtion and
90	 * the current and primary MAC address
91	 */
92	mac_unicast_primary_get(mac->fm_handle, mac->fm_primary_addr);
93	bcopy(mac->fm_primary_addr, mac->fm_current_addr,
94	    ETHERADDRL);
95
96	if (mac_unicast_add(mac->fm_cli_handle, NULL, MAC_UNICAST_PRIMARY,
97	    &mac->fm_unicst_handle, 0, &diag)) {
98		(void) fcoe_close_mac(mac);
99		return (FCOE_FAILURE);
100	}
101
102	if (force_promisc) {
103		mac->fm_force_promisc = B_TRUE;
104	}
105
106	/* Get mtu */
107	mac_sdu_get(mac->fm_handle, NULL, &mac->fm_eport.eport_mtu);
108	if (mac->fm_eport.eport_mtu < FCOE_MIN_MTU_SIZE) {
109		if (!fcoe_enable_p2pmode || mac->fm_eport.eport_mtu < 1500) {
110			/*
111			 * Fail open if fail to get mtu, or we are not
112			 * using p2p, or we are using p2p, but
113			 * the mtu is too small
114			 */
115			(void) fcoe_close_mac(mac);
116			*err_detail = FCOEIOE_NEED_JUMBO_FRAME;
117			return (FCOE_FAILURE);
118		}
119	}
120
121	mac->fm_eport.eport_link_speed =
122	    mac_client_stat_get(mac->fm_cli_handle, MAC_STAT_IFSPEED);
123
124	cv_init(&mac->fm_tx_cv, NULL, CV_DRIVER, NULL);
125	mutex_init(&mac->fm_mutex, NULL, MUTEX_DRIVER, NULL);
126	mac->fm_running = B_TRUE;
127
128	fcoe_ret = FCOE_SUCCESS;
129	return (fcoe_ret);
130}
131
132int
133fcoe_close_mac(fcoe_mac_t *mac)
134{
135	int ret;
136
137	if (mac->fm_handle == NULL) {
138		return (FCOE_SUCCESS);
139	}
140
141	if (mac->fm_running) {
142		cv_destroy(&mac->fm_tx_cv);
143		mutex_destroy(&mac->fm_mutex);
144		mac->fm_running = B_FALSE;
145	}
146
147	if (mac->fm_promisc_handle != NULL) {
148		mac_promisc_remove(mac->fm_promisc_handle);
149		mac->fm_promisc_handle = NULL;
150	} else {
151		mac_rx_clear(mac->fm_cli_handle);
152	}
153
154	if (mac->fm_notify_handle != NULL) {
155		ret = mac_notify_remove(mac->fm_notify_handle, B_TRUE);
156		ASSERT(ret == 0);
157		mac->fm_notify_handle = NULL;
158	}
159
160	if (mac->fm_unicst_handle != NULL) {
161		(void) mac_unicast_remove(mac->fm_cli_handle,
162		    mac->fm_unicst_handle);
163		mac->fm_unicst_handle = NULL;
164	}
165
166	mac_client_close(mac->fm_cli_handle, 0);
167	mac->fm_cli_handle = NULL;
168
169	(void) mac_close(mac->fm_handle);
170	mac->fm_handle = NULL;
171
172	return (FCOE_SUCCESS);
173}
174
175int
176fcoe_enable_callback(fcoe_mac_t *mac)
177{
178	int ret;
179
180	/*
181	 * Set message callback
182	 */
183	if (mac->fm_force_promisc) {
184		ret = mac_promisc_add(mac->fm_cli_handle,
185		    MAC_CLIENT_PROMISC_FILTERED, fcoe_rx, mac,
186		    &mac->fm_promisc_handle,
187		    MAC_PROMISC_FLAGS_NO_TX_LOOP);
188		if (ret != 0) {
189			FCOE_LOG("foce", "mac_promisc_add on %d failed %x",
190			    mac->fm_linkid, ret);
191			return (FCOE_FAILURE);
192		}
193	} else {
194		mac_rx_set(mac->fm_cli_handle, fcoe_rx, mac);
195	}
196
197	/* Get the link state, if it's up, we will need to notify client */
198	mac->fm_link_state =
199	    mac_stat_get(mac->fm_handle, MAC_STAT_LINK_UP)?
200	    FCOE_MAC_LINK_STATE_UP:FCOE_MAC_LINK_STATE_DOWN;
201
202	mac->fm_eport.eport_link_speed =
203	    mac_client_stat_get(mac->fm_cli_handle, MAC_STAT_IFSPEED);
204
205	/*
206	 * Add a notify function so that we get updates from MAC
207	 */
208	mac->fm_notify_handle = mac_notify_add(mac->fm_handle,
209	    fcoe_mac_notify, (void *)mac);
210	return (FCOE_SUCCESS);
211}
212
213int
214fcoe_disable_callback(fcoe_mac_t *mac)
215{
216	int ret;
217
218	if (mac->fm_promisc_handle) {
219		mac_promisc_remove(mac->fm_promisc_handle);
220		mac->fm_promisc_handle = NULL;
221	} else {
222		mac_rx_clear(mac->fm_cli_handle);
223	}
224
225	if (mac->fm_notify_handle) {
226		ret = mac_notify_remove(mac->fm_notify_handle, B_TRUE);
227		ASSERT(ret == 0);
228		mac->fm_notify_handle = NULL;
229	}
230
231	ret = fcoe_mac_set_address(&mac->fm_eport,
232	    mac->fm_primary_addr, B_FALSE);
233	FCOE_SET_DEFAULT_FPORT_ADDR(mac->fm_eport.eport_efh_dst);
234	return (ret);
235}
236
237/* ARGSUSED */
238static void
239fcoe_rx(void *arg, mac_resource_handle_t mrh, mblk_t *mp, boolean_t loopback)
240{
241	fcoe_mac_t	*mac = (fcoe_mac_t *)arg;
242	mblk_t		*next;
243	fcoe_frame_t	*frm;
244	uint32_t	raw_frame_size, frame_size;
245	uint16_t	frm_type;
246
247	while (mp != NULL) {
248		next = mp->b_next;
249		mp->b_next = NULL;
250		frm_type = ntohs(*(uint16_t *)((uintptr_t)mp->b_rptr + 12));
251
252		if (frm_type != ETHERTYPE_FCOE) {
253			/*
254			 * This mp is not allocated in FCoE, but we must free it
255			 */
256			freeb(mp);
257			mp = next;
258			continue;
259		}
260
261		raw_frame_size = MBLKL(mp);
262		frame_size = raw_frame_size - PADDING_SIZE;
263		frm = fcoe_allocate_frame(&mac->fm_eport, frame_size, mp);
264		if (frm != NULL) {
265			frm->frm_clock = CURRENT_CLOCK;
266			fcoe_post_frame(frm);
267		}
268
269		mp = next;
270	}
271}
272
273static void
274fcoe_mac_notify(void *arg, mac_notify_type_t type)
275{
276	fcoe_mac_t *mac = (fcoe_mac_t *)arg;
277
278	/*
279	 * We assume that the calls to this notification callback are serialized
280	 * by MAC layer
281	 */
282
283	switch (type) {
284	case MAC_NOTE_LINK:
285		/*
286		 * This notification is sent every time the MAC driver
287		 * updates the link state.
288		 */
289		if (mac_stat_get(mac->fm_handle, MAC_STAT_LINK_UP) != 0) {
290			if (mac->fm_link_state == FCOE_MAC_LINK_STATE_UP) {
291				break;
292			}
293			/* Get speed */
294			mac->fm_eport.eport_link_speed =
295			    mac_client_stat_get(mac->fm_cli_handle,
296			    MAC_STAT_IFSPEED);
297			(void) fcoe_mac_set_address(&mac->fm_eport,
298			    mac->fm_primary_addr, B_FALSE);
299
300			FCOE_SET_DEFAULT_FPORT_ADDR(
301			    mac->fm_eport.eport_efh_dst);
302
303			mac->fm_link_state = FCOE_MAC_LINK_STATE_UP;
304			FCOE_LOG(NULL,
305			    "fcoe_mac_notify: link/%d arg/%p LINK up",
306			    mac->fm_linkid, arg, type);
307			fcoe_mac_notify_link_up(mac);
308		} else {
309			if (mac->fm_link_state == FCOE_MAC_LINK_STATE_DOWN) {
310				break;
311			}
312			mac->fm_link_state = FCOE_MAC_LINK_STATE_DOWN;
313			FCOE_LOG(NULL,
314			    "fcoe_mac_notify: link/%d arg/%p LINK down",
315			    mac->fm_linkid, arg, type);
316			fcoe_mac_notify_link_down(mac);
317		}
318		break;
319
320	case MAC_NOTE_TX:
321		/*
322		 * MAC is not so busy now, then wake up fcoe_tx_frame to try
323		 */
324		mutex_enter(&mac->fm_mutex);
325		cv_broadcast(&mac->fm_tx_cv);
326		mutex_exit(&mac->fm_mutex);
327
328		FCOE_LOG("fcoe_mac_notify", "wake up");
329		break;
330
331	default:
332		FCOE_LOG("fcoe_mac_notify", "not supported arg/%p, type/%d",
333		    arg, type);
334		break;
335	}
336}
337
338int
339fcoe_mac_set_address(fcoe_port_t *eport, uint8_t *addr, boolean_t fc_assigned)
340{
341	fcoe_mac_t	*mac = EPORT2MAC(eport);
342	int		ret;
343
344	if (bcmp(addr, mac->fm_current_addr, 6) == 0) {
345		return (FCOE_SUCCESS);
346	}
347
348	mutex_enter(&mac->fm_mutex);
349	if (mac->fm_promisc_handle == NULL) {
350		ret = mac_unicast_primary_set(mac->fm_handle, addr);
351		if (ret != 0) {
352			mutex_exit(&mac->fm_mutex);
353			FCOE_LOG("fcoe", "mac_unicast_primary_set on %d "
354			    "failed %x", mac->fm_linkid, ret);
355			return (FCOE_FAILURE);
356		}
357	}
358	if (fc_assigned) {
359		bcopy(addr, mac->fm_current_addr, ETHERADDRL);
360	} else {
361		bcopy(mac->fm_primary_addr,
362		    mac->fm_current_addr, ETHERADDRL);
363	}
364	mutex_exit(&mac->fm_mutex);
365	return (FCOE_SUCCESS);
366}
367