Linux FireWire subsystem gives a way to listen to any units on IEEE 1394 bus. This subsystem adds FireWire character devices and applications can get any events by reading the devices.
This commit adds HINAWA_TYPE_FW_UNIT object to perform this. This object is written with GObject way.
When constructing this object, applications gets file descriptor by open() with a path of FireWire character devices. When calling listen(), the object starts listening. Then the 'generation' property of this object is valid and 'bus-update' signal is generated at IEEE 1394 bus-reset event. When destructing the object, the file descriptor is released.
I note that src/hinawa_sigs_marshal.* are newly generated by glib tools to detect the signature of GObject signal handler. These files should be regenerated when new signals are added or existed signals are changes, thus don't forget to 'make clean' when applying following patches.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- libhinawa/doc/reference/hinawa-docs.sgml | 1 + libhinawa/src/Makefile.am | 27 ++- libhinawa/src/fw_unit.c | 335 +++++++++++++++++++++++++++++++ libhinawa/src/fw_unit.h | 52 +++++ libhinawa/src/internal.h | 13 ++ 5 files changed, 425 insertions(+), 3 deletions(-) create mode 100644 libhinawa/src/fw_unit.c create mode 100644 libhinawa/src/fw_unit.h create mode 100644 libhinawa/src/internal.h
diff --git a/libhinawa/doc/reference/hinawa-docs.sgml b/libhinawa/doc/reference/hinawa-docs.sgml index 43c5c4e..662d8c5 100644 --- a/libhinawa/doc/reference/hinawa-docs.sgml +++ b/libhinawa/doc/reference/hinawa-docs.sgml @@ -30,6 +30,7 @@ functionality in ALSA firewire stack, the others are abstractions of the transaction functionality in Linux firewire stack. </para> + <xi:include href="xml/fw_unit.xml"/> </chapter> </part>
diff --git a/libhinawa/src/Makefile.am b/libhinawa/src/Makefile.am index 5673255..9eda8f4 100644 --- a/libhinawa/src/Makefile.am +++ b/libhinawa/src/Makefile.am @@ -1,5 +1,7 @@ # Remove auto-generated files when cleaning -CLEANFILES = +CLEANFILES = \ + hinawa_sigs_marshal.* +
AM_CPPFLAGS = \ -I$(top_builddir) \ @@ -19,6 +21,25 @@ libhinawa_la_LIBADD = \ $(GLIB_LIBS)
libhinawa_la_SOURCES = \ - hinawa_context.c + hinawa_context.c \ + hinawa_sigs_marshal.h \ + hinawa_sigs_marshal.c \ + fw_unit.h \ + fw_unit.c + +pkginclude_HEADERS = \ + hinawa_sigs_marshal.h \ + fw_unit.h + +hinawa_sigs_marshal.list: + $(AM_V_GEN)( find | grep .c$$ | xargs cat | \ + sed -n -e 's/.*hinawa_sigs_marshal_([A-Z]*__[A-Z_]*).*/\1/p' |\ + sed -e 's/__/:/' -e 'y/_/,/' ) | sort -u > $@ + +hinawa_sigs_marshal.h: hinawa_sigs_marshal.list + $(AM_V_GEN)( glib-genmarshal --header \ + --prefix=hinawa_sigs_marshal $< ) > $@
-pkginclude_HEADERS = +hinawa_sigs_marshal.c: hinawa_sigs_marshal.list hinawa_sigs_marshal.h + $(AM_V_GEN)(echo '#include "hinawa_sigs_marshal.h"'; \ + glib-genmarshal --body --prefix=hinawa_sigs_marshal $< ) > $@ diff --git a/libhinawa/src/fw_unit.c b/libhinawa/src/fw_unit.c new file mode 100644 index 0000000..b948ef8 --- /dev/null +++ b/libhinawa/src/fw_unit.c @@ -0,0 +1,335 @@ +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "hinawa_context.h" +#include "fw_unit.h" +#include "internal.h" + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +/** + * SECTION:fw_unit + * @Title: HinawaFwUnit + * @Short_description: An event listener for FireWire unit + * + * A #HinawaFwUnit is an event listener for a certain FireWire unit. + * This class is an application of Linux FireWire subsystem. + * All of operations utilize ioctl(2) with subsystem specific request commands. + */ +typedef struct { + GSource src; + HinawaFwUnit *unit; + gpointer tag; +} FwUnitSource; + +struct _HinawaFwUnitPrivate { + int fd; + guint64 generation; + + unsigned int len; + void *buf; + FwUnitSource *src; +}; +G_DEFINE_TYPE_WITH_PRIVATE(HinawaFwUnit, hinawa_fw_unit, G_TYPE_OBJECT) +#define FW_UNIT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), \ + HINAWA_TYPE_FW_UNIT, HinawaFwUnitPrivate)) + +/* This object has properties. */ +enum fw_unit_prop_type { + FW_UNIT_PROP_TYPE_GENERATION = 1, + FW_UNIT_PROP_TYPE_COUNT, +}; +static GParamSpec *fw_unit_props[FW_UNIT_PROP_TYPE_COUNT] = { NULL, }; + +/* This object has one signal. */ +enum fw_unit_sig_type { + FW_UNIT_SIG_TYPE_BUS_UPDATE = 0, + FW_UNIT_SIG_TYPE_COUNT, +}; +static guint fw_unit_sigs[FW_UNIT_SIG_TYPE_COUNT] = { 0 }; + +static void fw_unit_get_property(GObject *obj, guint id, + GValue *val, GParamSpec *spec) +{ + HinawaFwUnit *self = HINAWA_FW_UNIT(obj); + HinawaFwUnitPrivate *priv = FW_UNIT_GET_PRIVATE(self); + + switch (id) { + case FW_UNIT_PROP_TYPE_GENERATION: + g_value_set_uint64(val, priv->generation); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, spec); + break; + } +} + +static void fw_unit_set_property(GObject *obj, guint id, + const GValue *val, GParamSpec *spec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, spec); +} + +static void fw_unit_dispose(GObject *obj) +{ + HinawaFwUnit *self = HINAWA_FW_UNIT(obj); + HinawaFwUnitPrivate *priv = FW_UNIT_GET_PRIVATE(self); + + hinawa_fw_unit_unlisten(self); + + close(priv->fd); + + G_OBJECT_CLASS(hinawa_fw_unit_parent_class)->dispose(obj); +} + +static void fw_unit_finalize(GObject *gobject) +{ + G_OBJECT_CLASS(hinawa_fw_unit_parent_class)->finalize(gobject); +} + +static void hinawa_fw_unit_class_init(HinawaFwUnitClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->get_property = fw_unit_get_property; + gobject_class->set_property = fw_unit_set_property; + gobject_class->dispose = fw_unit_dispose; + gobject_class->finalize = fw_unit_finalize; + + fw_unit_props[FW_UNIT_PROP_TYPE_GENERATION] = + g_param_spec_uint64("generation", "generation", + "current level of generation on this bus.", + 0, ULONG_MAX, 0, + G_PARAM_READABLE); + + g_object_class_install_properties(gobject_class, + FW_UNIT_PROP_TYPE_COUNT, + fw_unit_props); + + /** + * HinawaFwUnit::bus-update: + * @self: A #HinawaFwUnit + * + * When IEEE 1394 bus is updated, the ::bus-update signal is generated. + * Handlers can read current generation in the bus via 'generation' + * property. + */ + fw_unit_sigs[FW_UNIT_SIG_TYPE_BUS_UPDATE] = + g_signal_new("bus-update", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0, G_TYPE_NONE); +} + +static void hinawa_fw_unit_init(HinawaFwUnit *self) +{ + self->priv = hinawa_fw_unit_get_instance_private(self); +} + +/** + * hinawa_fw_unit_open: + * @self: A #HinawaFwUnit + * @path: A path to Linux FireWire character device + * @exception: A #GError + */ +void hinawa_fw_unit_open(HinawaFwUnit *self, gchar *path, GError **exception) +{ + HinawaFwUnitPrivate *priv; + int fd; + struct fw_cdev_get_info info = {0}; + struct fw_cdev_event_bus_reset br = {0}; + + g_return_if_fail(HINAWA_IS_FW_UNIT(self)); + priv = FW_UNIT_GET_PRIVATE(self); + + fd = open(path, O_RDONLY); + if (fd < 0) { + g_set_error(exception, g_quark_from_static_string(__func__), + errno, "%s", strerror(errno)); + return; + } + + info.version = 4; + info.bus_reset = (guint64)&br; + info.bus_reset_closure = (guint64)self; + if (ioctl(fd, FW_CDEV_IOC_GET_INFO, &info) < 0) { + g_set_error(exception, g_quark_from_static_string(__func__), + errno, "%s", strerror(errno)); + close(fd); + return; + } + + priv->fd = fd; + priv->generation = br.generation; +} + +/* Internal use only. */ +void hinawa_fw_unit_ioctl(HinawaFwUnit *self, int req, void *args, int *err) +{ + HinawaFwUnitPrivate *priv; + + g_return_if_fail(HINAWA_IS_FW_UNIT(self)); + priv = FW_UNIT_GET_PRIVATE(self); + + *err = 0; + if (ioctl(priv->fd, req, args) < 0) + *err = errno; +} + +static void handle_update(HinawaFwUnit *self, + struct fw_cdev_event_bus_reset *event) +{ + HinawaFwUnitPrivate *priv; + + g_return_if_fail(HINAWA_IS_FW_UNIT(self)); + priv = FW_UNIT_GET_PRIVATE(self); + + priv->generation = event->generation; + + g_signal_emit(self, fw_unit_sigs[FW_UNIT_SIG_TYPE_BUS_UPDATE], 0, + NULL); +} + +static gboolean prepare_src(GSource *src, gint *timeout) +{ + /* Set 2msec for poll(2) timeout. */ + *timeout = 2; + + /* This source is not ready, let's poll(2) */ + return FALSE; +} + +static gboolean check_src(GSource *gsrc) +{ + FwUnitSource *src = (FwUnitSource *)gsrc; + HinawaFwUnit *unit = src->unit; + HinawaFwUnitPrivate *priv = FW_UNIT_GET_PRIVATE(unit); + struct fw_cdev_event_common *common; + int len; + GIOCondition condition; + + if (unit == NULL) + goto end; + + /* Let's process this source if any inputs are available. */ + condition = g_source_query_unix_fd((GSource *)src, src->tag); + if (!(condition & G_IO_IN)) + goto end; + + len = read(priv->fd, priv->buf, priv->len); + if (len <= 0) + goto end; + + common = (struct fw_cdev_event_common *)priv->buf; + + if (HINAWA_IS_FW_UNIT(common->closure) && + common->type == FW_CDEV_EVENT_BUS_RESET) + handle_update(HINAWA_FW_UNIT(common->closure), + (struct fw_cdev_event_bus_reset *)common); +end: + /* Don't go to dispatch, then continue to process this source. */ + return FALSE; +} + +static gboolean dispatch_src(GSource *src, GSourceFunc callback, + gpointer user_data) +{ + /* Just be sure to continue to process this source. */ + return TRUE; +} + +/** + * hinawa_fw_unit_listen: + * @self: A #HinawaFwUnit + * @exception: A #GError + * + * Start to listen to any events from the unit. + */ +void hinawa_fw_unit_listen(HinawaFwUnit *self, GError **exception) +{ + static GSourceFuncs funcs = { + .prepare = prepare_src, + .check = check_src, + .dispatch = dispatch_src, + .finalize = NULL, + }; + HinawaFwUnitPrivate *priv; + void *buf; + GSource *src; + + g_return_if_fail(HINAWA_IS_FW_UNIT(self)); + priv = FW_UNIT_GET_PRIVATE(self); + + /* + * MEMO: allocate one page because we cannot assume the size of + * transaction frame. + */ + buf = g_malloc0(getpagesize()); + if (buf == NULL) { + g_set_error(exception, g_quark_from_static_string(__func__), + ENOMEM, "%s", strerror(ENOMEM)); + return; + } + + src = g_source_new(&funcs, sizeof(FwUnitSource)); + if (src == NULL) { + g_set_error(exception, g_quark_from_static_string(__func__), + ENOMEM, "%s", strerror(ENOMEM)); + g_free(buf); + return; + } + + g_source_set_name(src, "HinawaFwUnit"); + g_source_set_priority(src, G_PRIORITY_HIGH_IDLE); + g_source_set_can_recurse(src, TRUE); + + ((FwUnitSource *)src)->unit = self; + priv->src = (FwUnitSource *)src; + priv->buf = buf; + priv->len = getpagesize(); + + ((FwUnitSource *)src)->tag = + hinawa_context_add_src(src, priv->fd, G_IO_IN, exception); + if (*exception != NULL) { + g_free(buf); + g_source_destroy(src); + priv->buf = NULL; + priv->len = 0; + priv->src = NULL; + return; + } +} + +/** + * hinawa_fw_unit_unlisten: + * @self: A #HinawaFwUnit + * + * Stop to listen to any events from the unit. + */ +void hinawa_fw_unit_unlisten(HinawaFwUnit *self) +{ + HinawaFwUnitPrivate *priv; + + g_return_if_fail(HINAWA_IS_FW_UNIT(self)); + priv = FW_UNIT_GET_PRIVATE(self); + + if (priv->src == NULL) + return; + + g_source_destroy((GSource *)priv->src); + g_free(priv->src); + priv->src = NULL; + + g_free(priv->buf); + priv->buf = NULL; + priv->len = 0; +} diff --git a/libhinawa/src/fw_unit.h b/libhinawa/src/fw_unit.h new file mode 100644 index 0000000..f8f38a4 --- /dev/null +++ b/libhinawa/src/fw_unit.h @@ -0,0 +1,52 @@ +#ifndef __ALSA_TOOLS_HINAWA_FW_UNIT_H__ +#define __ALSA_TOOLS_HINAWA_FW_UNIT_H__ + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define HINAWA_TYPE_FW_UNIT (hinawa_fw_unit_get_type()) + +#define HINAWA_FW_UNIT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + HINAWA_TYPE_FW_UNIT, \ + HinawaFwUnit)) +#define HINAWA_IS_FW_UNIT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + HINAWA_TYPE_FW_UNIT)) + +#define HINAWA_FW_UNIT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), \ + HINAWA_TYPE_FW_UNIT, \ + HinawaFwUnitClass)) +#define HINAWA_IS_FW_UNIT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), \ + HINAWA_TYPE_FW_UNIT)) +#define HINAWA_FW_UNIT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), \ + HINAWA_TYPE_FW_UNIT, \ + HinawaFwUnitClass)) + +typedef struct _HinawaFwUnit HinawaFwUnit; +typedef struct _HinawaFwUnitClass HinawaFwUnitClass; +typedef struct _HinawaFwUnitPrivate HinawaFwUnitPrivate; + +struct _HinawaFwUnit { + GObject parent_instance; + + HinawaFwUnitPrivate *priv; +}; + +struct _HinawaFwUnitClass { + GObjectClass parent_class; +}; + +GType hinawa_fw_unit_get_type(void) G_GNUC_CONST; + +void hinawa_fw_unit_open(HinawaFwUnit *self, gchar *path, GError **exception); + +void hinawa_fw_unit_listen(HinawaFwUnit *self, GError **exception); +void hinawa_fw_unit_unlisten(HinawaFwUnit *self); + +#endif diff --git a/libhinawa/src/internal.h b/libhinawa/src/internal.h new file mode 100644 index 0000000..83f920b --- /dev/null +++ b/libhinawa/src/internal.h @@ -0,0 +1,13 @@ +#ifndef __ALSA_TOOLS_HINAWA_INTERNAL_H__ +#define __ALSA_TOOLS_HINAWA_INTERNAL_H__ + +#include <errno.h> +#include <string.h> + +#include <linux/firewire-cdev.h> +#include <linux/firewire-constants.h> + +#include "fw_unit.h" + +void hinawa_fw_unit_ioctl(HinawaFwUnit *self, int req, void *args, int *err); +#endif