Add spi(8), a utility for communicating with a device on a SPI bus from
userland, conceptually similar to what i2c(8) provides for i2c devices. Submitted by: Bob Frazier Differential Revision: https://reviews.freebsd.org/D15029
This commit is contained in:
parent
4251ebda6e
commit
ce55f499ef
@ -84,6 +84,7 @@ SUBDIR= adduser \
|
||||
setpmac \
|
||||
smbmsg \
|
||||
snapinfo \
|
||||
spi \
|
||||
spray \
|
||||
syslogd \
|
||||
sysrc \
|
||||
|
9
usr.sbin/spi/Makefile
Normal file
9
usr.sbin/spi/Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
# $FreeBSD$
|
||||
|
||||
#.include <src.opts.mk>
|
||||
|
||||
PROG= spi
|
||||
|
||||
MAN8= spi.8
|
||||
|
||||
.include <bsd.prog.mk>
|
197
usr.sbin/spi/spi.8
Normal file
197
usr.sbin/spi/spi.8
Normal file
@ -0,0 +1,197 @@
|
||||
.\" Copyright (c) 2018 by S.F.T. Inc.
|
||||
.\"
|
||||
.\" Redistribution and use in source and binary forms, with or without
|
||||
.\" modification, are permitted provided that the following conditions
|
||||
.\" are met:
|
||||
.\" 1. Redistributions of source code must retain the above copyright
|
||||
.\" notice, this list of conditions and the following disclaimer.
|
||||
.\" 2. Redistributions in binary form must reproduce the above copyright
|
||||
.\" notice, this list of conditions and the following disclaimer in the
|
||||
.\" documentation and/or other materials provided with the distribution.
|
||||
.\"
|
||||
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
.\" SUCH DAMAGE.
|
||||
.\"
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd "15 April 2018"
|
||||
.Dt spi 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm spi
|
||||
.Nd communicate on SPI bus with slave devices
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl f Ar device
|
||||
.Op Fl d Ar r|w|rw
|
||||
.Op Fl m Ar mode
|
||||
.Op Fl s Ar max-speed
|
||||
.Op Fl c Ar count
|
||||
.Op Fl C Ar cmd_bytes
|
||||
.Op Fl A
|
||||
.Op Fl b
|
||||
.Op Fl L
|
||||
.Op Fl v
|
||||
.Nm
|
||||
.Op Fl i
|
||||
.Op Fl f Ar device
|
||||
.Op Fl v
|
||||
.Nm
|
||||
.Op Fl h
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
utility can be used to perform raw data transfers
|
||||
.Pq read, write, or simultaneous read/write
|
||||
with devices on the SPI bus, via the
|
||||
.Xr spigen 4
|
||||
device.
|
||||
.Pp
|
||||
Each
|
||||
.Xr spigen 4
|
||||
device is associated with a specific
|
||||
.Sq chip select
|
||||
.Pq cs
|
||||
pin on the spibus, and therefore needs to be specified.
|
||||
If no device name is specified on the command line,
|
||||
.Nm
|
||||
assumes
|
||||
.Sq spigen0.0 .
|
||||
.Pp
|
||||
For more information on the spigen device, see
|
||||
.Xr spigen 4 .
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width ".Fl f Ar device"
|
||||
.It Fl A
|
||||
Specifies ASCII mode.
|
||||
Both read and write data is input and output as
|
||||
2-character hexadecimal values, optionally separated by white space,
|
||||
such as 00 01 02 etc.
|
||||
When combined with the
|
||||
.Sq -b
|
||||
flag, the data on stdin remains a sequence of ASCII hexadecimal
|
||||
byte values, but the output reverts to binary mode.
|
||||
.It Fl b
|
||||
Binary
|
||||
.Pq output
|
||||
mode.
|
||||
Only has an effect when
|
||||
.Sq -A
|
||||
has been specified.
|
||||
Reverts the output back to binary
|
||||
.Pq rather than ASCII ,
|
||||
while leaving the input format as-is.
|
||||
Use in combination with
|
||||
.Sq -A
|
||||
to allow using something like
|
||||
.Sq echo
|
||||
to pass hexadecimal values to the SPI device, but output the received data
|
||||
on stdout as binary.
|
||||
.It Fl C Ar command bytes
|
||||
Sends one or more
|
||||
.Sq command
|
||||
bytes, skipping any bytes read-in during the transfer.
|
||||
The byte values should be specified as a quoted parameter, similar to the
|
||||
format for data on stdin for
|
||||
.Sq -A ,
|
||||
that is, 2 character hexadecimal values, optionally separated by white space.
|
||||
An SPI device will typically require that a command be sent, followed by
|
||||
bytes of data.
|
||||
You can use this option to send the command without receiving any data bytes
|
||||
during the command sequence.
|
||||
.It Fl c Ar count
|
||||
The total number of bytes to transfer as a decimal integer.
|
||||
If a write or a read/write transaction is being performed, and fewer than
|
||||
this number of bytes are read in from stdin, the remaining bytes will be
|
||||
sent with a value of
|
||||
.Sq 0 .
|
||||
If the length can be determined from the input file size, you can use a
|
||||
.Sq count
|
||||
value of
|
||||
.Sq -1
|
||||
to base the transfer on the input file's size.
|
||||
.It Fl d Ar r|w|rw
|
||||
Transfer direction: Use
|
||||
.Sq r
|
||||
for read,
|
||||
.Sq w for write, and
|
||||
.Sq rw
|
||||
for simultaneous read and write.
|
||||
.It Fl f Ar device
|
||||
SPI device to use
|
||||
.Pq default is /dev/spigen0 .
|
||||
.It Fl h
|
||||
Print help text to stderr, explaining the command line options.
|
||||
.It Fl i
|
||||
Displays information about the SPI device to stderr.
|
||||
Whenever this flag is specified, no data is read or written, and the mode
|
||||
and clock speed are not changed.
|
||||
.It Fl L
|
||||
LSB bit order.
|
||||
The default is MSB, i.e., the highest order bit is
|
||||
transmitted first.
|
||||
Specifying -L caused the LSB to be transmitted and read first.
|
||||
.It Fl m Ar 0 - 3
|
||||
SPI mode, 0 through 3.
|
||||
This defines the clock phase and timing with respect to reading and writing
|
||||
data, as per the SPI specification.
|
||||
.It Fl s Ar speed
|
||||
Specify the maximum speed, in Hz, for the SPI clock.
|
||||
The bus will operate at its highest available speed which does not
|
||||
exceed this maximum.
|
||||
.It Fl v
|
||||
Specifies Verbose mode.
|
||||
Diagnostics and information are written to stderr.
|
||||
You can specify
|
||||
.Sq -v
|
||||
more than once to increase verbosity.
|
||||
.El
|
||||
.Sh EXAMPLES
|
||||
Here are a few examples of using the spi utility:
|
||||
.Bl -bullet
|
||||
.It
|
||||
Get information about the default SPI device
|
||||
.Pp
|
||||
spi -i
|
||||
.It
|
||||
Set the maximum clock speed to 200Khz and the mode to 3 on spigen0.1, but do
|
||||
not transmit nor receive any data
|
||||
.Pp
|
||||
spi -f spigen0.1 -s 200000 -m 3
|
||||
.It
|
||||
Send a command sequence consisting of 2 bytes, and read 2 additional bytes
|
||||
from the SPI device, using the current mode and speed on the default device
|
||||
.Pp
|
||||
spi -d r -C "00 01" -c 2
|
||||
.It
|
||||
Transmit a byte value of 5, and receive 2 bytes, displaying their values as
|
||||
2-byte ASCII hexadecimal, with mode 2, and a maximum clock speed of 500khz.
|
||||
.Pp
|
||||
echo "05" | spi -A -d rw -m 2 -s 500000 -c 2
|
||||
.It
|
||||
Send a binary file, and output the SPI result through
|
||||
.Sq od
|
||||
as hexadecimal bytes, using the current maximum clock speed and SPI mode.
|
||||
.Pp
|
||||
spi -d rw -c -1 <input_file.bin | od -An -t x1
|
||||
.It
|
||||
Send 2 bytes of data, receive a total of 4 bytes, and output the SPI result
|
||||
as binary data, piped through
|
||||
.Sq od ,
|
||||
displaying it as two hexadecimal unsigned short integer values.
|
||||
.Pp
|
||||
echo "00 01" | spi -A -b -d rw -c 4 | od -t x2
|
||||
.El
|
||||
.Pp
|
||||
.Sh SEE ALSO
|
||||
.Xr spigen 4
|
958
usr.sbin/spi/spi.c
Normal file
958
usr.sbin/spi/spi.c
Normal file
@ -0,0 +1,958 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2018 S.F.T. Inc.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioccom.h>
|
||||
#include <sys/spigenio.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <memory.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define DEFAULT_DEVICE_NAME "/dev/spigen0.0"
|
||||
|
||||
#define DEFAULT_BUFFER_SIZE 8192
|
||||
|
||||
#define DIR_READ 0
|
||||
#define DIR_WRITE 1
|
||||
#define DIR_READWRITE 2
|
||||
#define DIR_NONE -1
|
||||
|
||||
struct spi_options {
|
||||
int mode; /* mode (0,1,2,3, -1 == use default) */
|
||||
int speed; /* speed (in Hz, -1 == use default) */
|
||||
int count; /* count (0 through 'n' bytes, negative for
|
||||
* stdin length) */
|
||||
int binary; /* non-zero for binary output or zero for
|
||||
* ASCII output when ASCII != 0 */
|
||||
int ASCII; /* zero for binary input and output.
|
||||
* non-zero for ASCII input, 'binary'
|
||||
* determines output */
|
||||
int lsb; /* non-zero for LSB order (default order is
|
||||
* MSB) */
|
||||
int verbose; /* non-zero for verbosity */
|
||||
int ncmd; /* bytes to skip for incoming data */
|
||||
uint8_t *pcmd; /* command data (NULL if none) */
|
||||
};
|
||||
|
||||
static void usage(void);
|
||||
static int interpret_command_bytes(const char *parg, struct spi_options *popt);
|
||||
static void * prep_write_buffer(struct spi_options *popt);
|
||||
static int _read_write(int hdev, void *bufw, void *bufr, int cbrw, int lsb);
|
||||
static int _do_data_output(void *pr, struct spi_options *popt);
|
||||
static int get_info(int hdev, const char *dev_name);
|
||||
static int set_mode(int hdev, struct spi_options *popt);
|
||||
static int set_speed(int hdev, struct spi_options *popt);
|
||||
static int hexval(char c);
|
||||
static int perform_read(int hdev, struct spi_options *popt);
|
||||
static int perform_write(int hdev, struct spi_options *popt);
|
||||
static int perform_readwrite(int hdev, struct spi_options *popt);
|
||||
static void verbose_dump_buffer(void *pbuf, int icount, int lsb);
|
||||
|
||||
/*
|
||||
* LSB array - reversebits[n] is the LSB value of n as an MSB. Use this array
|
||||
* to obtain a reversed bit pattern of the index value when bits must
|
||||
* be sent/received in an LSB order vs the default MSB
|
||||
*/
|
||||
static uint8_t reversebits[256] = {
|
||||
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
|
||||
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
|
||||
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
|
||||
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
|
||||
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
|
||||
0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
|
||||
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
|
||||
0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
|
||||
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
|
||||
0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
|
||||
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
|
||||
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
|
||||
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
|
||||
0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
|
||||
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
|
||||
0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
|
||||
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
|
||||
0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
|
||||
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
|
||||
0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
|
||||
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
|
||||
0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
|
||||
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
|
||||
0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
|
||||
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
|
||||
0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
|
||||
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
|
||||
0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
|
||||
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
|
||||
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
|
||||
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
|
||||
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
usage(void)
|
||||
{
|
||||
fputs(getprogname(), stderr);
|
||||
fputs(" - communicate on SPI bus with slave devices\n"
|
||||
"Usage:\n"
|
||||
" spi [-f device] [-d r|w|rw] [-m mode] [-s max-speed] [-c count]\n"
|
||||
" [-C \"command bytes\"] [-A] [-b] [-L] [-v]\n"
|
||||
" spi -i [-f device] [-v]\n"
|
||||
" spi -h\n"
|
||||
" where\n"
|
||||
" -f specifies the device (default is spigen0.0)\n"
|
||||
" -d specifies the operation (r, w, or rw; default is rw)\n"
|
||||
" -m specifies the mode (0, 1, 2, or 3)\n"
|
||||
" -s specifies the maximum speed (default is 0, device default)\n"
|
||||
" -c specifies the number of data bytes to transfer (default 0, i.e. none)\n"
|
||||
" A negative value uses the length of the input data\n"
|
||||
" -C specifies 'command bytes' to be sent, as 2 byte hexadecimal values\n"
|
||||
" (these should be quoted, separated by optional white space)\n"
|
||||
" -L specifies 'LSB' order on the SPI bus (default is MSB)\n"
|
||||
" -i query information about the device\n"
|
||||
" -A uses ASCII for input/output as 2-digit hex values\n"
|
||||
" -b Override output format as binary (only valid with '-A')\n"
|
||||
" -v verbose output\n"
|
||||
" -h prints this message\n"
|
||||
"\n"
|
||||
"NOTE: setting the mode and/or speed is 'sticky'. Subsequent transactions\n"
|
||||
" on that device will, by default, use the previously set values.\n"
|
||||
"\n",
|
||||
stderr);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[], char *envp[] __unused)
|
||||
{
|
||||
struct spi_options opt;
|
||||
int err, ch, hdev, finfo, fdir;
|
||||
char *pstr;
|
||||
char dev_name[PATH_MAX * 2 + 5];
|
||||
|
||||
finfo = 0;
|
||||
fdir = DIR_NONE;
|
||||
|
||||
hdev = -1;
|
||||
err = 0;
|
||||
|
||||
dev_name[0] = 0;
|
||||
|
||||
opt.mode = -1;
|
||||
opt.speed = -1;
|
||||
opt.count = 0;
|
||||
opt.ASCII = 0;
|
||||
opt.binary = 0;
|
||||
opt.lsb = 0;
|
||||
opt.verbose = 0;
|
||||
opt.ncmd = 0;
|
||||
opt.pcmd = NULL;
|
||||
|
||||
while (!err && (ch = getopt(argc, argv, "f:d:m:s:c:C:AbLvih")) != -1) {
|
||||
switch (ch) {
|
||||
case 'd':
|
||||
if (optarg[0] == 'r') {
|
||||
if (optarg[1] == 'w' && optarg[2] == 0) {
|
||||
fdir = DIR_READWRITE;
|
||||
}
|
||||
else if (optarg[1] == 0) {
|
||||
fdir = DIR_READ;
|
||||
}
|
||||
}
|
||||
else if (optarg[0] == 'w' && optarg[1] == 0) {
|
||||
fdir = DIR_WRITE;
|
||||
}
|
||||
else {
|
||||
err = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
if (!optarg[0]) { /* unlikely */
|
||||
fputs("error - missing device name\n", stderr);
|
||||
err = 1;
|
||||
}
|
||||
else {
|
||||
if (optarg[0] == '/')
|
||||
strlcpy(dev_name, optarg,
|
||||
sizeof(dev_name));
|
||||
else
|
||||
snprintf(dev_name, sizeof(dev_name),
|
||||
"/dev/%s", optarg);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
opt.mode = (int)strtol(optarg, &pstr, 10);
|
||||
|
||||
if (!pstr || *pstr || opt.mode < 0 || opt.mode > 3) {
|
||||
fprintf(stderr, "Invalid mode specified: %s\n",
|
||||
optarg);
|
||||
err = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 's':
|
||||
opt.speed = (int)strtol(optarg, &pstr, 10);
|
||||
|
||||
if (!pstr || *pstr || opt.speed < 0) {
|
||||
fprintf(stderr, "Invalid speed specified: %s\n",
|
||||
optarg);
|
||||
err = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
opt.count = (int)strtol(optarg, &pstr, 10);
|
||||
|
||||
if (!pstr || *pstr) {
|
||||
fprintf(stderr, "Invalid count specified: %s\n",
|
||||
optarg);
|
||||
err = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
if(opt.pcmd) /* specified more than once */
|
||||
err = 1;
|
||||
else {
|
||||
/* get malloc'd buffer or error */
|
||||
if (interpret_command_bytes(optarg, &opt))
|
||||
err = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'A':
|
||||
opt.ASCII = 1;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
opt.binary = 1;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
opt.lsb = 1;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
opt.verbose++;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
finfo = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
err = 1;
|
||||
/* FALLTHROUGH */
|
||||
case 'h':
|
||||
usage();
|
||||
goto the_end;
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (err ||
|
||||
(fdir == DIR_NONE && !finfo && opt.mode == -1 && opt.speed == -1 && opt.count == 0)) {
|
||||
/*
|
||||
* if any of the direction, mode, speed, or count not specified,
|
||||
* print usage
|
||||
*/
|
||||
|
||||
usage();
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
if ((opt.count != 0 || opt.ncmd != 0) && fdir == DIR_NONE) {
|
||||
/*
|
||||
* count was specified, but direction was not. default is
|
||||
* read/write
|
||||
*/
|
||||
/*
|
||||
* this includes a negative count, which implies write from
|
||||
* stdin
|
||||
*/
|
||||
if (opt.count == 0)
|
||||
fdir = DIR_WRITE;
|
||||
else
|
||||
fdir = DIR_READWRITE;
|
||||
}
|
||||
|
||||
if (opt.count < 0 && fdir != DIR_READWRITE && fdir != DIR_WRITE) {
|
||||
fprintf(stderr, "Invalid length %d when not writing data\n",
|
||||
opt.count);
|
||||
|
||||
err = 1;
|
||||
usage();
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
|
||||
if (!dev_name[0]) /* no device name specified */
|
||||
strlcpy(dev_name, DEFAULT_DEVICE_NAME, sizeof(dev_name));
|
||||
|
||||
hdev = open(dev_name, O_RDWR);
|
||||
|
||||
if (hdev == -1) {
|
||||
fprintf(stderr, "Error - unable to open '%s', errno=%d\n",
|
||||
dev_name, errno);
|
||||
err = 1;
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
if (finfo) {
|
||||
err = get_info(hdev, dev_name);
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
/* check and assign mode, speed */
|
||||
|
||||
if (opt.mode != -1) {
|
||||
err = set_mode(hdev, &opt);
|
||||
|
||||
if (err)
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
if (opt.speed != -1) {
|
||||
err = set_speed(hdev, &opt);
|
||||
|
||||
if (err)
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
/* do data transfer */
|
||||
|
||||
if (fdir == DIR_READ) {
|
||||
err = perform_read(hdev, &opt);
|
||||
}
|
||||
else if (fdir == DIR_WRITE) {
|
||||
err = perform_write(hdev, &opt);
|
||||
}
|
||||
else if (fdir == DIR_READWRITE) {
|
||||
err = perform_readwrite(hdev, &opt);
|
||||
}
|
||||
|
||||
the_end:
|
||||
|
||||
if (hdev != -1)
|
||||
close(hdev);
|
||||
|
||||
free(opt.pcmd);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
static int
|
||||
interpret_command_bytes(const char *parg, struct spi_options *popt)
|
||||
{
|
||||
int ch, ch2, ctr, cbcmd, err;
|
||||
const char *ppos;
|
||||
void *ptemp;
|
||||
uint8_t *pcur;
|
||||
|
||||
err = 0;
|
||||
cbcmd = DEFAULT_BUFFER_SIZE; /* initial cmd buffer size */
|
||||
popt->pcmd = (uint8_t *)malloc(cbcmd);
|
||||
|
||||
if (!popt->pcmd)
|
||||
return 1;
|
||||
|
||||
pcur = popt->pcmd;
|
||||
|
||||
ctr = 0;
|
||||
ppos = parg;
|
||||
|
||||
while (*ppos) {
|
||||
while (*ppos && *ppos <= ' ') {
|
||||
ppos++; /* skip (optional) leading white space */
|
||||
}
|
||||
|
||||
if (!*ppos)
|
||||
break; /* I am done */
|
||||
|
||||
ch = hexval(*(ppos++));
|
||||
if (ch < 0 || !*ppos) { /* must be valid pair of hex characters */
|
||||
err = 1;
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
ch2 = hexval(*(ppos++));
|
||||
if (ch2 < 0) {
|
||||
err = 1;
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
ch = (ch * 16 + ch2) & 0xff; /* convert to byte */
|
||||
|
||||
if (ctr >= cbcmd) { /* need re-alloc buffer? (unlikely) */
|
||||
cbcmd += 8192; /* increase by additional 8k */
|
||||
ptemp = realloc(popt->pcmd, cbcmd);
|
||||
|
||||
if (!ptemp) {
|
||||
err = 1;
|
||||
fprintf(stderr,
|
||||
"Not enough memory to interpret command bytes, errno=%d\n",
|
||||
errno);
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
popt->pcmd = (uint8_t *)ptemp;
|
||||
pcur = popt->pcmd + ctr;
|
||||
}
|
||||
|
||||
if (popt->lsb)
|
||||
*pcur = reversebits[ch];
|
||||
else
|
||||
*pcur = (uint8_t)ch;
|
||||
|
||||
pcur++;
|
||||
ctr++;
|
||||
}
|
||||
|
||||
popt->ncmd = ctr; /* record num bytes in '-C' argument */
|
||||
|
||||
the_end:
|
||||
|
||||
/* at this point popt->pcmd is NULL or a valid pointer */
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
get_info(int hdev, const char *dev_name)
|
||||
{
|
||||
uint32_t fmode, fspeed;
|
||||
int err;
|
||||
char temp_buf[PATH_MAX], cpath[PATH_MAX];
|
||||
|
||||
if (!realpath(dev_name, cpath)) /* get canonical name for info purposes */
|
||||
strlcpy(cpath, temp_buf, sizeof(cpath)); /* this shouldn't happen */
|
||||
|
||||
err = ioctl(hdev, SPIGENIOC_GET_SPI_MODE, &fmode);
|
||||
|
||||
if (err == 0)
|
||||
err = ioctl(hdev, SPIGENIOC_GET_CLOCK_SPEED, &fspeed);
|
||||
|
||||
if (err == 0) {
|
||||
fprintf(stderr,
|
||||
"Device name: %s\n"
|
||||
"Device mode: %d\n"
|
||||
"Device speed: %d\n",
|
||||
cpath, fmode, fspeed);//, max_cmd, max_data, temp_buf);
|
||||
}
|
||||
else
|
||||
fprintf(stderr, "Unable to query info (err=%d), errno=%d\n",
|
||||
err, errno);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
set_mode(int hdev, struct spi_options *popt)
|
||||
{
|
||||
uint32_t fmode = popt->mode;
|
||||
|
||||
if (popt->mode < 0) /* use default? */
|
||||
return 0;
|
||||
|
||||
return ioctl(hdev, SPIGENIOC_SET_SPI_MODE, &fmode);
|
||||
}
|
||||
|
||||
static int
|
||||
set_speed(int hdev, struct spi_options *popt)
|
||||
{
|
||||
uint32_t clock_speed = popt->speed;
|
||||
|
||||
if (popt->speed < 0)
|
||||
return 0;
|
||||
|
||||
return ioctl(hdev, SPIGENIOC_SET_CLOCK_SPEED, &clock_speed);
|
||||
}
|
||||
|
||||
static int
|
||||
hexval(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 10;
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void *
|
||||
prep_write_buffer(struct spi_options *popt)
|
||||
{
|
||||
int ch, ch2, ch3, ncmd, lsb, err;
|
||||
uint8_t *pdata, *pdat2;
|
||||
size_t cbdata, cbread;
|
||||
const char *szbytes;
|
||||
|
||||
ncmd = popt->ncmd; /* num command bytes (can be zero) */
|
||||
|
||||
if (ncmd == 0 && popt->count == 0)
|
||||
return NULL; /* always since it's an error if it happens
|
||||
* now */
|
||||
|
||||
if (popt->count < 0) {
|
||||
cbdata = DEFAULT_BUFFER_SIZE;
|
||||
}
|
||||
else {
|
||||
cbdata = popt->count;
|
||||
}
|
||||
|
||||
lsb = popt->lsb; /* non-zero if LSB order; else MSB */
|
||||
|
||||
pdata = malloc(cbdata + ncmd + 1);
|
||||
cbread = 0;
|
||||
|
||||
err = 0;
|
||||
|
||||
if (!pdata)
|
||||
return NULL;
|
||||
|
||||
if (popt->pcmd && ncmd > 0) {
|
||||
memcpy(pdata, popt->pcmd, ncmd); /* copy command bytes */
|
||||
pdat2 = pdata + ncmd;
|
||||
}
|
||||
else
|
||||
pdat2 = pdata; /* no prepended command data */
|
||||
|
||||
/*
|
||||
* read up to 'cbdata' bytes. If I get an EOF, do one of two things:
|
||||
* a) change the data count to match how many bytes I read in b) fill
|
||||
* the rest of the input buffer with zeros
|
||||
*
|
||||
* If the specified length is negative, I do 'a', else 'b'
|
||||
*/
|
||||
|
||||
while (!err && cbread < cbdata && (ch = fgetc(stdin)) != EOF) {
|
||||
if (popt->ASCII) {
|
||||
/* skip consecutive white space */
|
||||
|
||||
while (ch <= ' ') {
|
||||
if ((ch = fgetc(stdin)) == EOF)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch != EOF) {
|
||||
ch2 = hexval(ch);
|
||||
|
||||
if (ch2 < 0) {
|
||||
invalid_character:
|
||||
fprintf(stderr,
|
||||
"Invalid input character '%c'\n", ch);
|
||||
err = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
ch = fgetc(stdin);
|
||||
|
||||
if (ch != EOF) {
|
||||
ch3 = hexval(ch);
|
||||
|
||||
if (ch3 < 0)
|
||||
goto invalid_character;
|
||||
|
||||
ch = ch2 * 16 + ch3;
|
||||
}
|
||||
}
|
||||
|
||||
if (err || ch == EOF)
|
||||
break;
|
||||
}
|
||||
|
||||
/* for LSB, flip the bits - otherwise, just copy the value */
|
||||
if (lsb)
|
||||
pdat2[cbread] = reversebits[ch];
|
||||
else
|
||||
pdat2[cbread] = (uint8_t) ch;
|
||||
|
||||
cbread++; /* increment num bytes read so far */
|
||||
}
|
||||
|
||||
/* if it was an error, not an EOF, that ended the I/O, return NULL */
|
||||
|
||||
if (err || ferror(stdin)) {
|
||||
free(pdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (popt->verbose > 0) {
|
||||
const char *sz_bytes;
|
||||
|
||||
if (cbread != 1)
|
||||
sz_bytes = "bytes"; /* correct plurality of 'byte|bytes' */
|
||||
else
|
||||
sz_bytes = "byte";
|
||||
|
||||
if (popt->ASCII)
|
||||
fprintf(stderr, "ASCII input of %zd %s\n", cbread,
|
||||
sz_bytes);
|
||||
else
|
||||
fprintf(stderr, "Binary input of %zd %s\n", cbread,
|
||||
sz_bytes);
|
||||
}
|
||||
|
||||
/*
|
||||
* if opt.count is negative, copy actual byte count to opt.count which does
|
||||
* not include any of the 'command' bytes that are being sent. Can be zero.
|
||||
*/
|
||||
if (popt->count < 0) {
|
||||
popt->count = cbread;
|
||||
}
|
||||
/*
|
||||
* for everything else, fill the rest of the read buffer with '0'
|
||||
* bytes, as per the standard practice for SPI
|
||||
*/
|
||||
else {
|
||||
while (cbread < cbdata)
|
||||
pdat2[cbread++] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* popt->count bytes will be sent and read from the SPI, preceded by the
|
||||
* 'popt->ncmd' command bytes (if any).
|
||||
* So we must use 'popt->count' and 'popt->ncmd' from this point on in
|
||||
* the code.
|
||||
*/
|
||||
|
||||
if (popt->verbose > 0 && popt->count + popt->ncmd) {
|
||||
if ((popt->count + popt->ncmd) == 1)
|
||||
szbytes = "byte";
|
||||
else
|
||||
szbytes = "bytes";
|
||||
|
||||
fprintf(stderr, "Writing %d %s to SPI device\n",
|
||||
popt->count + popt->ncmd, szbytes);
|
||||
|
||||
verbose_dump_buffer(pdata, popt->count + popt->ncmd, lsb);
|
||||
}
|
||||
|
||||
return pdata;
|
||||
}
|
||||
|
||||
static int
|
||||
_read_write(int hdev, void *bufw, void *bufr, int cbrw, int lsb)
|
||||
{
|
||||
int err, ctr;
|
||||
struct spigen_transfer spi;
|
||||
|
||||
if (!cbrw)
|
||||
return 0;
|
||||
|
||||
if (!bufr)
|
||||
bufr = bufw;
|
||||
else
|
||||
memcpy(bufr, bufw, cbrw); /* transaction uses bufr for
|
||||
* both R and W */
|
||||
|
||||
bzero(&spi, sizeof(spi)); /* zero structure first */
|
||||
|
||||
/* spigen code seems to suggest there must be at least 1 command byte */
|
||||
|
||||
spi.st_command.iov_base = bufr;
|
||||
spi.st_command.iov_len = cbrw;
|
||||
|
||||
/*
|
||||
* The remaining members for spi.st_data are zero - all bytes are
|
||||
* 'command' for this. The driver doesn't really do anything different
|
||||
* for 'command' vs 'data' and at least one command byte must be sent in
|
||||
* the transaction.
|
||||
*/
|
||||
|
||||
err = ioctl(hdev, SPIGENIOC_TRANSFER, &spi) < 0 ? -1 : 0;
|
||||
|
||||
if (!err && lsb) {
|
||||
/* flip the bits for 'lsb' mode */
|
||||
for (ctr = 0; ctr < cbrw; ctr++) {
|
||||
((uint8_t *) bufr)[ctr] =
|
||||
reversebits[((uint8_t *)bufr)[ctr]];
|
||||
}
|
||||
}
|
||||
|
||||
if (err)
|
||||
fprintf(stderr, "Error performing SPI transaction, errno=%d\n",
|
||||
errno);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
_do_data_output(void *pr, struct spi_options *popt)
|
||||
{
|
||||
int err, index, icount;
|
||||
const char *sz_bytes, *sz_byte2;
|
||||
const uint8_t *pbuf;
|
||||
|
||||
pbuf = (uint8_t *)pr + popt->ncmd; /* only the data we want */
|
||||
icount = popt->count;
|
||||
err = 0;
|
||||
|
||||
if (icount <= 0) {
|
||||
return -1; /* should not but could happen */
|
||||
}
|
||||
|
||||
if (icount != 1)
|
||||
sz_bytes = "bytes"; /* correct plurality of 'byte|bytes' */
|
||||
else
|
||||
sz_bytes = "byte";
|
||||
|
||||
if (popt->ncmd != 1)
|
||||
sz_byte2 = "bytes";
|
||||
else
|
||||
sz_byte2 = "byte";
|
||||
|
||||
/* binary on stdout */
|
||||
if (popt->binary || !popt->ASCII) {
|
||||
if (popt->verbose > 0)
|
||||
fprintf(stderr, "Binary output of %d %s\n", icount,
|
||||
sz_bytes);
|
||||
|
||||
err = (int)fwrite(pbuf, 1, icount, stdout) != icount;
|
||||
}
|
||||
else if (icount > 0) {
|
||||
if (popt->verbose > 0)
|
||||
fprintf(stderr, "ASCII output of %d %s\n", icount,
|
||||
sz_bytes);
|
||||
|
||||
/* ASCII output */
|
||||
for (index = 0; !err && index < icount; index++) {
|
||||
if (index) {
|
||||
/*
|
||||
* not the first time, insert separating space
|
||||
*/
|
||||
err = fputc(' ', stdout) == EOF;
|
||||
}
|
||||
|
||||
if (!err)
|
||||
err = fprintf(stdout, "%02hhx", pbuf[index]) < 0;
|
||||
}
|
||||
|
||||
if (!err)
|
||||
err = fputc('\n', stdout) == EOF;
|
||||
}
|
||||
|
||||
/* verbose text out on stderr */
|
||||
|
||||
if (err)
|
||||
fprintf(stderr, "Error writing to stdout, errno=%d\n", errno);
|
||||
else if (popt->verbose > 0 && icount) {
|
||||
fprintf(stderr,
|
||||
"%d command %s and %d data %s read from SPI device\n",
|
||||
popt->ncmd, sz_byte2, icount, sz_bytes);
|
||||
|
||||
/* verbose output will show the command bytes as well */
|
||||
verbose_dump_buffer(pr, icount + popt->ncmd, popt->lsb);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
perform_read(int hdev, struct spi_options *popt)
|
||||
{
|
||||
int icount, err;
|
||||
void *pr, *pw;
|
||||
|
||||
pr = NULL;
|
||||
icount = popt->count + popt->ncmd;
|
||||
|
||||
/* prep write buffer filled with 0 bytes */
|
||||
pw = malloc(icount);
|
||||
|
||||
if (!pw) {
|
||||
err = -1;
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
bzero(pw, icount);
|
||||
|
||||
/* if I included a command sequence, copy bytes to the write buf */
|
||||
if (popt->pcmd && popt->ncmd > 0)
|
||||
memcpy(pw, popt->pcmd, popt->ncmd);
|
||||
|
||||
pr = malloc(icount + 1);
|
||||
|
||||
if (!pr) {
|
||||
err = -2;
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
bzero(pr, icount);
|
||||
|
||||
err = _read_write(hdev, pw, pr, icount, popt->lsb);
|
||||
|
||||
if (!err && popt->count > 0)
|
||||
err = _do_data_output(pr, popt);
|
||||
|
||||
the_end:
|
||||
|
||||
free(pr);
|
||||
free(pw);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
perform_write(int hdev, struct spi_options *popt)
|
||||
{
|
||||
int err;
|
||||
void *pw;
|
||||
|
||||
/* read data from cmd buf and stdin and write to 'write' buffer */
|
||||
|
||||
pw = prep_write_buffer(popt);
|
||||
|
||||
if (!pw) {
|
||||
err = -1;
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
err = _read_write(hdev, pw, NULL, popt->count + popt->ncmd, popt->lsb);
|
||||
|
||||
the_end:
|
||||
|
||||
free(pw);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
perform_readwrite(int hdev, struct spi_options *popt)
|
||||
{
|
||||
int icount, err;
|
||||
void *pr, *pw;
|
||||
|
||||
pr = NULL;
|
||||
|
||||
pw = prep_write_buffer(popt);
|
||||
icount = popt->count + popt->ncmd; /* assign after fn call */
|
||||
|
||||
if (!pw) {
|
||||
err = -1;
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
pr = malloc(icount + 1);
|
||||
|
||||
if (!pr) {
|
||||
err = -2;
|
||||
goto the_end;
|
||||
}
|
||||
|
||||
bzero(pr, icount);
|
||||
|
||||
err = _read_write(hdev, pw, pr, icount, popt->lsb);
|
||||
|
||||
if (!err)
|
||||
err = _do_data_output(pr, popt);
|
||||
|
||||
the_end:
|
||||
|
||||
free(pr);
|
||||
free(pw);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
verbose_dump_buffer(void *pbuf, int icount, int lsb)
|
||||
{
|
||||
uint8_t ch;
|
||||
int ictr, ictr2, index;
|
||||
|
||||
fputs(" | 0 1 2 3 4 5 6 7 8 9 A B C D E F "
|
||||
"| |\n", stderr);
|
||||
|
||||
for (ictr = 0; ictr < icount; ictr += 16) {
|
||||
fprintf(stderr, " %6x | ", ictr & 0xfffff0);
|
||||
|
||||
for (ictr2 = 0; ictr2 < 16; ictr2++) {
|
||||
index = ictr + ictr2;
|
||||
|
||||
if (index < icount) {
|
||||
ch = ((uint8_t *) pbuf)[index];
|
||||
|
||||
if (lsb)
|
||||
ch = reversebits[ch];
|
||||
|
||||
fprintf(stderr, "%02hhx ", ch);
|
||||
}
|
||||
else {
|
||||
fputs(" ", stderr);
|
||||
}
|
||||
}
|
||||
|
||||
fputs("| ", stderr);
|
||||
|
||||
for (ictr2 = 0; ictr2 < 16; ictr2++) {
|
||||
index = ictr + ictr2;
|
||||
|
||||
if (index < icount) {
|
||||
ch = ((uint8_t *) pbuf)[index];
|
||||
|
||||
if (lsb)
|
||||
ch = reversebits[ch];
|
||||
|
||||
if (ch < ' ' || ch > 127)
|
||||
goto out_of_range;
|
||||
|
||||
fprintf(stderr, "%c", ch);
|
||||
}
|
||||
else if (index < icount) {
|
||||
out_of_range:
|
||||
fputc('.', stderr);
|
||||
}
|
||||
else {
|
||||
fputc(' ', stderr);
|
||||
}
|
||||
}
|
||||
|
||||
fputs(" |\n", stderr);
|
||||
}
|
||||
|
||||
fflush(stderr);
|
||||
}
|
Loading…
Reference in New Issue
Block a user