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
27
28#include "HBA.h"
29#include "Exceptions.h"
30#include "Trace.h"
31#include <iostream>
32#include <iomanip>
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <time.h>
36#include <fcntl.h>
37#include <unistd.h>
38#include <stropts.h>
39#include <errno.h>
40#include <climits>
41#include <cstring>
42
43#define	    NSECS_PER_SEC	1000000000l
44#define	    BUSY_SLEEP		NSECS_PER_SEC/10 /* 1/10 second */
45#define	    BUSY_RETRY_TIMER	3000000000UL /* Retry for 3 seconds */
46
47using namespace std;
48
49/**
50 * Max number of Adatper ports per HBA that VSL supports.
51 *
52 */
53const uint8_t HBA::HBA_PORT_MAX = UCHAR_MAX;
54
55/**
56 * @memo	    Add a new port to this HBA
57 * @precondition    Port must be a valid port on this HBA
58 * @postcondition   Port will be exposed as one of the ports on this HBA
59 * @exception	    Throws InternalError when the HBA port count exceeds
60 *		    max number of ports and throws any underlying exception
61 * @param	    port The Port to add to this HBA
62 *
63 * @doc		    When discovering HBAs and their ports, use this
64 *		    routine to add a port to its existing HBA instance.
65 */
66void HBA::addPort(HBAPort* port) {
67	Trace log("HBA::addPort");
68	lock();
69	// support hba with up to UCHAR_MAX number of ports.
70	if (portsByIndex.size() + 1 > HBA_PORT_MAX) {
71	    unlock();
72	    throw InternalError("HBA Port count exceeds max number of ports");
73	}
74
75	try {
76	    portsByWWN[port->getPortWWN()] = port;
77	    portsByIndex.insert(portsByIndex.end(), port);
78	    unlock();
79	} catch (...) {
80	    unlock();
81	    throw;
82	}
83}
84
85/**
86 * @memo	    Return number of ports to this HBA
87 * @exception	    No exception for this method.
88 *
89 * @doc		    Returns the number of ports on this HBA. The max
90 *		    number of ports that VSL support is up to max uint8_t
91 *		    size.
92 */
93uint8_t HBA::getNumberOfPorts() {
94	Trace log("HBA::getNumberOfPorts");
95	return (uint8_t)portsByIndex.size();
96}
97
98/**
99 * @memo	    Retrieve an HBA port based on a Port WWN
100 * @exception	    IllegalWWNException Thrown if WWN does not match any
101 *		    known HBA port.
102 * @return	    HBAPort* to the port with a matching Port WWN
103 * @param	    wwn The wwn of the desired HBA port
104 *
105 * @doc		    Fetch an HBA port based on WWN.  If the port is not
106 *		    found, an exception will be thrown.  NULL will never
107 *		    be returned.
108 */
109HBAPort* HBA::getPort(uint64_t wwn) {
110	Trace log("HBA::getPort");
111	HBAPort *port = NULL;
112	lock();
113
114	log.debug("getPort(wwn): WWN %016llx", wwn);
115
116	try {
117	    // Make sure it is in the map
118	    if (portsByWWN.find(wwn) == portsByWWN.end()) {
119		throw IllegalWWNException();
120	    }
121	    port = portsByWWN[wwn];
122	    unlock();
123	    return (port);
124	} catch (...) {
125	    unlock();
126	    throw;
127	}
128}
129
130/**
131 * Iterator for WWN to HBAPort map type
132 */
133typedef map<uint64_t, HBAPort *>::const_iterator CI;
134
135/**
136 * @memo	    Return true if this HBA contains the stated WWN
137 *		    (node or port)
138 * @exception	    ... underlying exceptions will be thrown
139 * @return	    TRUE if the wwn is found
140 * @return	    FALSE if the wwn is not found
141 * @param	    wwn The wwn to look for
142 *
143 */
144bool HBA::containsWWN(uint64_t wwn) {
145	Trace log("HBA::containsWWN");
146	lock();
147
148	try {
149	    for (CI port = portsByWWN.begin(); port != portsByWWN.end();
150		    port++) {
151		if (port->second->getPortWWN() == wwn) {
152		    unlock();
153		    return (true);
154		}
155		if (port->second->getNodeWWN() == wwn) {
156		    unlock();
157		    return (true);
158		}
159	    }
160	    unlock();
161	    return (false);
162	} catch (...) {
163	    unlock();
164	    throw;
165	}
166}
167
168/**
169 * @memo	    Fetch the port based on index.
170 * @exception	    IllegalIndexException Thrown if the index is not valid
171 * @return	    HBAPort* the port matching the index
172 * @param	    index - the zero based index of the port to retrieve
173 *
174 */
175HBAPort* HBA::getPortByIndex(int index) {
176	Trace log("HBA::getPortByIndex");
177	lock();
178	try {
179	    log.debug("Port index size %d index %d ", portsByIndex.size(),
180		    index);
181
182	    if (index >= portsByIndex.size() || index < 0) {
183		throw IllegalIndexException();
184	    }
185
186	    HBAPort *tmp = portsByIndex[index];
187	    unlock();
188	    return (tmp);
189	} catch (...) {
190	    unlock();
191	    throw;
192	}
193}
194
195/**
196 * @memo	    Compare two HBAs for equality
197 * @precondition    Both HBAs should be fully discovered (all ports added)
198 * @exception	    ... underlying exceptions will be thrown
199 * @return	    TRUE The two HBA instances represent the same HBA
200 * @return	    FALSE The two HBA instances are different
201 *
202 * @doc		    This routine will compare each port within both
203 *		    HBAs and verify they are the same.  The ports must
204 *		    have been added in the same order.
205 */
206bool HBA::operator==(HBA &comp) {
207	Trace log("HBA::operator==");
208	lock();
209
210	try {
211	    bool ret = false;
212	    if (portsByIndex.size() == comp.portsByIndex.size()) {
213		if (portsByIndex.size() > 0) {
214		    ret = (*portsByIndex[0] == *comp.portsByIndex[0]);
215		}
216	    }
217	    unlock();
218	    return (ret);
219	} catch (...) {
220	    unlock();
221	    throw;
222	}
223}
224
225/**
226 * @memo	    Set the RNID data for all the ports in this HBA
227 * @precondition    All ports must be added
228 * @postcondition   Each port will have the same RNID value set
229 * @exception	    ... underlying exceptions will be thrown.  Partial failure
230 *		    is possible and will not be cleaned up.
231 * @param	    info The RNID information to program for each HBA port
232 * @see		    HBAPort::setRNID
233 *
234 */
235void HBA::setRNID(HBA_MGMTINFO info) {
236	Trace log("HBA::setRNID");
237	lock();
238
239	try {
240	    for (CI port = portsByWWN.begin(); port != portsByWWN.end();
241		    port++) {
242		port->second->setRNID(info);
243	    }
244	    unlock();
245	} catch (...) {
246	    unlock();
247	    throw;
248	}
249}
250
251/**
252 * @memo	    Verify that this HBA is present on the system
253 * @exception	    UnavailableException Thrown when HBA not present
254 * @see		    HBAPort::validatePresent
255 *
256 * @doc		    This routine is used to verify that a given HBA
257 *		    has not been removed through dynamic reconfiguration.
258 *		    If the HBA is present, the routine will return.
259 *		    If the HBA is not present (if any port is not present)
260 *		    an exception will be thrown
261 */
262void HBA::validatePresent() {
263	Trace log("HBA::validatePresent");
264	lock();
265	try {
266	    for (CI port = portsByWWN.begin(); port != portsByWWN.end();
267		    port++) {
268		port->second->validatePresent();
269	    }
270	    unlock();
271	} catch (...) {
272	    unlock();
273	    throw;
274	}
275}
276
277/**
278 * Opens a file, throwing exceptions on error.
279 */
280int HBA::_open(std::string path, int flag) {
281	Trace log("HBA::open");
282	int fd;
283	errno = 0;
284	if ((fd = open(path.c_str(), flag)) < 0) {
285	    log.debug("Unable to open \"%s\" - reason (%d) %s",
286		path.c_str(), errno, strerror(errno));
287	    if (errno == EBUSY) {
288		throw BusyException();
289	    } else if (errno == EAGAIN) {
290		throw TryAgainException();
291	    } else if (errno == ENOTSUP) {
292		throw NotSupportedException();
293	    } else if (errno == ENOENT) {
294		throw UnavailableException();
295	    } else {
296		string msg = "Unable to open ";
297		msg += path;
298		throw IOError(msg);
299	    }
300	}
301	return (fd);
302}
303
304/**
305 * Issues IOCTL, throwing exceptions on error.
306 * Note, if the IOCTL succeeds, but some IOCTL specific
307 * error is recorded in the response, this routine
308 * will not throw an exception.
309 */
310void HBA::_ioctl(int fd, int type, uchar_t *arg) {
311	Trace log("HBA::ioctl");
312	hrtime_t	    cur;
313	int		    saved_errno = 0;
314	struct timespec	    ts;
315
316	hrtime_t start = gethrtime();
317	hrtime_t end = start + BUSY_RETRY_TIMER;
318	ts.tv_sec = 0;
319	ts.tv_nsec = BUSY_SLEEP;
320	for (cur = start; cur < end; cur = gethrtime()) {
321		errno = 0;
322		if (ioctl(fd, type, arg) != 0) {
323			if (errno == EAGAIN) {
324				saved_errno = errno;
325				nanosleep(&ts, NULL);
326				continue;
327			} else if (errno == EBUSY) {
328				saved_errno = errno;
329				nanosleep(&ts, NULL);
330				continue;
331			} else if (errno == ENOTSUP) {
332				throw NotSupportedException();
333			} else if (errno == ENOENT) {
334				throw UnavailableException();
335			} else {
336				throw IOError("IOCTL failed");
337			}
338		} else {
339			break;
340		}
341	}
342	if (cur >= end) {
343		if (saved_errno == EAGAIN) {
344			throw TryAgainException();
345		} else if (saved_errno == EBUSY) {
346			throw BusyException();
347		} else {
348			throw IOError("IOCTL failed");
349		}
350	}
351}
352
353HBA::~HBA() {
354	Trace log("HBA::~HBA");
355	for (int i = 0; i < getNumberOfPorts(); i++) {
356	    delete (getPortByIndex(i));
357	}
358}
359
360