blob: 32d94a1ac7131abf50dff57c73d6ceecdbe6b5ec [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "internal.h"
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
/**
* HinawaFwResp:
* A transaction responder for request subaction initiated by node in IEEE 1394 bus.
*
* The [class@FwResp] responds to request subaction initiated by node in IEEE 1394 bus.
*
* This class is an application of Linux FireWire subsystem. Some operations utilize ioctl(2)
* with subsystem specific request commands.
*/
/**
* hinawa_fw_resp_error_quark:
*
* Return the [alias@GLib.Quark] for error domain of [struct@GLib.Error] which has code in
* Hinawa.FwRespError.
*
* Since: 2.2
*
* Returns: A [alias@GLib.Quark].
*/
G_DEFINE_QUARK(hinawa-fw-resp-error-quark, hinawa_fw_resp_error)
static const char *const err_msgs[] = {
[HINAWA_FW_RESP_ERROR_RESERVED] = "Reservation of address space is already done",
[HINAWA_FW_RESP_ERROR_ADDR_SPACE_USED] = "The requested range of address is already used exclusively",
};
#define generate_local_error(error, code) \
g_set_error_literal(error, HINAWA_FW_RESP_ERROR, code, err_msgs[code])
#define generate_syscall_error(error, errno, format, arg) \
g_set_error(error, HINAWA_FW_RESP_ERROR, HINAWA_FW_RESP_ERROR_FAILED, \
format " %d(%s)", arg, errno, strerror(errno))
typedef struct {
HinawaFwNode *node;
guint64 offset;
guint width;
guint64 addr_handle;
guint8 *req_frame;
gsize req_length;
guint8 *resp_frame;
gsize resp_length;
} HinawaFwRespPrivate;
G_DEFINE_TYPE_WITH_PRIVATE(HinawaFwResp, hinawa_fw_resp, G_TYPE_OBJECT)
// This object has one property.
enum fw_resp_prop_type {
FW_RESP_PROP_TYPE_IS_RESERVED = 1,
FW_RESP_PROP_TYPE_OFFSET,
FW_RESP_PROP_TYPE_WIDTH,
FW_RESP_PROP_TYPE_COUNT,
};
static GParamSpec *fw_resp_props[FW_RESP_PROP_TYPE_COUNT] = { NULL, };
// This object has one signal.
enum fw_resp_sig_type {
FW_RESP_SIG_TYPE_REQ = 0,
FW_RESP_SIG_TYPE_COUNT,
};
static guint fw_resp_sigs[FW_RESP_SIG_TYPE_COUNT] = { 0 };
static void fw_resp_get_property(GObject *obj, guint id, GValue *val,
GParamSpec *spec)
{
HinawaFwResp *self = HINAWA_FW_RESP(obj);
HinawaFwRespPrivate *priv = hinawa_fw_resp_get_instance_private(self);
switch (id) {
case FW_RESP_PROP_TYPE_IS_RESERVED:
g_value_set_boolean(val, priv->node != NULL);
break;
case FW_RESP_PROP_TYPE_OFFSET:
g_value_set_uint64(val, priv->offset);
break;
case FW_RESP_PROP_TYPE_WIDTH:
g_value_set_uint(val, priv->width);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, spec);
break;
}
}
static void fw_resp_finalize(GObject *obj)
{
HinawaFwResp *self = HINAWA_FW_RESP(obj);
hinawa_fw_resp_release(self);
G_OBJECT_CLASS(hinawa_fw_resp_parent_class)->finalize(obj);
}
static void hinawa_fw_resp_class_init(HinawaFwRespClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
gobject_class->get_property = fw_resp_get_property;
gobject_class->finalize = fw_resp_finalize;
/**
* HinawaFwResp:is-reserved:
*
* Whether a range of address is reserved or not.
*
* Since: 2.0
*/
fw_resp_props[FW_RESP_PROP_TYPE_IS_RESERVED] =
g_param_spec_boolean("is-reserved", "is-reserved",
"Whether a range of address is reserved or not.",
FALSE,
G_PARAM_READABLE);
/**
* HinawaFwResp:offset:
*
* The start offset of reserved address range.
*
* Since: 2.3
*/
fw_resp_props[FW_RESP_PROP_TYPE_OFFSET] =
g_param_spec_uint64("offset", "offset",
"The start offset of reserved address range.",
0, G_MAXUINT64,
0,
G_PARAM_READABLE);
/**
* HinawaFwResp:width:
*
* The width of reserved address range.
*
* Since: 2.3
*/
fw_resp_props[FW_RESP_PROP_TYPE_WIDTH] =
g_param_spec_uint("width", "width",
"The width of reserved address range.",
0, G_MAXUINT,
0,
G_PARAM_READABLE);
g_object_class_install_properties(gobject_class,
FW_RESP_PROP_TYPE_COUNT,
fw_resp_props);
/**
* HinawaFwResp::requested:
* @self: A [class@FwResp]
* @tcode: One of [enum@FwTcode] enumerations
* @offset: The address offset at which the transaction arrives.
* @src_node_id: The node ID of source for the transaction.
* @dst_node_id: The node ID of destination for the transaction.
* @card_id: The index of card specific to the 1394 OHCI hardware at which the request
* subaction arrived.
* @generation: The generation of bus when the transaction is transferred.
* @tstamp: The isochronous cycle at which the request arrived.
* @frame: (element-type guint8)(array length=length): The array with elements for byte
* data.
* @length: The length of bytes for the frame.
*
* Emitted when any node transfers request subaction to local nodes within the address
* range reserved in Linux system.
*
* The handler is expected to call [method@FwResp.set_resp_frame] with frame and return
* [enum@FwRcode] for response subaction.
*
* The value of @tstamp is unsigned 16 bit integer including higher 3 bits for three low
* order bits of second field and the rest 13 bits for cycle field in the format of IEEE
* 1394 CYCLE_TIMER register.
*
* If the version of kernel ABI for Linux FireWire subsystem is less than 6, the value of
* tstamp argument has invalid value (=G_MAXUINT). Furthermore, if the version is less than
* 4, the src, dst, card, generation arguments have invalid value (=G_MAXUINT).
*
* Returns: One of [enum@FwRcode] enumerations corresponding to rcodes defined in IEEE 1394
* specification.
* Since: 3.0
*/
fw_resp_sigs[FW_RESP_SIG_TYPE_REQ] =
g_signal_new("requested",
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(HinawaFwRespClass, requested),
NULL, NULL,
hinawa_sigs_marshal_ENUM__ENUM_UINT64_UINT_UINT_UINT_UINT_UINT_POINTER_UINT,
HINAWA_TYPE_FW_RCODE, 9, HINAWA_TYPE_FW_TCODE, G_TYPE_UINT64,
G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT,
G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
}
static void hinawa_fw_resp_init(HinawaFwResp *self)
{
return;
}
/**
* hinawa_fw_resp_new:
*
* Instantiate [class@FwResp] object and return the instance.
*
* Returns: a new instance of [class@FwResp].
* Since: 1.3
*/
HinawaFwResp *hinawa_fw_resp_new(void)
{
return g_object_new(HINAWA_TYPE_FW_RESP, NULL);
}
/**
* hinawa_fw_resp_reserve_within_region:
* @self: A [class@FwResp].
* @node: A [class@FwNode].
* @region_start: Start offset of address region in which range of address is looked up.
* @region_end: End offset of address region in which range of address is looked up.
* @width: The width for range of address to be looked up.
* @error: A [struct@GLib.Error]. Error can be generated with two domain of Hinawa.FwNodeError and
* Hinawa.FwRespError.
*
* Allocate an address range within Linux system for local nodes, each of which represents 1394
* OHCI hardware. Once successful, [signal@FwResp::requested] signal will be emitted whenever any
* request subactions arrive at the 1394 OHCI hardware within the dedicated range.
*
* The range is reserved between the values specified by @region_start and @region_end with the size
* indicated by @width, The starting offset may vary every time.
*
* Returns: TRUE if the overall operation finishes successfully, otherwise FALSE.
*
* Since: 3.0
*/
gboolean hinawa_fw_resp_reserve_within_region(HinawaFwResp *self, HinawaFwNode *node,
guint64 region_start, guint64 region_end,
guint width, GError **error)
{
HinawaFwRespPrivate *priv;
struct fw_cdev_allocate allocate = {0};
int err;
g_return_val_if_fail(HINAWA_IS_FW_RESP(self), FALSE);
g_return_val_if_fail(width > 0, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
priv = hinawa_fw_resp_get_instance_private(self);
if (priv->node != NULL) {
generate_local_error(error, HINAWA_FW_RESP_ERROR_RESERVED);
return FALSE;
}
allocate.offset = region_start;
allocate.closure = (guint64)self;
allocate.length = width;
allocate.region_end = region_end;
err = hinawa_fw_node_ioctl(node, FW_CDEV_IOC_ALLOCATE, &allocate, error);
if (*error != NULL)
return FALSE;
if (err > 0) {
if (err == EBUSY)
generate_local_error(error, HINAWA_FW_RESP_ERROR_ADDR_SPACE_USED);
else
generate_syscall_error(error, err, "ioctl(%s)", "FW_CDEV_IOC_ALLOCATE");
return FALSE;
}
priv->node = g_object_ref(node);
priv->req_frame = g_malloc(allocate.length);
priv->resp_frame = g_malloc0(allocate.length);
priv->offset = allocate.offset;
priv->width = allocate.length;
priv->addr_handle = allocate.handle;
return TRUE;
}
/**
* hinawa_fw_resp_reserve:
* @self: A [class@FwResp].
* @node: A [class@FwNode].
* @addr: A start address to listen to in 1394 OHCI hardware.
* @width: The byte width of address to listen to 1394 OHCI hardware.
* @error: A [struct@GLib.Error]. Error can be generated with two domain of Hinawa.FwNodeError and
* and Hinawa.FwRespError.
*
* Allocate an address range within Linux system for local nodes, each of which represents 1394
* OHCI hardware. Once successful, [signal@FwResp::requested] signal will be emitted whenever any
* request subactions arrive at the 1394 OHCI hardware within the dedicated range.
*
* The range is precisely reserved at the address specified by @addr with the size indicated by
* @width. In essence, this function is a variant of [method@FwResp.reserve_within_region] in
* which the specified address range is reserved as provided.
*
* Returns: TRUE if the overall operation finishes successfully, otherwise FALSE.
*
* Since: 3.0
*/
gboolean hinawa_fw_resp_reserve(HinawaFwResp *self, HinawaFwNode *node, guint64 addr, guint width,
GError **error)
{
return hinawa_fw_resp_reserve_within_region(self, node, addr, addr + width, width, error);
}
/**
* hinawa_fw_resp_release:
* @self: A [class@FwResp].
*
* Stop listening to the address range in Linu system for local nodes.
*
* Since: 1.4
*/
void hinawa_fw_resp_release(HinawaFwResp *self)
{
HinawaFwRespPrivate *priv;
struct fw_cdev_deallocate deallocate = {0};
GError *error = NULL;
g_return_if_fail(HINAWA_IS_FW_RESP(self));
priv = hinawa_fw_resp_get_instance_private(self);
if (priv->node == NULL)
return;
// Ignore ioctl error.
deallocate.handle = priv->addr_handle;
hinawa_fw_node_ioctl(priv->node, FW_CDEV_IOC_DEALLOCATE, &deallocate, &error);
g_clear_error(&error);
g_object_unref(priv->node);
priv->node = NULL;
if (priv->req_frame != NULL)
g_free(priv->req_frame);
priv->req_frame = NULL;
if (priv->resp_frame)
g_free(priv->resp_frame);
priv->resp_frame = NULL;
priv->resp_length = 0;
priv->offset = 0;
priv->width = 0;
}
/**
* hinawa_fw_resp_set_resp_frame:
* @self: A [class@FwResp]
* @frame: (element-type guint8)(array length=length): a 8bit array for response frame.
* @length: The length of bytes for the frame.
*
* Register byte frame for the response subaction.
*
* Since: 2.0
*/
void hinawa_fw_resp_set_resp_frame(HinawaFwResp *self, guint8 *frame,
gsize length)
{
HinawaFwRespPrivate *priv;
g_return_if_fail(HINAWA_IS_FW_RESP(self));
g_return_if_fail(frame != NULL);
g_return_if_fail(length > 0);
priv = hinawa_fw_resp_get_instance_private(self);
if (frame && length <= priv->width) {
memcpy(priv->resp_frame, frame, length);
priv->resp_length = length;
}
}
// NOTE: For HinawaFwNode, internal.
void hinawa_fw_resp_handle_request(HinawaFwResp *self, const struct fw_cdev_event_request *event)
{
HinawaFwRespPrivate *priv;
struct fw_cdev_send_response resp = {0};
HinawaFwRcode rcode;
GError *error = NULL;
g_return_if_fail(HINAWA_IS_FW_RESP(self));
priv = hinawa_fw_resp_get_instance_private(self);
memset(priv->resp_frame, 0, priv->width);
priv->resp_length = 0;
if (!priv->node || event->length > priv->width) {
rcode = RCODE_CONFLICT_ERROR;
} else {
g_signal_emit(self, fw_resp_sigs[FW_RESP_SIG_TYPE_REQ], 0, event->tcode,
event->offset, G_MAXUINT, G_MAXUINT, G_MAXUINT, G_MAXUINT,
G_MAXUINT, event->data, event->length, &rcode);
}
if (priv->resp_length > 0) {
resp.length = priv->resp_length;
resp.data = (guint64)priv->resp_frame;
}
// Ignore ioctl error.
resp.rcode = (__u32)rcode;
resp.handle = event->handle;
hinawa_fw_node_ioctl(priv->node, FW_CDEV_IOC_SEND_RESPONSE, &resp, &error);
g_clear_error(&error);
}
// NOTE: For HinawaFwNode, internal.
void hinawa_fw_resp_handle_request2(HinawaFwResp *self, const struct fw_cdev_event_request2 *event)
{
HinawaFwRespPrivate *priv;
struct fw_cdev_send_response resp = {0};
HinawaFwRcode rcode;
GError *error = NULL;
g_return_if_fail(HINAWA_IS_FW_RESP(self));
priv = hinawa_fw_resp_get_instance_private(self);
memset(priv->resp_frame, 0, priv->width);
priv->resp_length = 0;
if (!priv->node || event->length > priv->width) {
rcode = RCODE_CONFLICT_ERROR;
} else {
rcode = HINAWA_FW_RCODE_ADDRESS_ERROR;
g_signal_emit(self, fw_resp_sigs[FW_RESP_SIG_TYPE_REQ], 0, event->tcode,
event->offset, event->source_node_id, event->destination_node_id,
event->card, event->generation, G_MAXUINT, event->data, event->length,
&rcode);
}
if (priv->resp_length > 0) {
resp.length = priv->resp_length;
resp.data = (guint64)priv->resp_frame;
}
// Ignore ioctl error.
resp.rcode = (__u32)rcode;
resp.handle = event->handle;
hinawa_fw_node_ioctl(priv->node, FW_CDEV_IOC_SEND_RESPONSE, &resp, &error);
g_clear_error(&error);
}
// NOTE: For HinawaFwNode, internal.
void hinawa_fw_resp_handle_request3(HinawaFwResp *self, const struct fw_cdev_event_request3 *event)
{
HinawaFwRespPrivate *priv;
struct fw_cdev_send_response resp = {0};
HinawaFwRcode rcode;
GError *error = NULL;
g_return_if_fail(HINAWA_IS_FW_RESP(self));
priv = hinawa_fw_resp_get_instance_private(self);
memset(priv->resp_frame, 0, priv->width);
priv->resp_length = 0;
if (!priv->node || event->length > priv->width) {
rcode = RCODE_CONFLICT_ERROR;
} else {
rcode = HINAWA_FW_RCODE_ADDRESS_ERROR;
g_signal_emit(self, fw_resp_sigs[FW_RESP_SIG_TYPE_REQ], 0, event->tcode,
event->offset, event->source_node_id, event->destination_node_id,
event->card, event->generation, event->tstamp, event->data,
event->length, &rcode);
}
if (priv->resp_length > 0) {
resp.length = priv->resp_length;
resp.data = (guint64)priv->resp_frame;
}
// Ignore ioctl error.
resp.rcode = (__u32)rcode;
resp.handle = event->handle;
hinawa_fw_node_ioctl(priv->node, FW_CDEV_IOC_SEND_RESPONSE, &resp, &error);
g_clear_error(&error);
}