spi-transfer: Add SPI transfer helper routines

These routines assume that any messages is composed of a write and/or
read part. While the API allows sending and receiving as part of one
messages/transfer, it does not permit full duplex operation where data is
both send and received at the same time.
This commit is contained in:
Benjamin Berg 2021-01-18 16:41:01 +01:00 committed by Benjamin Berg
parent b0d9d00762
commit 7fcce7891a
6 changed files with 612 additions and 0 deletions

View file

@ -29,4 +29,5 @@
#include "fpi-log.h"
#include "fpi-print.h"
#include "fpi-usb-transfer.h"
#include "fpi-spi-transfer.h"
#include "fpi-ssm.h"

View file

@ -0,0 +1,432 @@
/*
* FPrint SPI transfer handling
* Copyright (C) 2019-2020 Benjamin Berg <bberg@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "fpi-spi-transfer.h"
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <errno.h>
/**
* SECTION:fpi-spi-transfer
* @title: SPI transfer helpers
* @short_description: Helpers to ease SPI transfers
*
* #FpiSpiTransfer is a structure to simplify the SPI transfer handling
* for the linux spidev device. The main goal are to ease memory management
* and provide a usable asynchronous API to libfprint drivers.
*
* Currently only transfers with a write and subsequent read are supported.
*
* Drivers should always use this API rather than calling read/write/ioctl on
* the spidev device.
*
* Setting G_MESSAGES_DEBUG and FP_DEBUG_TRANSFER will result in the message
* content to be dumped.
*/
G_DEFINE_BOXED_TYPE (FpiSpiTransfer, fpi_spi_transfer, fpi_spi_transfer_ref, fpi_spi_transfer_unref)
static void
dump_buffer (guchar *buf, gssize dump_len)
{
g_autoptr(GString) line = NULL;
line = g_string_new ("");
/* Dump the buffer. */
for (gssize i = 0; i < dump_len; i++)
{
g_string_append_printf (line, "%02x ", buf[i]);
if ((i + 1) % 16 == 0)
{
g_debug ("%s", line->str);
g_string_set_size (line, 0);
}
}
if (line->len)
g_debug ("%s", line->str);
}
static void
log_transfer (FpiSpiTransfer *transfer, gboolean submit, GError *error)
{
if (g_getenv ("FP_DEBUG_TRANSFER"))
{
if (submit)
{
g_debug ("Transfer %p submitted, write length %zd, read length %zd",
transfer,
transfer->length_wr,
transfer->length_rd);
if (transfer->buffer_wr)
dump_buffer (transfer->buffer_wr, transfer->length_wr);
}
else
{
g_autofree gchar *error_str = NULL;
if (error)
error_str = g_strdup_printf ("with error (%s)", error->message);
else
error_str = g_strdup ("successfully");
g_debug ("Transfer %p completed %s, write length %zd, read length %zd",
transfer,
error_str,
transfer->length_wr,
transfer->length_rd);
if (transfer->buffer_rd)
dump_buffer (transfer->buffer_rd, transfer->length_rd);
}
}
}
/**
* fpi_spi_transfer_new:
* @device: The #FpDevice the transfer is for
* @spidev_fd: The file descriptor for the spidev device
*
* Creates a new #FpiSpiTransfer.
*
* Returns: (transfer full): A newly created #FpiSpiTransfer
*/
FpiSpiTransfer *
fpi_spi_transfer_new (FpDevice * device, int spidev_fd)
{
FpiSpiTransfer *self;
g_assert (FP_IS_DEVICE (device));
self = g_slice_new0 (FpiSpiTransfer);
self->ref_count = 1;
/* Purely to enhance the debug log output. */
self->length_wr = -1;
self->length_rd = -1;
self->device = device;
self->spidev_fd = spidev_fd;
return self;
}
static void
fpi_spi_transfer_free (FpiSpiTransfer *self)
{
g_assert (self);
g_assert_cmpint (self->ref_count, ==, 0);
if (self->free_buffer_wr && self->buffer_wr)
self->free_buffer_wr (self->buffer_wr);
if (self->free_buffer_rd && self->buffer_rd)
self->free_buffer_rd (self->buffer_rd);
self->buffer_wr = NULL;
self->buffer_rd = NULL;
g_slice_free (FpiSpiTransfer, self);
}
/**
* fpi_spi_transfer_ref:
* @self: A #FpiSpiTransfer
*
* Increments the reference count of @self by one.
*
* Returns: (transfer full): @self
*/
FpiSpiTransfer *
fpi_spi_transfer_ref (FpiSpiTransfer *self)
{
g_return_val_if_fail (self, NULL);
g_return_val_if_fail (self->ref_count, NULL);
g_atomic_int_inc (&self->ref_count);
return self;
}
/**
* fpi_spi_transfer_unref:
* @self: A #FpiSpiTransfer
*
* Decrements the reference count of @self by one, freeing the structure when
* the reference count reaches zero.
*/
void
fpi_spi_transfer_unref (FpiSpiTransfer *self)
{
g_return_if_fail (self);
g_return_if_fail (self->ref_count);
if (g_atomic_int_dec_and_test (&self->ref_count))
fpi_spi_transfer_free (self);
}
/**
* fpi_spi_transfer_write:
* @transfer: The #FpiSpiTransfer
* @length: The buffer size to allocate
*
* Prepare the write part of an SPI transfer allocating a new buffer
* internally that will be free'ed automatically.
*/
void
fpi_spi_transfer_write (FpiSpiTransfer *transfer,
gsize length)
{
fpi_spi_transfer_write_full (transfer,
g_malloc0 (length),
length,
g_free);
}
/**
* fpi_spi_transfer_write_full:
* @transfer: The #FpiSpiTransfer
* @buffer: The data to write.
* @length: The size of @buffer
* @free_func: (destroy buffer): Destroy notify for @buffer
*
* Prepare the write part of an SPI transfer.
*/
void
fpi_spi_transfer_write_full (FpiSpiTransfer *transfer,
guint8 *buffer,
gsize length,
GDestroyNotify free_func)
{
g_assert (buffer != NULL);
g_return_if_fail (transfer);
/* Write is always before read, so ensure both are NULL. */
g_return_if_fail (transfer->buffer_wr == NULL);
g_return_if_fail (transfer->buffer_rd == NULL);
transfer->buffer_wr = buffer;
transfer->length_wr = length;
transfer->free_buffer_wr = free_func;
}
/**
* fpi_spi_transfer_read:
* @transfer: The #FpiSpiTransfer
* @length: The buffer size to allocate
*
* Prepare the read part of an SPI transfer allocating a new buffer
* internally that will be free'ed automatically.
*/
void
fpi_spi_transfer_read (FpiSpiTransfer *transfer,
gsize length)
{
fpi_spi_transfer_read_full (transfer,
g_malloc0 (length),
length,
g_free);
}
/**
* fpi_spi_transfer_read_full:
* @transfer: The #FpiSpiTransfer
* @buffer: Buffer to read data into.
* @length: The size of @buffer
* @free_func: (destroy buffer): Destroy notify for @buffer
*
* Prepare the read part of an SPI transfer.
*/
void
fpi_spi_transfer_read_full (FpiSpiTransfer *transfer,
guint8 *buffer,
gsize length,
GDestroyNotify free_func)
{
g_assert (buffer != NULL);
g_return_if_fail (transfer);
g_return_if_fail (transfer->buffer_rd == NULL);
transfer->buffer_rd = buffer;
transfer->length_rd = length;
transfer->free_buffer_rd = free_func;
}
static void
transfer_finish_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
GTask *task = G_TASK (res);
FpiSpiTransfer *transfer = g_task_get_task_data (task);
GError *error = NULL;
FpiSpiTransferCallback callback;
g_task_propagate_boolean (task, &error);
log_transfer (transfer, FALSE, error);
callback = transfer->callback;
transfer->callback = NULL;
callback (transfer, transfer->device, transfer->user_data, error);
}
static void
transfer_thread_func (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
FpiSpiTransfer *transfer = (FpiSpiTransfer *) task_data;
struct spi_ioc_transfer xfer[2];
int transfers = 0;
int status;
if (transfer->buffer_wr == NULL && transfer->buffer_rd == NULL)
{
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_INVALID_ARGUMENT,
"Transfer with neither write or read!");
return;
}
memset (xfer, 0, sizeof (xfer));
if (transfer->buffer_wr)
{
xfer[transfers].tx_buf = (guint64) transfer->buffer_wr;
xfer[transfers].len = transfer->length_wr;
transfers += 1;
}
if (transfer->buffer_rd)
{
xfer[transfers].rx_buf = (guint64) transfer->buffer_rd;
xfer[transfers].len = transfer->length_rd;
transfers += 1;
}
/* This ioctl cannot be interrupted. */
status = ioctl (transfer->spidev_fd, SPI_IOC_MESSAGE (transfers), xfer);
if (status < 0)
{
g_task_return_new_error (task,
G_IO_ERROR,
g_io_error_from_errno (errno),
"Error invoking ioctl for SPI transfer (%d)",
errno);
}
else
{
g_task_return_boolean (task, TRUE);
}
}
/**
* fpi_spi_transfer_submit:
* @transfer: (transfer full): The transfer to submit, must have been filled.
* @cancellable: Cancellable to use, e.g. fpi_device_get_cancellable()
* @callback: Callback on completion or error
* @user_data: Data to pass to callback
*
* Submit an SPI transfer with a specific timeout and callback functions.
*
* The underlying transfer cannot be cancelled. The current implementation
* will only call @callback after the transfer has been completed.
*
* Note that #FpiSpiTransfer will be stolen when this function is called.
* So that all associated data will be free'ed automatically, after the
* callback ran unless fpi_usb_transfer_ref() is explicitly called.
*/
void
fpi_spi_transfer_submit (FpiSpiTransfer *transfer,
GCancellable *cancellable,
FpiSpiTransferCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_return_if_fail (transfer);
g_return_if_fail (callback);
/* Recycling is allowed, but not two at the same time. */
g_return_if_fail (transfer->callback == NULL);
transfer->callback = callback;
transfer->user_data = user_data;
log_transfer (transfer, TRUE, NULL);
task = g_task_new (transfer->device,
cancellable,
transfer_finish_cb,
NULL);
g_task_set_task_data (task,
g_steal_pointer (&transfer),
(GDestroyNotify) fpi_spi_transfer_unref);
g_task_run_in_thread (task, transfer_thread_func);
}
/**
* fpi_spi_transfer_submit_sync:
* @transfer: The transfer to submit, must have been filled.
* @error: Location to store #GError to
*
* Synchronously submit an SPI transfer. Use of this function is discouraged
* as it will block all other operations in the application.
*
* Note that you still need to fpi_spi_transfer_unref() the
* #FpiSpiTransfer afterwards.
*
* Returns: #TRUE on success, otherwise #FALSE and @error will be set
*/
gboolean
fpi_spi_transfer_submit_sync (FpiSpiTransfer *transfer,
GError **error)
{
g_autoptr(GTask) task = NULL;
GError *err = NULL;
gboolean res;
g_return_val_if_fail (transfer, FALSE);
/* Recycling is allowed, but not two at the same time. */
g_return_val_if_fail (transfer->callback == NULL, FALSE);
log_transfer (transfer, TRUE, NULL);
task = g_task_new (transfer->device,
NULL,
NULL,
NULL);
g_task_set_task_data (task,
fpi_spi_transfer_ref (transfer),
(GDestroyNotify) fpi_spi_transfer_unref);
g_task_run_in_thread_sync (task, transfer_thread_func);
res = g_task_propagate_boolean (task, &err);
log_transfer (transfer, FALSE, err);
g_propagate_error (error, err);
return res;
}

