1/* $NetBSD: uscsi_subr.c,v 1.7 2002/10/08 20:17:06 soren Exp $	*/
2
3/*-
4 * Copyright (c) 1998 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Charles M. Hannum; Jason R. Thorpe of the Numerical Aerospace
9 * Simulation Facility, NASA Ames Research Center.
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 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 *
32 * Small changes, generalisations and Linux support by Reinoud Zandijk
33 * <reinoud@netbsd.org>.
34 *
35 */
36
37
38/*
39 * SCSI support subroutines.
40 */
41
42#include <sys/param.h>
43#include <sys/ioctl.h>
44#include <err.h>
45#include <errno.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50#include <fcntl.h>
51#include <sys/types.h>
52#include <inttypes.h>
53#include <assert.h>
54
55#include "uscsilib.h"
56
57
58int uscsilib_verbose = 0;
59
60
61#ifdef USCSI_SCSIPI
62	/*
63	 * scsipi is a integrated SCSI and ATAPI layer under NetBSD and exists
64	 * in a modified form under OpenBSD and possibly also under other
65	 * operating systems.
66	 */
67
68
69#include <sys/scsiio.h>
70#ifdef __OpenBSD__
71#include <scsi/uscsi_all.h>
72#else
73#include <dev/scsipi/scsipi_all.h>
74#endif
75
76
77int
78uscsi_open(struct uscsi_dev *disc)
79{
80	struct stat dstat;
81
82	disc->fhandle = open(disc->dev_name, O_RDWR, 0); /* no create */
83	if (disc->fhandle<0) {
84		perror("Failure to open device or file");
85		return ENODEV;
86	}
87
88	if (fstat(disc->fhandle, &dstat) < 0) {
89		perror("Can't stat device or file");
90		uscsi_close(disc);
91		return ENODEV;
92	}
93
94	return 0;
95}
96
97
98int
99uscsi_close(struct uscsi_dev * disc)
100{
101	close(disc->fhandle);
102	disc->fhandle = -1;
103
104	return 0;
105}
106
107
108int
109uscsi_command(int flags, struct uscsi_dev *disc,
110	void *cmd, size_t cmdlen, void *data, size_t datalen,
111	uint32_t timeout, struct uscsi_sense *uscsi_sense)
112{
113	scsireq_t req;
114
115	memset(&req, 0, sizeof(req));
116	if (uscsi_sense)
117		bzero(uscsi_sense, sizeof(struct uscsi_sense));
118
119	memcpy(req.cmd, cmd, cmdlen);
120	req.cmdlen = cmdlen;
121	req.databuf = data;
122	req.datalen = datalen;
123	req.timeout = timeout;
124	req.flags = flags;
125	req.senselen = SENSEBUFLEN;
126
127	if (ioctl(disc->fhandle, SCIOCCOMMAND, &req) == -1)
128		err(1, "SCIOCCOMMAND");
129
130	if (req.retsts == SCCMD_OK)
131		return 0;
132
133	/* Some problem; report it and exit. */
134	if (req.retsts == SCCMD_TIMEOUT) {
135		if (uscsilib_verbose)
136			fprintf(stderr, "%s: SCSI command timed out\n",
137				disc->dev_name);
138		return EAGAIN;
139	} else if (req.retsts == SCCMD_BUSY) {
140		if (uscsilib_verbose)
141			fprintf(stderr, "%s: device is busy\n",
142				disc->dev_name);
143		return EBUSY;
144	} else if (req.retsts == SCCMD_SENSE) {
145		if (uscsi_sense) {
146			uscsi_sense->asc        =  req.sense[12];
147			uscsi_sense->ascq       =  req.sense[13];
148			uscsi_sense->skey_valid =  req.sense[15] & 128;
149			uscsi_sense->sense_key  = (req.sense[16] << 8) |
150						  (req.sense[17]);
151		}
152		if (uscsilib_verbose)
153			uscsi_print_sense((char *) disc->dev_name,
154				req.cmd, req.cmdlen,
155				req.sense, req.senselen_used, 1);
156		return EIO;
157	} else
158		if (uscsilib_verbose)
159			fprintf(stderr, "%s: device had unknown status %x\n",
160				disc->dev_name,
161		  	  req.retsts);
162
163	return EFAULT;
164}
165
166
167/*
168 * The reasoning behind this explicit copy is for compatibility with changes
169 * in our uscsi_addr structure.
170 */
171int
172uscsi_identify(struct uscsi_dev *disc, struct uscsi_addr *saddr)
173{
174	struct scsi_addr raddr;
175	int error;
176
177	bzero(saddr, sizeof(struct scsi_addr));
178	error = ioctl(disc->fhandle, SCIOCIDENTIFY, &raddr);
179	if (error) return error;
180
181#ifdef __NetBSD__
182	/* scsi and atapi are split up like in uscsi_addr */
183	if (raddr.type == 0) {
184		saddr->type = USCSI_TYPE_SCSI;
185		saddr->addr.scsi.scbus  = raddr.addr.scsi.scbus;
186		saddr->addr.scsi.target = raddr.addr.scsi.target;
187		saddr->addr.scsi.lun    = raddr.addr.scsi.lun;
188	} else {
189		saddr->type = USCSI_TYPE_ATAPI;
190		saddr->addr.atapi.atbus = raddr.addr.atapi.atbus;
191		saddr->addr.atapi.drive = raddr.addr.atapi.drive;
192	}
193#endif
194#ifdef __OpenBSD__
195	/* atapi's are shown as SCSI devices */
196	if (raddr.type == 0) {
197		saddr->type = USCSI_TYPE_SCSI;
198		saddr->addr.scsi.scbus  = raddr.scbus;
199		saddr->addr.scsi.target = raddr.target;
200		saddr->addr.scsi.lun    = raddr.lun;
201	} else {
202		saddr->type = USCSI_TYPE_ATAPI;
203		saddr->addr.atapi.atbus = raddr.scbus;	/* overload */
204		saddr->addr.atapi.drive = raddr.target;	/* overload */
205	}
206#endif
207
208	return 0;
209}
210
211
212int
213uscsi_check_for_scsi(struct uscsi_dev *disc)
214{
215	struct uscsi_addr	saddr;
216
217	return uscsi_identify(disc, &saddr);
218}
219#endif	/* SCSILIB_SCSIPI */
220
221
222
223
224#ifdef USCSI_LINUX_SCSI
225	/*
226	 * Support code for Linux SCSI code. It uses the ioctl() way of
227	 * communicating since this is more close to the origional NetBSD
228	 * scsipi implementation.
229	 */
230#include <scsi/sg.h>
231#include <scsi/scsi.h>
232
233#define SENSEBUFLEN 48
234
235
236int
237uscsi_open(struct uscsi_dev * disc)
238{
239	int flags;
240	struct stat stat;
241
242	/* in Linux we are NOT allowed to open it blocking */
243	/* no create! */
244	disc->fhandle = open(disc->dev_name, O_RDWR | O_NONBLOCK, 0);
245	if (disc->fhandle<0) {
246		perror("Failure to open device or file");
247		return ENODEV;
248	}
249
250	/* explicitly mark it non blocking (again) (silly Linux) */
251	flags = fcntl(disc->fhandle, F_GETFL);
252	flags &= ~O_NONBLOCK;
253	fcntl(disc->fhandle, F_SETFL, flags);
254
255	if (fstat(disc->fhandle, &stat) < 0) {
256		perror("Can't stat device or file");
257		uscsi_close(disc);
258		return ENODEV;
259	}
260
261	return 0;
262}
263
264
265int
266uscsi_close(struct uscsi_dev * disc)
267{
268	close(disc->fhandle);
269	disc->fhandle = -1;
270
271	return 0;
272}
273
274
275int
276uscsi_command(int flags, struct uscsi_dev *disc,
277	void *cmd, size_t cmdlen,
278	void *data, size_t datalen,
279	uint32_t timeout, struct uscsi_sense *uscsi_sense)
280{
281	struct sg_io_hdr req;
282	uint8_t sense_buffer[SENSEBUFLEN];
283	int error;
284
285	bzero(&req, sizeof(req));
286	if (flags == SG_DXFER_FROM_DEV) bzero(data, datalen);
287
288	req.interface_id    = 'S';
289	req.dxfer_direction = flags;
290	req.cmd_len	    = cmdlen;
291	req.mx_sb_len	    = SENSEBUFLEN;
292	req.iovec_count	    = 0;
293	req.dxfer_len	    = datalen;
294	req.dxferp	    = data;
295	req.cmdp	    = cmd;
296	req.sbp		    = sense_buffer;
297	req.flags	    = 0;
298	req.timeout	    = timeout;
299
300	error = ioctl(disc->fhandle, SG_IO, &req);
301
302	if (req.status) {
303		/* Is this OK? */
304		if (uscsi_sense) {
305			uscsi_sense->asc        =  sense_buffer[12];
306			uscsi_sense->ascq       =  sense_buffer[13];
307			uscsi_sense->skey_valid =  sense_buffer[15] & 128;
308			uscsi_sense->sense_key  = (sense_buffer[16] << 8) |
309						  (sense_buffer[17]);
310		}
311		if (uscsilib_verbose) {
312			uscsi_print_sense((char *) disc->dev_name,
313				cmd, cmdlen, sense_buffer, req.sb_len_wr, 1);
314		}
315	}
316
317	return error;
318}
319
320
321int
322uscsi_identify(struct uscsi_dev *disc, struct uscsi_addr *saddr)
323{
324	struct sg_scsi_id sg_scsi_id;
325	struct sg_id {
326		/* target | lun << 8 | channel << 16 | low_ino << 24 */
327		uint32_t tlci;
328		uint32_t uniq_id;
329	} sg_id;
330	int emulated;
331	int error;
332
333	/* clean result */
334	bzero(saddr, sizeof(struct uscsi_addr));
335
336	/* check if its really SCSI or emulated SCSI (ATAPI f.e.) */
337	saddr->type = USCSI_TYPE_SCSI;
338	ioctl(disc->fhandle, SG_EMULATED_HOST, &emulated);
339	if (emulated) saddr->type = USCSI_TYPE_ATAPI;
340
341	/* try 2.4 kernel or older */
342	error = ioctl(disc->fhandle, SG_GET_SCSI_ID, &sg_scsi_id);
343	if (!error) {
344		saddr->addr.scsi.target = sg_scsi_id.scsi_id;
345		saddr->addr.scsi.lun    = sg_scsi_id.lun;
346		saddr->addr.scsi.scbus  = sg_scsi_id.channel;
347
348		return 0;
349	}
350
351	/* 2.6 kernel or newer */
352 	error = ioctl(disc->fhandle, SCSI_IOCTL_GET_IDLUN, &sg_id);
353	if (error) return error;
354
355	saddr->addr.scsi.target = (sg_id.tlci      ) & 0xff;
356	saddr->addr.scsi.lun    = (sg_id.tlci >>  8) & 0xff;
357	saddr->addr.scsi.scbus  = (sg_id.tlci >> 16) & 0xff;
358
359	return 0;
360}
361
362
363int uscsi_check_for_scsi(struct uscsi_dev *disc) {
364	struct uscsi_addr saddr;
365
366	return uscsi_identify(disc, &saddr);
367}
368#endif	/* USCSI_LINUX_SCSI */
369
370
371
372
373#ifdef USCSI_FREEBSD_CAM
374
375int
376uscsi_open(struct uscsi_dev *disc)
377{
378	disc->devhandle = cam_open_device(disc->dev_name, O_RDWR);
379
380	if (disc->devhandle == NULL) {
381		disc->fhandle = open(disc->dev_name, O_RDWR | O_NONBLOCK, 0);
382		if (disc->fhandle < 0) {
383			perror("Failure to open device or file");
384			return ENODEV;
385		}
386	}
387
388	return 0;
389}
390
391
392int
393uscsi_close(struct uscsi_dev *disc)
394{
395	if (disc->devhandle != NULL) {
396		cam_close_device(disc->devhandle);
397		disc->devhandle = NULL;
398	} else {
399		close(disc->fhandle);
400		disc->fhandle = -1;
401	}
402
403	return 0;
404}
405
406
407int
408uscsi_command(int flags, struct uscsi_dev *disc,
409	void *cmd, size_t cmdlen,
410	void *data, size_t datalen,
411	uint32_t timeout, struct uscsi_sense *uscsi_sense)
412{
413	struct cam_device *cam_dev;
414	struct scsi_sense_data *cam_sense_data;
415	union ccb ccb;
416	uint32_t cam_sense;
417	uint8_t *keypos;
418	int camflags;
419
420	memset(&ccb, 0, sizeof(ccb));
421	cam_dev = (struct cam_device *) disc->devhandle;
422
423	if (datalen == 0) flags = SCSI_NODATACMD;
424	/* optional : */
425	/* if (data) assert(flags == SCSI_NODATACMD); */
426
427	camflags = CAM_DIR_NONE;
428	if (flags & SCSI_READCMD)
429		camflags = CAM_DIR_IN;
430	if (flags & SCSI_WRITECMD)
431		camflags = CAM_DIR_OUT;
432
433	cam_fill_csio(
434		&ccb.csio,
435		0,			/* retries */
436		NULL,			/* cbfcnp */
437		camflags,		/* flags */
438		MSG_SIMPLE_Q_TAG,	/* tag_action */
439		(u_int8_t *) data,	/* data_ptr */
440		datalen,		/* dxfer_len */
441		SSD_FULL_SIZE,		/* sense_len */
442		cmdlen,			/* cdb_len */
443		timeout			/* timeout */
444	);
445
446	/* Disable freezing the device queue */
447	ccb.ccb_h.flags |= CAM_DEV_QFRZDIS;
448
449	memcpy(ccb.csio.cdb_io.cdb_bytes, cmd, cmdlen);
450
451	/* Send the command down via the CAM interface */
452	if (cam_send_ccb(cam_dev, &ccb) < 0) {
453		err(1, "cam_send_ccb");
454	}
455
456	if ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)
457		return 0;
458
459	/* print error using the uscsi_sense routines? */
460
461	cam_sense = (ccb.ccb_h.status & (CAM_STATUS_MASK | CAM_AUTOSNS_VALID));
462	if (cam_sense != (CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID))
463		return EFAULT;
464
465	/* drive responds with sense information */
466	if (!uscsilib_verbose)
467		return EFAULT;
468
469	/* print sense info */
470	cam_sense_data = &ccb.csio.sense_data;
471	if (uscsi_sense) {
472		uscsi_sense->asc  = cam_sense_data->add_sense_code;
473		uscsi_sense->ascq = cam_sense_data->add_sense_code_qual;
474		keypos  = cam_sense_data->sense_key_spec;
475		uscsi_sense->skey_valid =  keypos[0] & 128;
476		uscsi_sense->sense_key  = (keypos[1] << 8) | (keypos[2]);
477	}
478
479	uscsi_print_sense((char *) disc->dev_name,
480		cmd, cmdlen,
481		(uint8_t *) cam_sense_data, 8 + cam_sense_data->extra_len, 1);
482
483	return EFAULT;
484}
485
486
487int
488uscsi_identify(struct uscsi_dev *disc, struct uscsi_addr *saddr)
489{
490	struct cam_device *cam_dev;
491
492	/* clean result */
493	bzero(saddr, sizeof(struct uscsi_addr));
494
495	cam_dev = (struct cam_device *) disc->devhandle;
496	if (!cam_dev) return ENODEV;
497
498	/* check if its really SCSI or emulated SCSI (ATAPI f.e.) ? */
499	saddr->type = USCSI_TYPE_SCSI;
500	saddr->addr.scsi.target = cam_dev->target_id;
501	saddr->addr.scsi.lun    = cam_dev->target_lun;
502	saddr->addr.scsi.scbus  = cam_dev->bus_id;
503
504	return 0;
505}
506
507
508int
509uscsi_check_for_scsi(struct uscsi_dev *disc)
510{
511	struct uscsi_addr saddr;
512
513	return uscsi_identify(disc, &saddr);
514}
515
516#endif	/* USCSI_FREEBSD_CAM */
517
518
519
520/*
521 * Generic SCSI funtions also used by the sense printing functionality.
522 * FreeBSD support has it allready asked for by the CAM.
523 */
524
525int
526uscsi_mode_sense(struct uscsi_dev *dev,
527	uint8_t pgcode, uint8_t pctl, void *buf, size_t len)
528{
529	scsicmd cmd;
530
531	bzero(buf, len);		/* initialise recieving buffer	*/
532
533	bzero(cmd, SCSI_CMD_LEN);
534	cmd[ 0] = 0x1a;			/* MODE SENSE			*/
535	cmd[ 1] = 0;			/* -				*/
536	cmd[ 2] = pgcode | pctl;	/* page code and control flags	*/
537	cmd[ 3] = 0;			/* -				*/
538	cmd[ 4] = len;			/* length of recieve buffer	*/
539	cmd[ 5] = 0;			/* control			*/
540
541	return uscsi_command(SCSI_READCMD, dev, &cmd, 6, buf, len, 10000, NULL);
542}
543
544
545int
546uscsi_mode_select(struct uscsi_dev *dev,
547	uint8_t byte2, void *buf, size_t len)
548{
549	scsicmd cmd;
550
551	bzero(cmd, SCSI_CMD_LEN);
552	cmd[ 0] = 0x15;			/* MODE SELECT			*/
553	cmd[ 1] = 0x10 | byte2;		/* SCSI-2 page format select	*/
554	cmd[ 4] = len;			/* length of page settings	*/
555	cmd[ 5] = 0;			/* control			*/
556
557	return uscsi_command(SCSI_WRITECMD, dev, &cmd, 6, buf, len,
558			10000, NULL);
559}
560
561
562int
563uscsi_request_sense(struct uscsi_dev *dev, void *buf, size_t len)
564{
565	scsicmd cmd;
566
567	bzero(buf, len);		/* initialise recieving buffer	*/
568
569	bzero(cmd, SCSI_CMD_LEN);
570	cmd[ 0] = 0x03;			/* REQUEST SENSE		*/
571	cmd[ 4] = len;			/* length of data to be read	*/
572	cmd[ 5] = 0;			/* control			*/
573
574	return uscsi_command(SCSI_WRITECMD, dev, &cmd, 6, buf, len,
575			10000, NULL);
576}
577
578
579/* end of uscsi_subr.c */
580
581