summaryrefslogtreecommitdiff
path: root/utils/scsi
diff options
context:
space:
mode:
authorAmaury Pouly <amaury.pouly@gmail.com>2017-01-03 22:19:24 +0100
committerAmaury Pouly <amaury.pouly@gmail.com>2017-01-07 15:52:33 +0100
commitae84354b4062e4c4ceb3ce402468d6c095a760c2 (patch)
tree892a3d6ad719dc4d45790eba5011b4aa6f7102f8 /utils/scsi
parent1728565e243300b5f50d56399931fa19169031d5 (diff)
downloadrockbox-ae84354b4062e4c4ceb3ce402468d6c095a760c2.tar.gz
rockbox-ae84354b4062e4c4ceb3ce402468d6c095a760c2.zip
Add multiplatform library for raw SCSI commands
Several tools need to perform raw SCSI commands, and we need to support Linux, Windows and Mac OS, without pulling tons of dependencies to build it easily. This very simple library has no dependency and supports Linux. TODO: - windows - mac os Change-Id: I496f5ad2490bd3e96ad962d31cce4e511a523c3a
Diffstat (limited to 'utils/scsi')
-rw-r--r--utils/scsi/Makefile17
-rw-r--r--utils/scsi/rbscsi.c313
-rw-r--r--utils/scsi/rbscsi.h83
3 files changed, 413 insertions, 0 deletions
diff --git a/utils/scsi/Makefile b/utils/scsi/Makefile
new file mode 100644
index 0000000000..2591bf2416
--- /dev/null
+++ b/utils/scsi/Makefile
@@ -0,0 +1,17 @@
1PREFIX?=
2CC=$(PREFIX)gcc
3AR=$(PREFIX)ar
4CFLAGS=-g -std=c99 -Wall
5BINS=librbscsi.a
6ARFLAGS=rcs
7
8all: $(BINS)
9
10%.o: %.c
11 $(CC) $(CFLAGS) -c -o $@ $<
12
13librbscsi.a: rbscsi.o
14 $(AR) $(ARFLAGS) -o $@ $^
15
16clean:
17 rm -fr *.o $(BINS)
diff --git a/utils/scsi/rbscsi.c b/utils/scsi/rbscsi.c
new file mode 100644
index 0000000000..aa62ba0118
--- /dev/null
+++ b/utils/scsi/rbscsi.c
@@ -0,0 +1,313 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include <stdlib.h>
22#include <stdio.h>
23#include <stdarg.h>
24#include "rbscsi.h"
25
26static void misc_std_printf(void *user, const char *fmt, ...)
27{
28 (void) user;
29 va_list args;
30 va_start(args, fmt);
31 vprintf(fmt, args);
32 va_end(args);
33}
34
35#if defined(_WIN32) || defined(__WIN32__)
36#define RB_SCSI_WINDOWS
37#include <windows.h>
38#include <ntddscsi.h>
39#include <stdint.h>
40typedef HANDLE rb_scsi_handle_t;
41#elif defined(__linux) || defined(__linux__) || defined(linux)
42#include <unistd.h>
43#include <fcntl.h>
44#include <stdio.h>
45#include <string.h>
46#include <errno.h>
47#include <sys/ioctl.h>
48#include <scsi/sg.h>
49#include <scsi/sg_lib.h>
50#include <scsi/sg_io_linux.h>
51#define RB_SCSI_LINUX
52typedef int rb_scsi_handle_t;
53#else
54typedef void *rb_scsi_handle_t;
55#endif
56
57struct rb_scsi_device_t
58{
59 rb_scsi_handle_t handle;
60 void *user;
61 rb_scsi_printf_t printf;
62 unsigned flags;
63};
64
65/* Linux */
66#ifdef RB_SCSI_LINUX
67rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user,
68 rb_scsi_printf_t printf)
69{
70 if(printf == NULL)
71 printf = misc_std_printf;
72 int fd = open(path, (flags & RB_SCSI_READ_ONLY) ? O_RDONLY : O_RDWR);
73 if(fd < 0)
74 {
75 if(flags & RB_SCSI_DEBUG)
76 printf(user, "rb_scsi: open failed: %s\n", strerror(errno));
77 return NULL;
78 }
79 rb_scsi_device_t dev = malloc(sizeof(struct rb_scsi_device_t));
80 dev->flags = flags;
81 dev->handle = fd;
82 dev->user = user;
83 dev->printf = printf ? printf : misc_std_printf;
84 return dev;
85}
86
87int rb_scsi_raw_xfer(rb_scsi_device_t dev, struct rb_scsi_raw_cmd_t *raw)
88{
89#define rb_printf(...) \
90 do{ if(dev->flags & RB_SCSI_DEBUG) dev->printf(dev->user, __VA_ARGS__); }while(0)
91 sg_io_hdr_t hdr;
92 memset(&hdr, 0, sizeof(hdr));
93 /* prepare transfer descriptor */
94 hdr.interface_id = 'S';
95 switch(raw->dir)
96 {
97 case RB_SCSI_NONE: hdr.dxfer_direction = SG_DXFER_NONE; break;
98 case RB_SCSI_READ: hdr.dxfer_direction = SG_DXFER_FROM_DEV; break;
99 case RB_SCSI_WRITE: hdr.dxfer_direction = SG_DXFER_TO_DEV; break;
100 default: rb_printf("rb_scsi: invalid direction"); return -1;
101 }
102 hdr.cmd_len = raw->cdb_len;
103 hdr.mx_sb_len = raw->sense_len;
104 hdr.dxfer_len = raw->buf_len;
105 hdr.dxferp = raw->buf;
106 hdr.cmdp = raw->cdb;
107 hdr.sbp = raw->sense;
108 hdr.timeout = raw->tmo * 1000;
109 hdr.flags = SG_FLAG_LUN_INHIBIT;
110 /* do raw command */
111 if(ioctl(dev->handle, SG_IO, &hdr) < 0)
112 {
113 raw->status = errno;
114 rb_printf("rb_scsi: ioctl failed: %s\n", strerror(raw->status));
115 return RB_SCSI_OS_ERROR;
116 }
117 /* error handling is weird: host status has the priority */
118 if(hdr.host_status)
119 {
120 rb_printf("rb_scsi: host error: %d\n", hdr.host_status);
121 raw->status = hdr.host_status;
122 return RB_SCSI_ERROR;
123 }
124 raw->status = hdr.status & 0x7e;
125 raw->buf_len -= hdr.resid;
126 raw->sense_len = hdr.sb_len_wr;
127 /* driver error can report sense */
128 if((hdr.driver_status & 0xf) == DRIVER_SENSE)
129 {
130 rb_printf("rb_scsi: driver status reported sense\n");
131 return RB_SCSI_SENSE;
132 }
133 /* otherwise errors */
134 if(hdr.driver_status)
135 {
136 rb_printf("rb_scsi: driver error: %d\n", hdr.driver_status);
137 return RB_SCSI_ERROR;
138 }
139 /* scsi status can imply sense */
140 if(raw->status == RB_SCSI_CHECK_CONDITION || raw->status == RB_SCSI_COMMAND_TERMINATED)
141 return RB_SCSI_SENSE;
142 return raw->status ? RB_SCSI_STATUS : RB_SCSI_OK;
143#undef rb_printf
144}
145
146void rb_scsi_close(rb_scsi_device_t dev)
147{
148 close(dev->handle);
149 free(dev);
150}
151
152/* Windpws */
153#elif defined(RB_SCSI_WINDOWS)
154rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user,
155 rb_scsi_printf_t printf)
156{
157 if(printf == NULL)
158 printf = misc_std_printf;
159 HANDLE h = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
160 NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING, NULL);
161 if(h == INVALID_HANDLE_VALUE)
162 {
163 if(flags & RB_SCSI_DEBUG)
164 {
165 LPSTR msg = NULL;
166 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
167 NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL);
168 printf(user, "rb_scsi: open failed: %s\n", msg);
169 LocalFree(msg);
170 }
171 return NULL;
172 }
173 rb_scsi_device_t dev = malloc(sizeof(struct rb_scsi_device_t));
174 dev->flags = flags;
175 dev->handle = h;
176 dev->user = user;
177 dev->printf = printf ? printf : misc_std_printf;
178 return dev;
179}
180
181int rb_scsi_raw_xfer(rb_scsi_device_t dev, struct rb_scsi_raw_cmd_t *raw)
182{
183#define rb_printf(...) \
184 do{ if(dev->flags & RB_SCSI_DEBUG) dev->printf(dev->user, __VA_ARGS__); }while(0)
185 /* we need to allocate memory for SCSI_PASS_THROUGH_WITH_BUFFERS and the
186 * data and sense */
187 DWORD length = sizeof(SCSI_PASS_THROUGH) + raw->buf_len + raw->sense_len;
188 DWORD returned = 0;
189 SCSI_PASS_THROUGH *spt = malloc(length);
190 memset(spt, 0, length);
191 spt->Length = sizeof(SCSI_PASS_THROUGH);
192 spt->PathId = 0;
193 spt->TargetId = 0;
194 spt->Lun = 0;
195 spt->CdbLength = raw->cdb_len;
196 memcpy(spt->Cdb, raw->cdb, raw->cdb_len);
197 spt->SenseInfoLength = raw->sense_len;
198 spt->SenseInfoOffset = spt->Length; /* Sense after header */
199 switch(raw->dir)
200 {
201 case RB_SCSI_NONE: spt->DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; break;
202 case RB_SCSI_READ: spt->DataIn = SCSI_IOCTL_DATA_IN; break;
203 case RB_SCSI_WRITE: spt->DataIn = SCSI_IOCTL_DATA_OUT; break;
204 default:
205 free(spt);
206 rb_printf("rb_scsi: invalid direction"); return -1;
207 }
208 spt->DataTransferLength = raw->buf_len;
209 spt->DataBufferOffset = spt->SenseInfoOffset + spt->SenseInfoLength; /* Data after Sense */
210 spt->TimeOutValue = raw->tmo;
211 /* on write, copy data to buffer */
212 if(raw->dir == RB_SCSI_WRITE)
213 memcpy((BYTE *)spt + spt->DataBufferOffset, raw->buf, raw->buf_len);
214 BOOL status = DeviceIoControl(dev->handle, IOCTL_SCSI_PASS_THROUGH,
215 spt, length, spt, length, &returned, FALSE);
216 if(!status)
217 {
218 if(dev->flags & RB_SCSI_DEBUG)
219 {
220 LPSTR msg = NULL;
221 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
222 NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL);
223 printf(dev->user, "rb_scsi: ioctl failed: %s\n", msg);
224 LocalFree(msg);
225 }
226 free(spt);
227 return RB_SCSI_OS_ERROR;
228 }
229 /* on read, copy data from buffer */
230 if(raw->dir == RB_SCSI_READ)
231 {
232 raw->buf_len = spt->DataTransferLength;
233 memcpy(raw->buf, (BYTE *)spt + spt->DataBufferOffset, raw->buf_len);
234 }
235 /* check status */
236 raw->status = spt->ScsiStatus;
237 if(raw->status == RB_SCSI_CHECK_CONDITION || raw->status == RB_SCSI_COMMAND_TERMINATED)
238 {
239 memcpy(raw->sense, (BYTE *)spt + spt->SenseInfoOffset, raw->sense_len);
240 free(spt);
241 return RB_SCSI_SENSE;
242 }
243 else
244 raw->sense_len = 0;
245 free(spt);
246 return raw->status ? RB_SCSI_STATUS : RB_SCSI_OK;
247#undef rb_printf
248}
249
250void rb_scsi_close(rb_scsi_device_t dev)
251{
252 CloseHandle(dev->handle);
253 free(dev);
254}
255
256/* other targets */
257#else
258rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user,
259 rb_scsi_printf_t printf)
260{
261 if(printf == NULL)
262 printf = misc_std_printf;
263 (void) path;
264 if(flags & RB_SCSI_DEBUG)
265 printf(user, "rb_scsi: unimplemented on this platform\n");
266 return NULL;
267}
268
269int rb_scsi_raw_xfer(rb_scsi_device_t dev, struct rb_scsi_raw_cmd_t *raw)
270{
271 return RB_SCSI_ERROR;
272}
273
274void rb_scsi_close(rb_scsi_device_t dev)
275{
276 free(dev);
277}
278#endif
279
280void rb_scsi_decode_sense(rb_scsi_device_t dev, void *_sense, int sense_len)
281{
282#define rb_printf(...) \
283 do{ if(dev->flags & RB_SCSI_DEBUG) dev->printf(dev->user, __VA_ARGS__); }while(0)
284
285 uint8_t *sense = _sense;
286 uint8_t type = sense[0] & 0x7f;
287 switch(type)
288 {
289 case 0x70: case 0x73: rb_printf("Current sense: "); break;
290 case 0x71: case 0x72: rb_printf("Previous sense: "); break;
291 default: rb_printf("Unknown sense\n"); return;
292 }
293 unsigned key = sense[2] & 0xf;
294 switch(key)
295 {
296 case 0: rb_printf("no sense"); break;
297 case 1: rb_printf("recovered error"); break;
298 case 2: rb_printf("not ready"); break;
299 case 3: rb_printf("medium error"); break;
300 case 4: rb_printf("hardware error"); break;
301 case 5: rb_printf("illegal request"); break;
302 case 6: rb_printf("unit attention"); break;
303 case 7: rb_printf("data protect"); break;
304 case 8: rb_printf("blank check"); break;
305 case 9: rb_printf("vendor specific"); break;
306 case 10: rb_printf("copy aborted"); break;
307 case 11: rb_printf("aborted command"); break;
308 default: rb_printf("unknown key"); break;
309 }
310 rb_printf("\n");
311
312#undef rb_printf
313}
diff --git a/utils/scsi/rbscsi.h b/utils/scsi/rbscsi.h
new file mode 100644
index 0000000000..2b56aabad2
--- /dev/null
+++ b/utils/scsi/rbscsi.h
@@ -0,0 +1,83 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef __RB_SCSI_H__
22#define __RB_SCSI_H__
23
24#ifdef __cplusplus
25extern "C" {
26#endif
27
28struct rb_scsi_device_t;
29typedef struct rb_scsi_device_t *rb_scsi_device_t;
30
31typedef void (*rb_scsi_printf_t)(void *user, const char *fmt, ...);
32
33/* flags for rb_scsi_open */
34#define RB_SCSI_READ_ONLY (1 << 0)
35#define RB_SCSI_DEBUG (1 << 1)
36
37/* transfer direction */
38#define RB_SCSI_NONE 0
39#define RB_SCSI_READ 1
40#define RB_SCSI_WRITE 2
41
42/* most common status */
43#define RB_SCSI_GOOD 0
44#define RB_SCSI_CHECK_CONDITION 2
45#define RB_SCSI_COMMAND_TERMINATED 0x22
46
47/* return codes */
48#define RB_SCSI_OK 0 /* Everything worked */
49#define RB_SCSI_STATUS 1 /* Device returned an error in status */
50#define RB_SCSI_SENSE 2 /* Device returned sense data */
51#define RB_SCSI_OS_ERROR 3 /* Transfer failed, got OS error */
52#define RB_SCSI_ERROR 4 /* Transfer failed, got transfer/host error */
53
54/* structure for raw transfers */
55struct rb_scsi_raw_cmd_t
56{
57 int dir; /* direction: none, read or write */
58 int cdb_len; /* command buffer length */
59 void *cdb; /* command buffer */
60 int buf_len; /* data buffer length (will be overwritten with actual count) */
61 void *buf; /* buffer */
62 int sense_len; /* sense buffer length (will be overwritten with actual count) */
63 void *sense; /* sense buffer */
64 int tmo; /* timeout (in seconds) */
65 int status; /* status returned by device (STATUS) or errno (OS_ERROR) or other error (ERROR) */
66};
67
68/* open a device, returns a handle or NULL on error
69 * the caller can optionally provide an error printing function */
70rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user,
71 rb_scsi_printf_t printf);
72/* performs a raw transfer, returns !=0 on error */
73int rb_scsi_raw_xfer(rb_scsi_device_t dev, struct rb_scsi_raw_cmd_t *raw);
74/* decode sense and print information if debug flag is set */
75void rb_scsi_decode_sense(rb_scsi_device_t dev, void *sense, int sense_len);
76/* close a device */
77void rb_scsi_close(rb_scsi_device_t dev);
78
79#ifdef __cplusplus
80}
81#endif
82
83#endif /* __RB_SCSI_H__ */