View file

@ -0,0 +1,113 @@
/*
* FPrint spidev transfer handling
* Copyright (C) 2019-2020 Benjamin Berg <bberg@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include "fpi-compat.h"
#include "fpi-device.h"
G_BEGIN_DECLS
#define FPI_TYPE_SPI_TRANSFER (fpi_spi_transfer_get_type ())
typedef struct _FpiSpiTransfer FpiSpiTransfer;
typedef struct _FpiSsm FpiSsm;
typedef void (*FpiSpiTransferCallback)(FpiSpiTransfer *transfer,
FpDevice *dev,
gpointer user_data,
GError *error);
/**
* FpiSpiTransfer:
* @device: The #FpDevice that the transfer belongs to.
* @ssm: Storage slot to associate the transfer with a state machine.
* Used by fpi_ssm_spi_transfer_cb() to modify the given state machine.
* @length_wr: The length of the write buffer
* @length_rd: The length of the read buffer
* @buffer_wr: The write buffer.
* @buffer_rd: The read buffer.
*
* Helper for handling SPI transfers. Currently transfers can either be pure
* write/read transfers or a write followed by a read (full duplex support
* can easily be added if desired).
*/
struct _FpiSpiTransfer
{
/*< public >*/
FpDevice *device;
FpiSsm *ssm;
gssize length_wr;
gssize length_rd;
guchar *buffer_wr;
guchar *buffer_rd;
/*< private >*/
guint ref_count;
int spidev_fd;
/* Callbacks */
gpointer user_data;
FpiSpiTransferCallback callback;
/* Data free function */
GDestroyNotify free_buffer_wr;
GDestroyNotify free_buffer_rd;
};
GType fpi_spi_transfer_get_type (void) G_GNUC_CONST;
FpiSpiTransfer *fpi_spi_transfer_new (FpDevice *device,
int spidev_fd);
FpiSpiTransfer *fpi_spi_transfer_ref (FpiSpiTransfer *self);
void fpi_spi_transfer_unref (FpiSpiTransfer *self);
void fpi_spi_transfer_write (FpiSpiTransfer *transfer,
gsize length);
FP_GNUC_ACCESS (read_only, 2, 3)
void fpi_spi_transfer_write_full (FpiSpiTransfer *transfer,
guint8 *buffer,
gsize length,
GDestroyNotify free_func);
void fpi_spi_transfer_read (FpiSpiTransfer *transfer,
gsize length);
FP_GNUC_ACCESS (write_only, 2, 3)
void fpi_spi_transfer_read_full (FpiSpiTransfer *transfer,
guint8 *buffer,
gsize length,
GDestroyNotify free_func);
void fpi_spi_transfer_submit (FpiSpiTransfer *transfer,
GCancellable *cancellable,
FpiSpiTransferCallback callback,
gpointer user_data);
gboolean fpi_spi_transfer_submit_sync (FpiSpiTransfer *transfer,
GError **error);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpiSpiTransfer, fpi_spi_transfer_unref)
G_END_DECLS

