diff --git a/libhinawa/doc/reference/hinawa-docs.sgml b/libhinawa/doc/reference/hinawa-docs.sgml
index 58c862a..731ae15 100644
--- a/libhinawa/doc/reference/hinawa-docs.sgml
+++ b/libhinawa/doc/reference/hinawa-docs.sgml
@@ -36,6 +36,7 @@
             <xi:include href="xml/fw_fcp.xml"/>
             <xi:include href="xml/snd_unit.xml"/>
             <xi:include href="xml/snd_dice.xml"/>
+            <xi:include href="xml/snd_efw.xml"/>
         </chapter>
     </part>
 
diff --git a/libhinawa/src/Makefile.am b/libhinawa/src/Makefile.am
index 696e567..b746bf1 100644
--- a/libhinawa/src/Makefile.am
+++ b/libhinawa/src/Makefile.am
@@ -36,7 +36,9 @@ libhinawa_la_SOURCES =				\
 	snd_unit.h				\
 	snd_unit.c				\
 	snd_dice.h				\
-	snd_dice.c
+	snd_dice.c				\
+	snd_efw.h				\
+	snd_efw.c
 
 pkginclude_HEADERS =				\
 	hinawa_sigs_marshal.h			\
@@ -45,7 +47,8 @@ pkginclude_HEADERS =				\
 	fw_req.h				\
 	fw_fcp.h				\
 	snd_unit.h				\
