xref: /illumos-gate/usr/src/cmd/ndmpd/tlm/tlm_init.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*
7  * BSD 3 Clause License
8  *
9  * Copyright (c) 2007, The Storage Networking Industry Association.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 	- Redistributions of source code must retain the above copyright
15  *	  notice, this list of conditions and the following disclaimer.
16  *
17  * 	- Redistributions in binary form must reproduce the above copyright
18  *	  notice, this list of conditions and the following disclaimer in
19  *	  the documentation and/or other materials provided with the
20  *	  distribution.
21  *
22  *	- Neither the name of The Storage Networking Industry Association (SNIA)
23  *	  nor the names of its contributors may be used to endorse or promote
24  *	  products derived from this software without specific prior written
25  *	  permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 #include <sys/errno.h>
40 #include <sys/types.h>
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <sys/scsi/impl/uscsi.h>
44 #include <sys/scsi/scsi.h>
45 #include <tlm.h>
46 #include <pthread.h>
47 #include "tlm_proto.h"
48 
49 /*
50  * generic routine to read a SCSI page
51  */
52 int
53 read_scsi_page(scsi_link_t *slink, union scsi_cdb *cdb,
54     int command_size, caddr_t data, int size)
55 {
56 	struct uscsi_cmd uscsi_cmd;
57 	char *dname;
58 	int dev;
59 
60 	if (slink == 0 || slink->sl_sa == 0)
61 		return (EINVAL);
62 
63 	(void) memset(&uscsi_cmd, 0, sizeof (uscsi_cmd));
64 
65 	/* Lun is in the 5th bit */
66 	cdb->scc_lun = slink->sl_lun;
67 	uscsi_cmd.uscsi_flags |= USCSI_READ | USCSI_ISOLATE;
68 	uscsi_cmd.uscsi_bufaddr = data;
69 	uscsi_cmd.uscsi_buflen = size;
70 	uscsi_cmd.uscsi_timeout = 1000;
71 	uscsi_cmd.uscsi_cdb = (char *)cdb;
72 
73 	if (cdb->scc_cmd == SCMD_READ_ELEMENT_STATUS) {
74 		uscsi_cmd.uscsi_flags |= USCSI_RQENABLE;
75 		uscsi_cmd.uscsi_rqbuf = data;
76 		uscsi_cmd.uscsi_rqlen = size;
77 	}
78 	uscsi_cmd.uscsi_cdblen = command_size;
79 
80 	dname = sasd_slink_name(slink);
81 	dev = open(dname, O_RDWR | O_NDELAY);
82 	if (dev == -1) {
83 		NDMP_LOG(LOG_DEBUG, "Open failed for %s err=%d",
84 		    dname, errno);
85 		return (errno);
86 	}
87 	if (tlm_ioctl(dev, USCSICMD, &uscsi_cmd) < 0) {
88 		NDMP_LOG(LOG_DEBUG, "SCSI cmd %d failed for %s err=%d",
89 		    cdb->scc_cmd, dname, errno);
90 		(void) close(dev);
91 		return (errno);
92 	}
93 	(void) close(dev);
94 	return (uscsi_cmd.uscsi_status);
95 }
96 
97 /*
98  * Read the Inquiry Page.
99  */
100 static int
101 read_inquiry_page(scsi_link_t *slink, struct scsi_inquiry *inq)
102 {
103 	union scsi_cdb cdb;
104 
105 	(void) memset(&cdb, 0, sizeof (union scsi_cdb));
106 	cdb.scc_cmd = SCMD_INQUIRY;
107 	cdb.g0_count0 = sizeof (struct scsi_inquiry);
108 
109 	return (read_scsi_page(slink, &cdb, CDB_GROUP0,
110 	    (caddr_t)inq, sizeof (*inq)) ? -1 : 0);
111 }
112 
113 /*
114  * Read the Product Data Page.
115  */
116 static int
117 read_data_page(scsi_link_t *slink, int pcode, char *snum, int size)
118 {
119 	char cmd[CDB_GROUP0];
120 
121 	(void) memset(cmd, 0, sizeof (cmd));
122 
123 	cmd[0] = SCMD_INQUIRY;
124 	cmd[1] = pcode ? 0x01 : 0x00;
125 	cmd[2] = pcode;
126 	cmd[4] = size;
127 
128 	/* LINTED improper alignment */
129 	return (read_scsi_page(slink, (union scsi_cdb *)&cmd, CDB_GROUP0,
130 	    (caddr_t)snum, size) == -1 ? -1 : 0);
131 }
132 
133 
134 /*
135  * Read the Serial Number Page.
136  */
137 static int
138 read_serial_num_page(scsi_link_t *slink, char *snum, int size)
139 {
140 	scsi_serial_t serial;
141 	int rv;
142 
143 	(void) memset(&serial, 0, sizeof (scsi_serial_t));
144 	rv = read_data_page(slink, SCSI_SERIAL_PAGE, (caddr_t)&serial,
145 	    sizeof (scsi_serial_t));
146 	(void) strlcpy(snum, serial.sr_num, size);
147 
148 	return (rv == -1 ? -1 : 0);
149 }
150 
151 
152 /*
153  * Read the Device Name Page.
154  */
155 static int
156 read_dev_name_page(scsi_link_t *slink, device_name_page_t *devp)
157 {
158 	(void) memset(devp, 0, sizeof (device_name_page_t));
159 
160 	if (read_data_page(slink, SCSI_DEVICE_IDENT_PAGE, (caddr_t)devp,
161 	    sizeof (device_name_page_t)) == -1)
162 		return (-1);
163 
164 	if (devp->np_header.di_page_code == SCSI_DEVICE_IDENT_PAGE &&
165 	    devp->np_node.ni_code_set == 1 &&
166 	    devp->np_node.ni_ident_type == 3 &&
167 	    devp->np_node.ni_ident_length == 8)
168 		return (0);
169 
170 	if (devp->np_header.di_page_code == SCSI_DEVICE_IDENT_PAGE)
171 		return (0);
172 
173 	return (-1);
174 }
175 
176 /*
177  * Formatted print of WWN
178  */
179 char *
180 snprintf_wwn(char *buf, int size, uint8_t *wwn)
181 {
182 	if (wwn == NULL || buf == NULL)
183 		return (0);
184 
185 	(void) snprintf(buf, size, "0x%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X",
186 	    wwn[0], wwn[1], wwn[2], wwn[3], wwn[4], wwn[5], wwn[6], wwn[7]);
187 	return (buf);
188 }
189 
190 
191 /*
192  * Extract and print the world wide name (WWN)
193  */
194 int
195 read_device_wwn(scsi_link_t *slink, char *wwnp, int wsize)
196 {
197 	device_name_page_t dinfo;
198 
199 	(void) memset(wwnp, 0, wsize);
200 	if (read_dev_name_page(slink, &dinfo) == -1)
201 		return (-1);
202 
203 	if (dinfo.np_port.ni_code_set == 1 &&
204 	    dinfo.np_port.ni_ident_type == 3) {
205 		(void) snprintf_wwn(wwnp, wsize, dinfo.np_port_info.d_name);
206 		return (0);
207 	}
208 	if (dinfo.np_node.ni_code_set == 1 &&
209 	    dinfo.np_node.ni_ident_type == 3) {
210 		(void) snprintf_wwn(wwnp, wsize, dinfo.np_node_info.d_name);
211 		return (0);
212 	}
213 	if (dinfo.np_port.ni_code_set == 2 &&
214 	    dinfo.np_port.ni_ident_type == 1) {
215 		(void) snprintf(wwnp, wsize, "%.*s",
216 		    dinfo.np_port.ni_ident_length, dinfo.np_port_info.d_name);
217 		return (0);
218 	}
219 	if (dinfo.np_node.ni_code_set == 2 &&
220 	    dinfo.np_node.ni_ident_type == 1) {
221 		(void) snprintf(wwnp, wsize, "%.*s",
222 		    dinfo.np_node.ni_ident_length, dinfo.np_node_info.d_name);
223 		return (0);
224 	}
225 	return (-1);
226 }
227 
228 /*
229  * Add the tape library call back function (used while scanning the bus)
230  */
231 static int
232 add_lib(scsi_link_t *slink, struct scsi_inquiry *sd, void *arg)
233 {
234 	int l;
235 	int *nlp; /* pointer to library counter */
236 	sasd_drive_t *ssd;
237 
238 	if (!slink || !sd) {
239 		NDMP_LOG(LOG_DEBUG, "Invalid argument %x %x %x",
240 		    slink, sd, arg);
241 		return (-TLM_INVALID);
242 	}
243 
244 	if (sd->inq_dtype == DTYPE_CHANGER) {
245 		/* This is a robot, which means this is also a library */
246 		nlp = (int *)arg;
247 		(*nlp)++;
248 		l = tlm_insert_new_library(slink);
249 		tlm_enable_barcode(l);
250 
251 		NDMP_LOG(LOG_DEBUG, "lib %d sid %d lun %d",
252 		    l, slink->sl_sid, slink->sl_lun);
253 
254 		if ((ssd = sasd_slink_drive(slink)) != NULL) {
255 			(void) strlcpy(ssd->sd_vendor, sd->inq_vid,
256 			    sizeof (ssd->sd_vendor));
257 			(void) strlcpy(ssd->sd_id, sd->inq_pid,
258 			    sizeof (ssd->sd_id));
259 			(void) strlcpy(ssd->sd_rev, sd->inq_revision,
260 			    sizeof (ssd->sd_rev));
261 			(void) read_serial_num_page(slink, ssd->sd_serial,
262 			    sizeof (ssd->sd_serial));
263 			(void) read_device_wwn(slink, ssd->sd_wwn,
264 			    sizeof (ssd->sd_wwn));
265 		}
266 	}
267 
268 	return (TLM_NO_ERRORS);
269 }
270 
271 /*
272  * Create some virutal slots
273  */
274 static int
275 make_virtual_slot(int l, tlm_drive_t *dp)
276 {
277 	int s;
278 	tlm_slot_t *sp;
279 
280 	if (l <= 0 || !dp) {
281 		NDMP_LOG(LOG_DEBUG, "Invalid argument %d, %x", l, dp);
282 		return (-TLM_INVALID);
283 	}
284 
285 	if ((s = tlm_insert_new_slot(l)) <= 0)
286 		return (-TLM_NO_MEMORY);
287 
288 	if (!(sp = tlm_slot(l, s))) {
289 		NDMP_LOG(LOG_DEBUG, "Internal error: slot not found %d", s);
290 		return (-TLM_ERROR_INTERNAL);
291 	}
292 	/*
293 	 * For virtual slots element number is 0 and they are always full.
294 	 */
295 	sp->ts_element = 0;
296 	sp->ts_status_full = TRUE;
297 	return (TLM_NO_ERRORS);
298 }
299 
300 /*
301  * Make the tape drive not part of a tape library (stand alone)
302  */
303 static int
304 make_stand_alone_drive(scsi_link_t *slink, int l)
305 {
306 	int d;
307 	tlm_drive_t *dp;
308 
309 	if (!slink || l <= 0) {
310 		NDMP_LOG(LOG_DEBUG, "Invalid argument %x %d", slink, l);
311 		return (-TLM_INVALID);
312 	}
313 
314 	d = tlm_insert_new_drive(l);
315 	if (!(dp = tlm_drive(l, d))) {
316 		NDMP_LOG(LOG_DEBUG, "Internal error: drive not found %d", d);
317 		return (-TLM_ERROR_INTERNAL);
318 	}
319 
320 	/* For stand-alone drives, the element number is the drive number. */
321 	dp->td_element = d;
322 	dp->td_slink = slink;
323 	dp->td_scsi_id = slink->sl_sid;
324 	dp->td_lun = slink->sl_lun;
325 	dp->td_exists = TRUE;
326 
327 	/*
328 	 * Note: There is no way to remove library elements.  We cannot clean
329 	 * up if make_virtual_slot() fails.
330 	 */
331 	(void) make_virtual_slot(l, dp);
332 	return (d);
333 }
334 
335 /*
336  * Find the LIBRARY structure that has control of this DRIVE.
337  */
338 static int
339 new_drive(scsi_link_t *slink, int *lib)
340 {
341 	int d;
342 	tlm_drive_t *dp;
343 	tlm_library_t *lp;
344 
345 	/* Walk through all libraries. */
346 	for (*lib = 1; *lib <= tlm_library_count(); (*lib)++) {
347 		if (!(lp = tlm_library(*lib)))
348 			continue;
349 		/* Walk through drives that are already found. */
350 		for (d = 1; d <= lp->tl_drive_count; d++) {
351 			if (!(dp = tlm_drive(*lib, d)))
352 				continue;
353 			if (dp->td_scsi_id == slink->sl_sid &&
354 			    dp->td_lun == slink->sl_lun)
355 				return (d);
356 		}
357 	}
358 
359 	/* Not part of any library, this is a newly found tape drive. */
360 	return (0);
361 }
362 
363 
364 /*
365  * Add the tape library call back function (used while scanning the bus)
366  */
367 static int
368 add_drv(scsi_link_t *slink, struct scsi_inquiry *sd, void *arg)
369 {
370 	int l, d;
371 	int *vlp; /* pointer to virtual library number */
372 	sasd_drive_t *ssd;
373 	tlm_library_t *library;
374 	tlm_drive_t *drive;
375 
376 	if (!slink || !sd) {
377 		NDMP_LOG(LOG_DEBUG, "Invalid argument %x %x %x",
378 		    slink, sd, arg);
379 		return (-TLM_INVALID);
380 	}
381 
382 	if (sd->inq_dtype == DTYPE_SEQUENTIAL) {
383 		vlp = (int *)arg;
384 		d = new_drive(slink, &l);
385 		if (d == 0) {
386 			/* This tape drive was not found inside any robot. */
387 			if (*vlp == 0) {
388 				/*
389 				 * First, create a virtual library if it's not
390 				 * done yet.
391 				 */
392 				*vlp = tlm_insert_new_library(slink);
393 				if ((library = tlm_library(*vlp)) != NULL)
394 					library->tl_capability_robot = FALSE;
395 			}
396 			if ((d = make_stand_alone_drive(slink, *vlp)) < 0) {
397 				/* sorry, we can not clean up the vlib now * */
398 				return (-TLM_INVALID);
399 			}
400 			l = *vlp;
401 			NDMP_LOG(LOG_DEBUG, "vlib(%d, %d) sid %d lun %d",
402 			    l, d, slink->sl_sid, slink->sl_lun);
403 		} else
404 			NDMP_LOG(LOG_DEBUG, "(%d, %d) sid %d lun %d",
405 			    l, d, slink->sl_sid, slink->sl_lun);
406 
407 		if ((drive = tlm_drive(l, d)) != NULL) {
408 			drive->td_exists = TRUE;
409 			drive->td_slink = slink;
410 		}
411 		if ((ssd = sasd_slink_drive(slink)) != NULL) {
412 			(void) strlcpy(ssd->sd_vendor,
413 			    sd->inq_vid, sizeof (ssd->sd_vendor));
414 			(void) strlcpy(ssd->sd_id, sd->inq_pid,
415 			    sizeof (ssd->sd_id));
416 			(void) strlcpy(ssd->sd_rev, sd->inq_revision,
417 			    sizeof (ssd->sd_rev));
418 			(void) read_serial_num_page(slink, ssd->sd_serial,
419 			    sizeof (ssd->sd_serial));
420 			(void) read_device_wwn(slink, ssd->sd_wwn,
421 			    sizeof (ssd->sd_wwn));
422 		}
423 	}
424 
425 	return (TLM_NO_ERRORS);
426 }
427 
428 /*
429  * Scan the specified bus and call the handler function.
430  */
431 static int
432 scan_bus(scsi_adapter_t *sa, int(*hndlr)(), void *args)
433 {
434 	int nerr;
435 	scsi_link_t *slink;
436 	struct scsi_inquiry scsi_data;
437 
438 	nerr = 0;
439 	slink = sa->sa_link_head.sl_next;
440 	for (; slink != &sa->sa_link_head; slink = slink->sl_next) {
441 		(void) memset(&scsi_data, 0, sizeof (struct scsi_inquiry));
442 		if (read_inquiry_page(slink, &scsi_data) == -1)
443 			nerr++;
444 		else
445 			if ((*hndlr)(slink, &scsi_data, args) != TLM_NO_ERRORS)
446 				nerr++;
447 	}
448 
449 	return (nerr);
450 }
451 
452 /*
453  * Marks the library/slots inaccessible if there are not enough drives
454  * available on the library
455  */
456 static void
457 inaccbl_drv_warn(int start, int max)
458 {
459 	char *dname;
460 	int l, d;
461 	tlm_library_t *lp;
462 
463 	for (l = start; l < max; l++) {
464 		if (!(lp = tlm_library(l)))
465 			continue;
466 		if (lp->tl_drive_count <= 0)
467 			continue;
468 
469 		NDMP_LOG(LOG_DEBUG,
470 		    "Warning: The following drives are not accessible:");
471 		for (d = 1; d <= lp->tl_drive_count; d++)
472 			if (!(dname = tlm_get_tape_name(l, d))) {
473 				NDMP_LOG(LOG_DEBUG,
474 				    "Error getting drive(%d, %d)", l, d);
475 			} else
476 				NDMP_LOG(LOG_DEBUG, "%s", dname);
477 
478 		/*
479 		 * Note: Make the slots inaccessible to prevent running
480 		 * discovery on these libraries.  The better idea is
481 		 * removing these libraries, but we don't have that
482 		 * feature available now.
483 		 */
484 		lp->tl_slot_count = 0;
485 	}
486 }
487 
488 /*
489  * Initialize the tape library data structure, asks the libraries what
490  * equipments they have.
491  */
492 int
493 tlm_init(void)
494 {
495 	static int nlibs; /* number of found libraries */
496 	int i, nsa;
497 	int l, vlibs, d;
498 	int rv;
499 	scsi_adapter_t *sa;
500 	tlm_library_t *lp;
501 	tlm_drive_t *dp;
502 
503 	/* Search through all SCSI adapters, look for tape robots. */
504 	nlibs = 0;
505 
506 	/*
507 	 * We probe both changers and tape drives here
508 	 * but later on this needs to be removed as the
509 	 * probe will happen somewhere else.
510 	 */
511 	(void) probe_scsi();
512 
513 	nsa = scsi_get_adapter_count();
514 	for (i = 0; i < nsa; i++)
515 		if ((sa = scsi_get_adapter(i)))
516 			(void) scan_bus(sa, add_lib, (void *)&nlibs);
517 
518 	NDMP_LOG(LOG_DEBUG, "nlibs %d", nlibs);
519 
520 	/* Search through all SCSI adapters, look for tape drives. */
521 	vlibs = 0;
522 	for (i = 0; i < nsa; i++)
523 		if ((sa = scsi_get_adapter(i)))
524 			(void) scan_bus(sa, add_drv, (void *)&vlibs);
525 
526 	NDMP_LOG(LOG_DEBUG, "vlibs %d", vlibs);
527 
528 	if (nlibs > 0 && vlibs > 0)
529 		inaccbl_drv_warn(nlibs + 1, vlibs + nlibs + 1);
530 
531 	for (l = 1; l <= tlm_library_count(); l++) {
532 		if (!(lp = tlm_library(l))) {
533 			NDMP_LOG(LOG_DEBUG, "can't find lib %d", l);
534 			continue;
535 		}
536 
537 		/*
538 		 * Make sure all libraries have tape drives.
539 		 */
540 		if (lp->tl_drive_count == 0)
541 			continue;
542 
543 		/*
544 		 * Make sure all tape drives exist. A drive that is not
545 		 * linked into the SCSI chain will be seen by the library
546 		 * but we cannot talk to it.
547 		 */
548 		for (d = 1; d <= lp->tl_drive_count; d++) {
549 			dp = tlm_drive(l, d);
550 			if (dp && !dp->td_exists) {
551 				NDMP_LOG(LOG_DEBUG, "Ghost drive found %d.%d",
552 				    l, d);
553 				lp->tl_ghost_drives = TRUE;
554 				continue;
555 			}
556 		}
557 	}
558 
559 	if (nlibs > 0)
560 		rv = (vlibs > 0) ? 0 : nlibs;
561 	else
562 		rv = vlibs;
563 
564 	return (rv);
565 }
566