View file

@ -737,3 +737,56 @@ fpi_ssm_usb_transfer_with_weak_pointer_cb (FpiUsbTransfer *transfer,
fpi_ssm_usb_transfer_cb (transfer, device, weak_ptr, error);
}
/**
* fpi_ssm_spi_transfer_cb:
* @transfer: a #FpiSpiTransfer
* @device: a #FpDevice
* @unused_data: User data (unused)
* @error: The #GError or %NULL
*
* Can be used in as a #FpiSpiTransfer callback handler to automatically
* advance or fail a statemachine on transfer completion.
*
* Make sure to set the #FpiSsm on the transfer.
*/
void
fpi_ssm_spi_transfer_cb (FpiSpiTransfer *transfer, FpDevice *device,
gpointer unused_data, GError *error)
{
g_return_if_fail (transfer->ssm);
if (error)
fpi_ssm_mark_failed (transfer->ssm, error);
else
fpi_ssm_next_state (transfer->ssm);
}
/**
* fpi_ssm_spi_transfer_with_weak_pointer_cb:
* @transfer: a #FpiSpiTransfer
* @device: a #FpDevice
* @weak_ptr: A #gpointer pointer to nullify. You can pass a pointer to any
* #gpointer to nullify when the callback is completed. I.e a
* pointer to the current #FpiSpiTransfer.
* @error: The #GError or %NULL
*
* Can be used in as a #FpiSpiTransfer callback handler to automatically
* advance or fail a statemachine on transfer completion.
* Passing a #gpointer* as @weak_ptr permits to nullify it once we're done
* with the transfer.
*
* Make sure to set the #FpiSsm on the transfer.
*/
void
fpi_ssm_spi_transfer_with_weak_pointer_cb (FpiSpiTransfer *transfer,
FpDevice *device, gpointer weak_ptr,
GError *error)
{
g_return_if_fail (transfer->ssm);
if (weak_ptr)
g_nullify_pointer ((gpointer *) weak_ptr);
fpi_ssm_spi_transfer_cb (transfer, device, weak_ptr, error);
}

View file

@ -112,4 +112,15 @@ void fpi_ssm_usb_transfer_with_weak_pointer_cb (FpiUsbTransfer *transfer,
gpointer weak_ptr,
GError *error);
typedef struct _FpiSpiTransfer FpiSpiTransfer;
void fpi_ssm_spi_transfer_cb (FpiSpiTransfer *transfer,
FpDevice *device,
gpointer unused_data,
GError *error);
void fpi_ssm_spi_transfer_with_weak_pointer_cb (FpiSpiTransfer *transfer,
FpDevice *device,
gpointer weak_ptr,
GError *error);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpiSsm, fpi_ssm_free)

View file

@ -16,6 +16,7 @@ libfprint_private_sources = [
'fpi-print.c',
'fpi-ssm.c',
'fpi-usb-transfer.c',
'fpi-spi-transfer.c',
]
libfprint_public_headers = [
@ -40,6 +41,7 @@ libfprint_private_headers = [
'fpi-minutiae.h',
'fpi-print.h',
'fpi-usb-transfer.h',
'fpi-spi-transfer.h',
'fpi-ssm.h',
]