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:
Ian Lepore 2018-06-22 01:59:19 +00:00
parent b7a8f5e16f
commit 6666009530
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=335527
4 changed files with 1165 additions and 0 deletions

View File

@ -84,6 +84,7 @@ SUBDIR= adduser \
setpmac \
smbmsg \
snapinfo \
spi \
spray \
syslogd \
sysrc \

9
usr.sbin/spi/Makefile Normal file
View 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
View 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
View 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);
}