-	snd_dice.h
+	snd_dice.h				\
+	snd_efw.h
 
 hinawa_sigs_marshal.list:
 	$(AM_V_GEN)( find | grep \.c$$ | xargs cat | 			\
diff --git a/libhinawa/src/internal.h b/libhinawa/src/internal.h
index ddbb298..9ff8fe4 100644
--- a/libhinawa/src/internal.h
+++ b/libhinawa/src/internal.h
@@ -13,6 +13,7 @@
 #include "fw_req.h"
 #include "snd_unit.h"
 #include "snd_dice.h"
+#include "snd_efw.h"
 
 void hinawa_fw_unit_ioctl(HinawaFwUnit *self, int req, void *args, int *err);
 void hinawa_fw_resp_handle_request(HinawaFwResp *self,
@@ -25,4 +26,6 @@ void hinawa_snd_unit_write(HinawaSndUnit *unit,
 			   GError **exception);
 void hinawa_snd_dice_handle_notification(HinawaSndDice *self,
 					 const void *buf, unsigned int len);
+void hinawa_snd_efw_handle_response(HinawaSndEfw *self,
+				    const void *buf, unsigned int len);
 #endif
diff --git a/libhinawa/src/snd_efw.c b/libhinawa/src/snd_efw.c
new file mode 100644
index 0000000..6e1882b
--- /dev/null
+++ b/libhinawa/src/snd_efw.c
@@ -0,0 +1,315 @@
+#include <linux/types.h>
+#include <sound/firewire.h>
+#include <alsa/asoundlib.h>
+#include "snd_efw.h"
+#include "internal.h"
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+/**
+ * SECTION:snd_efw
+ * @Title: HinawaSndEfw
+ * @Short_description: A transaction executor for Fireworks models
+ *
+ * A #HinawaSndEfw is an application of Echo Fireworks Transaction (EFT).
+ * This inherits #HinawaSndUnit.
+ */
+#define MINIMUM_SUPPORTED_VERSION	1
+#define MAXIMUM_FRAME_BYTES		0x200U
+
+enum efw_status {
+	EFT_STATUS_OK			= 0,
+	EFT_STATUS_BAD			= 1,
+	EFT_STATUS_BAD_COMMAND		= 2,
+	EFT_STATUS_COMM_ERR		= 3,
+	EFT_STATUS_BAD_QUAD_COUNT	= 4,
+	EFT_STATUS_UNSUPPORTED		= 5,
+	EFT_STATUS_1394_TIMEOUT		= 6,
+	EFT_STATUS_DSP_TIMEOUT		= 7,
+	EFT_STATUS_BAD_RATE		= 8,
+	EFT_STATUS_BAD_CLOCK		= 9,
+	EFT_STATUS_BAD_CHANNEL		= 10,
+	EFT_STATUS_BAD_PAN		= 11,
+	EFT_STATUS_FLASH_BUSY		= 12,
+	EFT_STATUS_BAD_MIRROR		= 13,
+	EFT_STATUS_BAD_LED		= 14,
+	EFT_STATUS_BAD_PARAMETER	= 15,
+};
+static const char *const efw_status_names[] = {
+	[EFT_STATUS_OK]			= "OK",
+	[EFT_STATUS_BAD]		= "bad",
+	[EFT_STATUS_BAD_COMMAND]	= "bad command",
+	[EFT_STATUS_COMM_ERR]		= "comm err",
+	[EFT_STATUS_BAD_QUAD_COUNT]	= "bad quad count",
+	[EFT_STATUS_UNSUPPORTED]	= "unsupported",
+	[EFT_STATUS_1394_TIMEOUT]	= "1394 timeout",
+	[EFT_STATUS_DSP_TIMEOUT]	= "DSP timeout",
+	[EFT_STATUS_BAD_RATE]		= "bad rate",
+	[EFT_STATUS_BAD_CLOCK]		= "bad clock",
+	[EFT_STATUS_BAD_CHANNEL]	= "bad channel",
+	[EFT_STATUS_BAD_PAN]		= "bad pan",
+	[EFT_STATUS_FLASH_BUSY]		= "flash busy",
+	[EFT_STATUS_BAD_MIRROR]		= "bad mirror",
+	[EFT_STATUS_BAD_LED]		= "bad LED",
+	[EFT_STATUS_BAD_PARAMETER]	= "bad parameter",
+};
+
+struct efw_transaction {
+	guint seqnum;
+
+	struct snd_efw_transaction *frame;
+
+	GCond cond;
+};
+
+struct _HinawaSndEfwPrivate {
+	guint seqnum;
+
+	GList *transactions;
+	GMutex lock;
+};
+G_DEFINE_TYPE_WITH_PRIVATE(HinawaSndEfw, hinawa_snd_efw, HINAWA_TYPE_SND_UNIT)
+#define SND_EFW_GET_PRIVATE(obj)					\
+	(G_TYPE_INSTANCE_GET_PRIVATE((obj),				\
+				     HINAWA_TYPE_SND_EFW, HinawaSndEfwPrivate))
+
+static void snd_efw_dispose(GObject *obj)
+{
+	G_OBJECT_CLASS(hinawa_snd_efw_parent_class)->dispose(obj);
+}
+
+static void snd_efw_finalize(GObject *gobject)
+{
+	G_OBJECT_CLASS(hinawa_snd_efw_parent_class)->finalize(gobject);
+}
+
+static void hinawa_snd_efw_class_init(HinawaSndEfwClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+	gobject_class->dispose = snd_efw_dispose;
+	gobject_class->finalize = snd_efw_finalize;
+}
+
+static void hinawa_snd_efw_init(HinawaSndEfw *self)
+{
+	self->priv = hinawa_snd_efw_get_instance_private(self);
+}
+
+/**
+ * hinawa_snd_efw_new:
+ * @path: A path to ALSA hwdep device for Fireworks models (i.e. hw:0)
+ * @exception: A #GError
+ *
+ * Returns: A #HinawaSndEfw
+ */
+void hinawa_snd_efw_open(HinawaSndEfw *self, gchar *path, GError **exception)
+{
+	HinawaSndEfwPrivate *priv;
+	int type;
+
+	g_return_if_fail(HINAWA_IS_SND_EFW(self));
+	priv = SND_EFW_GET_PRIVATE(self);
+
+	hinawa_snd_unit_open(&self->parent_instance, path, exception);
+	if (*exception != NULL) {
+		g_clear_object(&self);
+		return;
+	}
+
+	g_object_get(G_OBJECT(self), "type", &type, NULL);
+	if (type != SNDRV_FIREWIRE_TYPE_FIREWORKS) {
+		g_set_error(exception, g_quark_from_static_string(__func__),
+			    EINVAL, "%s", strerror(EINVAL));
+		g_clear_object(&self);
+		return;
+	}
+
+	priv = SND_EFW_GET_PRIVATE(self);
+	priv->seqnum = 0;
+	priv->transactions = NULL;
+	g_mutex_init(&priv->lock);
+}
+
+/**
+ * hinawa_snd_efw_transact:
+ * @self: A #HinawaSndEfw
+ * @category: one of category for the transact
+ * @command: one of commands for the transact
+ * @args: (nullable) (element-type guint32) (array) (in): arguments for the
+ *        transaction
+ * @params: (element-type guint32) (array) (out caller-allocates): return params
+ * @exception: A #GError
+ */
+void hinawa_snd_efw_transact(HinawaSndEfw *self, guint category, guint command,
+			     GArray *args, GArray *params, GError **exception)
+{
+	HinawaSndEfwPrivate *priv;
+	int type;
+
+	struct efw_transaction trans;
+	__le32 *items;
+
+	unsigned int quads;
+	unsigned int count;
+	unsigned int i;
+
+	gint64 expiration;
+
+	g_return_if_fail(HINAWA_IS_SND_EFW(self));
+	priv = SND_EFW_GET_PRIVATE(self);
+
+	/* Check unit type and function arguments . */
+	g_object_get(G_OBJECT(self), "type", &type, NULL);
+	if ((type != SNDRV_FIREWIRE_TYPE_FIREWORKS) ||
+	    (args && g_array_get_element_size(args) != sizeof(guint32)) ||
+	    (params && g_array_get_element_size(params) != sizeof(guint32))) {
+		g_set_error(exception, g_quark_from_static_string(__func__),
+			    EINVAL, "%s", strerror(EINVAL));
+		return;
+	}
+
+	trans.frame = g_malloc0(MAXIMUM_FRAME_BYTES);
+	if (trans.frame == NULL) {
+		g_set_error(exception, g_quark_from_static_string(__func__),
+			    ENOMEM, "%s", strerror(ENOMEM));
+		return;
+	}
+
+	/* Increment the sequence number for next transaction. */
+	trans.frame->seqnum = priv->seqnum;
+	priv->seqnum += 2;
+	if (priv->seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX)
+		priv->seqnum = 0;
+
+	/* Fill transaction frame. */
+	quads = sizeof(struct snd_efw_transaction) / 4;
+	if (args)
+		quads += args->len;
+	trans.frame->length	= quads;
+	trans.frame->version	= MINIMUM_SUPPORTED_VERSION;
+	trans.frame->category	= category;
+	trans.frame->command	= command;
+	trans.frame->status	= 0xff;
+	if (args)
+		memcpy(trans.frame->params,
+		       args->data, args->len * sizeof(guint32));
+
+	/* The transactions are aligned to big-endian. */
+	items = (__le32 *)trans.frame;
+	for (i = 0; i < quads; i++)
+		items[i] = htobe32(items[i]);
+
+	/* Insert this entry to list and enter critical section. */
+	g_mutex_lock(&priv->lock);
+	priv->transactions = g_list_append(priv->transactions, &trans);
+
+	/* NOTE: Timeout is 200 milli-seconds. */
+	expiration = g_get_monotonic_time() + 200 * G_TIME_SPAN_MILLISECOND;
+	g_cond_init(&trans.cond);
+
+	/* Send this request frame. */
+	hinawa_snd_unit_write(&self->parent_instance, trans.frame, quads * 4,
+			      exception);
+	if (*exception != NULL)
+		goto end;
+
+	/*
+	 * Wait corresponding response till timeout and temporarily leave the
+	 * critical section.
+	 */
+	if (!g_cond_wait_until(&trans.cond, &priv->lock, expiration)) {
+		g_set_error(exception, g_quark_from_static_string(__func__),
+			    ETIMEDOUT, "%s", strerror(ETIMEDOUT));
+		goto end;
+	}
+
+	quads = be32toh(trans.frame->length);
+
+	/* The transactions are aligned to big-endian. */
+	items = (__le32 *)trans.frame;
+	for (i = 0; i < quads; i++)
+		items[i] = be32toh(items[i]);
+
+	/* Check transaction status. */
+	if (trans.frame->status != EFT_STATUS_OK) {
+		g_set_error(exception, g_quark_from_static_string(__func__),
+			    EPROTO, "%s",
+			    efw_status_names[trans.frame->status]);
+		goto end;
+	}
+
+	/* Check transaction headers. */
+	if ((trans.frame->version  <  MINIMUM_SUPPORTED_VERSION) ||
+	    (trans.frame->category != category) ||
+	    (trans.frame->command  != command)) {
+		g_set_error(exception, g_quark_from_static_string(__func__),
+			    EIO, "%s", strerror(EIO));
+		goto end;
+	}
+
+	/* Check returned parameters. */
+	count = quads - sizeof(struct snd_efw_transaction) / 4;
+	if (count > 0 && params == NULL) {
+		g_set_error(exception, g_quark_from_static_string(__func__),
+			    EINVAL, "%s", strerror(EINVAL));
+		goto end;
+	}
+
+	/* Copy parameters. */
+	g_array_insert_vals(params, 0, trans.frame->params, count);
+end:
+	/* Remove thie entry from list and leave the critical section. */
+	priv->transactions =
+			g_list_remove(priv->transactions, (gpointer *)&trans);
+	g_mutex_unlock(&priv->lock);
+
+	g_free(trans.frame);
+}
+
+void hinawa_snd_efw_handle_response(HinawaSndEfw *self,
+				    const void *buf, unsigned int len)
+{
+	HinawaSndEfwPrivate *priv;
+	struct snd_firewire_event_efw_response *event =
+				(struct snd_firewire_event_efw_response *)buf;
+	guint *responses = event->response;
+
+	struct snd_efw_transaction *resp_frame;
+	struct efw_transaction *trans;
+
+	unsigned int quadlets;
+	GList *entry;
+
+	g_return_if_fail(HINAWA_IS_SND_EFW(self));
+	priv = SND_EFW_GET_PRIVATE(self);
+
+	while (len > 0) {
+		resp_frame =  (struct snd_efw_transaction *)responses;
+
+		g_mutex_lock(&priv->lock);
+
+		trans = NULL;
+		for (entry = priv->transactions;
+		     entry != NULL; entry = entry->next) {
+			trans = (struct efw_transaction *)entry->data;
+
+			if (be32toh(resp_frame->seqnum) == trans->seqnum)
+				break;
+		}
+
+		g_mutex_unlock(&priv->lock);
+
+		if (trans == NULL)
+			continue;
+
+		quadlets = be32toh(resp_frame->length);
+		memcpy(trans->frame, resp_frame, quadlets * 4);
+		g_cond_signal(&trans->cond);
+
+		responses += quadlets;
+		len -= quadlets * sizeof(guint);
+	}
+}
diff --git a/libhinawa/src/snd_efw.h b/libhinawa/src/snd_efw.h
new file mode 100644
index 0000000..cdc514b
--- /dev/null
+++ b/libhinawa/src/snd_efw.h
@@ -0,0 +1,54 @@
+#ifndef __ALSA_TOOLS_HINAWA_SND_EFW_H__
+#define __ALSA_TOOLS_HINAWA_SND_EFW_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include "snd_unit.h"
+
+G_BEGIN_DECLS
+
+#define HINAWA_TYPE_SND_EFW	(hinawa_snd_efw_get_type())
+
+#define HINAWA_SND_EFW(obj)					\
+	(G_TYPE_CHECK_INSTANCE_CAST((obj),			\
+				    HINAWA_TYPE_SND_EFW,	\
+				    HinawaSndEfw))
+#define HINAWA_IS_SND_EFW(obj)					\
+	(G_TYPE_CHECK_INSTANCE_TYPE((obj),			\
+				    HINAWA_TYPE_SND_EFW))
+
+#define HINAWA_SND_EFW_CLASS(klass)				\
+	(G_TYPE_CHECK_CLASS_CAST((klass),			\
+				 HINAWA_TYPE_SND_EFW,		\
+				 HinawaSndEfwClass))
+#define HINAWA_IS_SND_EFW_CLASS(klass)				\
+	(G_TYPE_CHECK_CLASS_TYPE((klass),			\
+				 HINAWA_TYPE_SND_EFW))
+#define HINAWA_SND_EFW_GET_CLASS(obj)				\
+	(G_TYPE_INSTANCE_GET_CLASS((obj),			\
+				   HINAWA_TYPE_SND_EFW,		\
+				   HinawaSndEfwClass))
+
+typedef struct _HinawaSndEfw		HinawaSndEfw;
+typedef struct _HinawaSndEfwClass	HinawaSndEfwClass;
+typedef struct _HinawaSndEfwPrivate	HinawaSndEfwPrivate;
+
+struct _HinawaSndEfw {
+	HinawaSndUnit parent_instance;
+
+	HinawaSndEfwPrivate *priv;
+};
+
+struct _HinawaSndEfwClass {
+	HinawaSndUnitClass parent_class;
+};
+
+GType hinawa_snd_efw_get_type(void) G_GNUC_CONST;
+
+void hinawa_snd_efw_open(HinawaSndEfw *self, gchar *path, GError **exception);
+
+void hinawa_snd_efw_transact(HinawaSndEfw *self, guint category, guint command,
+			     GArray *args, GArray *params,
+			     GError **exception);
+
+#endif
diff --git a/libhinawa/src/snd_unit.c b/libhinawa/src/snd_unit.c
index b7537a3..eea6bbe 100644
--- a/libhinawa/src/snd_unit.c
+++ b/libhinawa/src/snd_unit.c
@@ -7,6 +7,7 @@
 #include "fw_req.h"
 #include "fw_fcp.h"
 #include "snd_dice.h"
+#include "snd_efw.h"
 #include "internal.h"
 
 #ifdef HAVE_CONFIG_H
@@ -412,6 +413,10 @@ static gboolean check_src(GSource *gsrc)
 		 common->type == SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION)
 		hinawa_snd_dice_handle_notification(HINAWA_SND_DICE(unit),
 						    priv->buf, len);
+	else if (HINAWA_IS_SND_EFW(unit) &&
+		 common->type == SNDRV_FIREWIRE_EVENT_EFW_RESPONSE)
+		hinawa_snd_efw_handle_response(HINAWA_SND_EFW(unit),
+					       priv->buf, len);
 end:
 	/* Don't go to dispatch, then continue to process this source. */
 	return FALSE;
