summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/dcopy.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/io/dcopy.c')
-rw-r--r--usr/src/uts/common/io/dcopy.c932
1 files changed, 932 insertions, 0 deletions
diff --git a/usr/src/uts/common/io/dcopy.c b/usr/src/uts/common/io/dcopy.c
new file mode 100644
index 0000000000..2dc5a311bc
--- /dev/null
+++ b/usr/src/uts/common/io/dcopy.c
@@ -0,0 +1,932 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * dcopy.c
+ * dcopy misc module
+ */
+
+#include <sys/conf.h>
+#include <sys/kmem.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+#include <sys/sysmacros.h>
+#include <sys/atomic.h>
+
+
+#include <sys/dcopy.h>
+#include <sys/dcopy_device.h>
+
+
+/* Number of entries per channel to allocate */
+uint_t dcopy_channel_size = 1024;
+
+
+typedef struct dcopy_list_s {
+ list_t dl_list;
+ kmutex_t dl_mutex;
+ uint_t dl_cnt; /* num entries on list */
+} dcopy_list_t;
+
+/* device state for register/unregister */
+struct dcopy_device_s {
+ /* DMA device drivers private pointer */
+ void *dc_device_private;
+
+ /* to track list of channels from this DMA device */
+ dcopy_list_t dc_devchan_list;
+ list_node_t dc_device_list_node;
+
+ /*
+ * dc_removing_cnt track how many channels still have to be freed up
+ * before it's safe to allow the DMA device driver to detach.
+ */
+ uint_t dc_removing_cnt;
+ dcopy_device_cb_t *dc_cb;
+
+ dcopy_device_info_t dc_info;
+
+};
+
+typedef struct dcopy_stats_s {
+ kstat_named_t cs_bytes_xfer;
+ kstat_named_t cs_cmd_alloc;
+ kstat_named_t cs_cmd_post;
+ kstat_named_t cs_cmd_poll;
+ kstat_named_t cs_notify_poll;
+ kstat_named_t cs_notify_pending;
+ kstat_named_t cs_id;
+ kstat_named_t cs_capabilities;
+} dcopy_stats_t;
+
+/* DMA channel state */
+struct dcopy_channel_s {
+ /* DMA driver channel private pointer */
+ void *ch_channel_private;
+
+ /* shortcut to device callbacks */
+ dcopy_device_cb_t *ch_cb;
+
+ /*
+ * number of outstanding allocs for this channel. used to track when
+ * it's safe to free up this channel so the DMA device driver can
+ * detach.
+ */
+ uint64_t ch_ref_cnt;
+
+ /* state for if channel needs to be removed when ch_ref_cnt gets to 0 */
+ boolean_t ch_removing;
+
+ list_node_t ch_devchan_list_node;
+ list_node_t ch_globalchan_list_node;
+
+ /*
+ * per channel list of commands actively blocking waiting for
+ * completion.
+ */
+ dcopy_list_t ch_poll_list;
+
+ /* pointer back to our device */
+ struct dcopy_device_s *ch_device;
+
+ dcopy_query_channel_t ch_info;
+
+ kstat_t *ch_kstat;
+ dcopy_stats_t ch_stat;
+};
+
+/*
+ * If grabbing both device_list mutex & globalchan_list mutex,
+ * Always grab globalchan_list mutex before device_list mutex
+ */
+typedef struct dcopy_state_s {
+ dcopy_list_t d_device_list;
+ dcopy_list_t d_globalchan_list;
+} dcopy_state_t;
+dcopy_state_t *dcopy_statep;
+
+
+/* Module Driver Info */
+static struct modlmisc dcopy_modlmisc = {
+ &mod_miscops,
+ "dcopy kernel module"
+};
+
+/* Module Linkage */
+static struct modlinkage dcopy_modlinkage = {
+ MODREV_1,
+ &dcopy_modlmisc,
+ NULL
+};
+
+static int dcopy_init();
+static void dcopy_fini();
+
+static int dcopy_list_init(dcopy_list_t *list, size_t node_size,
+ offset_t link_offset);
+static void dcopy_list_fini(dcopy_list_t *list);
+static void dcopy_list_push(dcopy_list_t *list, void *list_node);
+static void *dcopy_list_pop(dcopy_list_t *list);
+
+static void dcopy_device_cleanup(dcopy_device_handle_t device,
+ boolean_t do_callback);
+
+static int dcopy_stats_init(dcopy_handle_t channel);
+static void dcopy_stats_fini(dcopy_handle_t channel);
+
+
+/*
+ * _init()
+ */
+int
+_init()
+{
+ int e;
+
+ e = dcopy_init();
+ if (e != 0) {
+ return (e);
+ }
+
+ return (mod_install(&dcopy_modlinkage));
+}
+
+
+/*
+ * _info()
+ */
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&dcopy_modlinkage, modinfop));
+}
+
+
+/*
+ * _fini()
+ */
+int
+_fini()
+{
+ int e;
+
+ e = mod_remove(&dcopy_modlinkage);
+ if (e != 0) {
+ return (e);
+ }
+
+ dcopy_fini();
+
+ return (e);
+}
+
+/*
+ * dcopy_init()
+ */
+static int
+dcopy_init()
+{
+ int e;
+
+
+ dcopy_statep = kmem_zalloc(sizeof (*dcopy_statep), KM_SLEEP);
+
+ /* Initialize the list we use to track device register/unregister */
+ e = dcopy_list_init(&dcopy_statep->d_device_list,
+ sizeof (struct dcopy_device_s),
+ offsetof(struct dcopy_device_s, dc_device_list_node));
+ if (e != DCOPY_SUCCESS) {
+ goto dcopyinitfail_device;
+ }
+
+ /* Initialize the list we use to track all DMA channels */
+ e = dcopy_list_init(&dcopy_statep->d_globalchan_list,
+ sizeof (struct dcopy_channel_s),
+ offsetof(struct dcopy_channel_s, ch_globalchan_list_node));
+ if (e != DCOPY_SUCCESS) {
+ goto dcopyinitfail_global;
+ }
+
+ return (0);
+
+dcopyinitfail_cback:
+ dcopy_list_fini(&dcopy_statep->d_globalchan_list);
+dcopyinitfail_global:
+ dcopy_list_fini(&dcopy_statep->d_device_list);
+dcopyinitfail_device:
+ kmem_free(dcopy_statep, sizeof (*dcopy_statep));
+
+ return (-1);
+}
+
+
+/*
+ * dcopy_fini()
+ */
+static void
+dcopy_fini()
+{
+ /*
+ * if mod_remove was successfull, we shouldn't have any
+ * devices/channels to worry about.
+ */
+ ASSERT(list_head(&dcopy_statep->d_globalchan_list.dl_list) == NULL);
+ ASSERT(list_head(&dcopy_statep->d_device_list.dl_list) == NULL);
+
+ dcopy_list_fini(&dcopy_statep->d_globalchan_list);
+ dcopy_list_fini(&dcopy_statep->d_device_list);
+ kmem_free(dcopy_statep, sizeof (*dcopy_statep));
+}
+
+
+/* *** EXTERNAL INTERFACE *** */
+/*
+ * dcopy_query()
+ */
+void
+dcopy_query(dcopy_query_t *query)
+{
+ query->dq_version = DCOPY_QUERY_V0;
+ query->dq_num_channels = dcopy_statep->d_globalchan_list.dl_cnt;
+}
+
+
+/*
+ * dcopy_alloc()
+ */
+/*ARGSUSED*/
+int
+dcopy_alloc(int flags, dcopy_handle_t *handle)
+{
+ dcopy_handle_t channel;
+ dcopy_list_t *list;
+
+
+ /*
+ * we don't use the dcopy_list_* code here because we need to due
+ * some non-standard stuff.
+ */
+
+ list = &dcopy_statep->d_globalchan_list;
+
+ /*
+ * if nothing is on the channel list, return DCOPY_NORESOURCES. This
+ * can happen if there aren't any DMA device registered.
+ */
+ mutex_enter(&list->dl_mutex);
+ channel = list_head(&list->dl_list);
+ if (channel == NULL) {
+ mutex_exit(&list->dl_mutex);
+ return (DCOPY_NORESOURCES);
+ }
+
+ /*
+ * increment the reference count, and pop the channel off the head and
+ * push it on the tail. This ensures we rotate through the channels.
+ * DMA channels are shared.
+ */
+ channel->ch_ref_cnt++;
+ list_remove(&list->dl_list, channel);
+ list_insert_tail(&list->dl_list, channel);
+ mutex_exit(&list->dl_mutex);
+
+ *handle = (dcopy_handle_t)channel;
+ return (DCOPY_SUCCESS);
+}
+
+
+/*
+ * dcopy_free()
+ */
+void
+dcopy_free(dcopy_handle_t *channel)
+{
+ dcopy_device_handle_t device;
+ dcopy_list_t *list;
+ boolean_t cleanup;
+
+
+ ASSERT(*channel != NULL);
+
+ /*
+ * we don't need to add the channel back to the list since we never
+ * removed it. decrement the reference count.
+ */
+ list = &dcopy_statep->d_globalchan_list;
+ mutex_enter(&list->dl_mutex);
+ (*channel)->ch_ref_cnt--;
+
+ /*
+ * if we need to remove this channel, and the reference count is down
+ * to 0, decrement the number of channels which still need to be
+ * removed on the device.
+ */
+ if ((*channel)->ch_removing && ((*channel)->ch_ref_cnt == 0)) {
+ cleanup = B_FALSE;
+ device = (*channel)->ch_device;
+ mutex_enter(&device->dc_devchan_list.dl_mutex);
+ device->dc_removing_cnt--;
+ if (device->dc_removing_cnt == 0) {
+ cleanup = B_TRUE;
+ }
+ mutex_exit(&device->dc_devchan_list.dl_mutex);
+ }
+ mutex_exit(&list->dl_mutex);
+
+ /*
+ * if there are no channels which still need to be removed, cleanup the
+ * device state and call back into the DMA device driver to tell them
+ * the device is free.
+ */
+ if (cleanup) {
+ dcopy_device_cleanup(device, B_TRUE);
+ }
+
+ *channel = NULL;
+}
+
+
+/*
+ * dcopy_query_channel()
+ */
+void
+dcopy_query_channel(dcopy_handle_t channel, dcopy_query_channel_t *query)
+{
+ *query = channel->ch_info;
+}
+
+
+/*
+ * dcopy_cmd_alloc()
+ */
+int
+dcopy_cmd_alloc(dcopy_handle_t handle, int flags, dcopy_cmd_t *cmd)
+{
+ dcopy_handle_t channel;
+ dcopy_cmd_priv_t priv;
+ int e;
+
+
+ channel = handle;
+
+ atomic_inc_64(&channel->ch_stat.cs_cmd_alloc.value.ui64);
+ e = channel->ch_cb->cb_cmd_alloc(channel->ch_channel_private, flags,
+ cmd);
+ if (e == DCOPY_SUCCESS) {
+ priv = (*cmd)->dp_private;
+ priv->pr_channel = channel;
+ /*
+ * we won't initialize the blocking state until we actually
+ * need to block.
+ */
+ priv->pr_block_init = B_FALSE;
+ }
+
+ return (e);
+}
+
+
+/*
+ * dcopy_cmd_free()
+ */
+void
+dcopy_cmd_free(dcopy_cmd_t *cmd)
+{
+ dcopy_handle_t channel;
+ dcopy_cmd_priv_t priv;
+
+
+ ASSERT(*cmd != NULL);
+
+ priv = (*cmd)->dp_private;
+ channel = priv->pr_channel;
+
+ /* if we initialized the blocking state, clean it up too */
+ if (priv->pr_block_init) {
+ cv_destroy(&priv->pr_cv);
+ mutex_destroy(&priv->pr_mutex);
+ }
+
+ channel->ch_cb->cb_cmd_free(channel->ch_channel_private, cmd);
+}
+
+
+/*
+ * dcopy_cmd_post()
+ */
+int
+dcopy_cmd_post(dcopy_cmd_t cmd)
+{
+ dcopy_handle_t channel;
+ int e;
+
+
+ channel = cmd->dp_private->pr_channel;
+
+ atomic_inc_64(&channel->ch_stat.cs_cmd_post.value.ui64);
+ if (cmd->dp_cmd == DCOPY_CMD_COPY) {
+ atomic_add_64(&channel->ch_stat.cs_bytes_xfer.value.ui64,
+ cmd->dp.copy.cc_size);
+ }
+ e = channel->ch_cb->cb_cmd_post(channel->ch_channel_private, cmd);
+ if (e != DCOPY_SUCCESS) {
+ return (e);
+ }
+
+ return (DCOPY_SUCCESS);
+}
+
+
+/*
+ * dcopy_cmd_poll()
+ */
+int
+dcopy_cmd_poll(dcopy_cmd_t cmd, int flags)
+{
+ dcopy_handle_t channel;
+ dcopy_cmd_priv_t priv;
+ int e;
+
+
+ priv = cmd->dp_private;
+ channel = priv->pr_channel;
+
+ /*
+ * if the caller is trying to block, they needed to post the
+ * command with DCOPY_CMD_INTR set.
+ */
+ if ((flags & DCOPY_POLL_BLOCK) && !(cmd->dp_flags & DCOPY_CMD_INTR)) {
+ return (DCOPY_FAILURE);
+ }
+
+ atomic_inc_64(&channel->ch_stat.cs_cmd_poll.value.ui64);
+
+repoll:
+ e = channel->ch_cb->cb_cmd_poll(channel->ch_channel_private, cmd);
+ if (e == DCOPY_PENDING) {
+ /*
+ * if the command is still active, and the blocking flag
+ * is set.
+ */
+ if (flags & DCOPY_POLL_BLOCK) {
+
+ /*
+ * if we haven't initialized the state, do it now. A
+ * command can be re-used, so it's possible it's
+ * already been initialized.
+ */
+ if (!priv->pr_block_init) {
+ priv->pr_block_init = B_TRUE;
+ mutex_init(&priv->pr_mutex, NULL, MUTEX_DRIVER,
+ NULL);
+ cv_init(&priv->pr_cv, NULL, CV_DRIVER, NULL);
+ priv->pr_cmd = cmd;
+ }
+
+ /* push it on the list for blocking commands */
+ priv->pr_wait = B_TRUE;
+ dcopy_list_push(&channel->ch_poll_list, priv);
+
+ mutex_enter(&priv->pr_mutex);
+ /*
+ * it's possible we already cleared pr_wait before we
+ * grabbed the mutex.
+ */
+ if (priv->pr_wait) {
+ cv_wait(&priv->pr_cv, &priv->pr_mutex);
+ }
+ mutex_exit(&priv->pr_mutex);
+
+ /*
+ * the command has completed, go back and poll so we
+ * get the status.
+ */
+ goto repoll;
+ }
+ }
+
+ return (e);
+}
+
+/* *** END OF EXTERNAL INTERFACE *** */
+
+/*
+ * dcopy_list_init()
+ */
+static int
+dcopy_list_init(dcopy_list_t *list, size_t node_size, offset_t link_offset)
+{
+ mutex_init(&list->dl_mutex, NULL, MUTEX_DRIVER, NULL);
+ list_create(&list->dl_list, node_size, link_offset);
+ list->dl_cnt = 0;
+
+ return (DCOPY_SUCCESS);
+}
+
+
+/*
+ * dcopy_list_fini()
+ */
+static void
+dcopy_list_fini(dcopy_list_t *list)
+{
+ list_destroy(&list->dl_list);
+ mutex_destroy(&list->dl_mutex);
+}
+
+
+/*
+ * dcopy_list_push()
+ */
+static void
+dcopy_list_push(dcopy_list_t *list, void *list_node)
+{
+ mutex_enter(&list->dl_mutex);
+ list_insert_tail(&list->dl_list, list_node);
+ list->dl_cnt++;
+ mutex_exit(&list->dl_mutex);
+}
+
+
+/*
+ * dcopy_list_pop()
+ */
+static void *
+dcopy_list_pop(dcopy_list_t *list)
+{
+ list_node_t *list_node;
+
+ mutex_enter(&list->dl_mutex);
+ list_node = list_head(&list->dl_list);
+ if (list_node == NULL) {
+ mutex_exit(&list->dl_mutex);
+ return (list_node);
+ }
+ list->dl_cnt--;
+ list_remove(&list->dl_list, list_node);
+ mutex_exit(&list->dl_mutex);
+
+ return (list_node);
+}
+
+
+/* *** DEVICE INTERFACE *** */
+/*
+ * dcopy_device_register()
+ */
+int
+dcopy_device_register(void *device_private, dcopy_device_info_t *info,
+ dcopy_device_handle_t *handle)
+{
+ struct dcopy_channel_s *channel;
+ struct dcopy_device_s *device;
+ int e;
+ int i;
+
+
+ /* initialize the per device state */
+ device = kmem_zalloc(sizeof (*device), KM_SLEEP);
+ device->dc_device_private = device_private;
+ device->dc_info = *info;
+ device->dc_removing_cnt = 0;
+ device->dc_cb = info->di_cb;
+
+ /*
+ * we have a per device channel list so we can remove a device in the
+ * future.
+ */
+ e = dcopy_list_init(&device->dc_devchan_list,
+ sizeof (struct dcopy_channel_s),
+ offsetof(struct dcopy_channel_s, ch_devchan_list_node));
+ if (e != DCOPY_SUCCESS) {
+ goto registerfail_devchan;
+ }
+
+ /*
+ * allocate state for each channel, allocate the channel, and then add
+ * the devices dma channels to the devices channel list.
+ */
+ for (i = 0; i < info->di_num_dma; i++) {
+ channel = kmem_zalloc(sizeof (*channel), KM_SLEEP);
+ channel->ch_device = device;
+ channel->ch_removing = B_FALSE;
+ channel->ch_ref_cnt = 0;
+ channel->ch_cb = info->di_cb;
+
+ e = info->di_cb->cb_channel_alloc(device_private, channel,
+ DCOPY_SLEEP, dcopy_channel_size, &channel->ch_info,
+ &channel->ch_channel_private);
+ if (e != DCOPY_SUCCESS) {
+ kmem_free(channel, sizeof (*channel));
+ goto registerfail_alloc;
+ }
+
+ e = dcopy_stats_init(channel);
+ if (e != DCOPY_SUCCESS) {
+ info->di_cb->cb_channel_free(
+ &channel->ch_channel_private);
+ kmem_free(channel, sizeof (*channel));
+ goto registerfail_alloc;
+ }
+
+ e = dcopy_list_init(&channel->ch_poll_list,
+ sizeof (struct dcopy_cmd_priv_s),
+ offsetof(struct dcopy_cmd_priv_s, pr_poll_list_node));
+ if (e != DCOPY_SUCCESS) {
+ dcopy_stats_fini(channel);
+ info->di_cb->cb_channel_free(
+ &channel->ch_channel_private);
+ kmem_free(channel, sizeof (*channel));
+ goto registerfail_alloc;
+ }
+
+ dcopy_list_push(&device->dc_devchan_list, channel);
+ }
+
+ /* add the device to device list */
+ dcopy_list_push(&dcopy_statep->d_device_list, device);
+
+ /*
+ * add the device's dma channels to the global channel list (where
+ * dcopy_alloc's come from)
+ */
+ mutex_enter(&dcopy_statep->d_globalchan_list.dl_mutex);
+ mutex_enter(&dcopy_statep->d_device_list.dl_mutex);
+ channel = list_head(&device->dc_devchan_list.dl_list);
+ while (channel != NULL) {
+ list_insert_tail(&dcopy_statep->d_globalchan_list.dl_list,
+ channel);
+ dcopy_statep->d_globalchan_list.dl_cnt++;
+ channel = list_next(&device->dc_devchan_list.dl_list, channel);
+ }
+ mutex_exit(&dcopy_statep->d_device_list.dl_mutex);
+ mutex_exit(&dcopy_statep->d_globalchan_list.dl_mutex);
+
+ *handle = device;
+ return (DCOPY_SUCCESS);
+
+registerfail_alloc:
+ channel = list_head(&device->dc_devchan_list.dl_list);
+ while (channel != NULL) {
+ /* remove from the list */
+ channel = dcopy_list_pop(&device->dc_devchan_list);
+ ASSERT(channel != NULL);
+
+ dcopy_list_fini(&channel->ch_poll_list);
+ dcopy_stats_fini(channel);
+ info->di_cb->cb_channel_free(&channel->ch_channel_private);
+ kmem_free(channel, sizeof (*channel));
+ }
+
+ dcopy_list_fini(&device->dc_devchan_list);
+registerfail_devchan:
+ kmem_free(device, sizeof (*device));
+
+ return (DCOPY_FAILURE);
+}
+
+
+/*
+ * dcopy_device_unregister()
+ */
+/*ARGSUSED*/
+int
+dcopy_device_unregister(dcopy_device_handle_t *handle)
+{
+ struct dcopy_channel_s *channel;
+ dcopy_device_handle_t device;
+ boolean_t device_busy;
+
+
+ device = *handle;
+ device_busy = B_FALSE;
+
+ /*
+ * remove the devices dma channels from the global channel list (where
+ * dcopy_alloc's come from)
+ */
+ mutex_enter(&dcopy_statep->d_globalchan_list.dl_mutex);
+ mutex_enter(&device->dc_devchan_list.dl_mutex);
+ channel = list_head(&device->dc_devchan_list.dl_list);
+ while (channel != NULL) {
+ /*
+ * if the channel has outstanding allocs, mark it as having
+ * to be removed and increment the number of channels which
+ * need to be removed in the device state too.
+ */
+ if (channel->ch_ref_cnt != 0) {
+ channel->ch_removing = B_TRUE;
+ device_busy = B_TRUE;
+ device->dc_removing_cnt++;
+ }
+ dcopy_statep->d_globalchan_list.dl_cnt--;
+ list_remove(&dcopy_statep->d_globalchan_list.dl_list, channel);
+ channel = list_next(&device->dc_devchan_list.dl_list, channel);
+ }
+ mutex_exit(&device->dc_devchan_list.dl_mutex);
+ mutex_exit(&dcopy_statep->d_globalchan_list.dl_mutex);
+
+ /*
+ * if there are channels which still need to be removed, we will clean
+ * up the device state after they are freed up.
+ */
+ if (device_busy) {
+ return (DCOPY_PENDING);
+ }
+
+ dcopy_device_cleanup(device, B_FALSE);
+
+ *handle = NULL;
+ return (DCOPY_SUCCESS);
+}
+
+
+/*
+ * dcopy_device_cleanup()
+ */
+static void
+dcopy_device_cleanup(dcopy_device_handle_t device, boolean_t do_callback)
+{
+ struct dcopy_channel_s *channel;
+
+ /*
+ * remove all the channels in the device list, free them, and clean up
+ * the state.
+ */
+ mutex_enter(&dcopy_statep->d_device_list.dl_mutex);
+ channel = list_head(&device->dc_devchan_list.dl_list);
+ while (channel != NULL) {
+ device->dc_devchan_list.dl_cnt--;
+ list_remove(&device->dc_devchan_list.dl_list, channel);
+ dcopy_list_fini(&channel->ch_poll_list);
+ dcopy_stats_fini(channel);
+ channel->ch_cb->cb_channel_free(&channel->ch_channel_private);
+ kmem_free(channel, sizeof (*channel));
+ channel = list_head(&device->dc_devchan_list.dl_list);
+ }
+
+ /* remove it from the list of devices */
+ list_remove(&dcopy_statep->d_device_list.dl_list, device);
+
+ mutex_exit(&dcopy_statep->d_device_list.dl_mutex);
+
+ /*
+ * notify the DMA device driver that the device is free to be
+ * detached.
+ */
+ if (do_callback) {
+ device->dc_cb->cb_unregister_complete(
+ device->dc_device_private, DCOPY_SUCCESS);
+ }
+
+ dcopy_list_fini(&device->dc_devchan_list);
+ kmem_free(device, sizeof (*device));
+}
+
+
+/*
+ * dcopy_device_channel_notify()
+ */
+/*ARGSUSED*/
+void
+dcopy_device_channel_notify(dcopy_handle_t handle, int status)
+{
+ struct dcopy_channel_s *channel;
+ dcopy_list_t *poll_list;
+ dcopy_cmd_priv_t priv;
+ int e;
+
+
+ ASSERT(status == DCOPY_COMPLETION);
+ channel = handle;
+
+ poll_list = &channel->ch_poll_list;
+
+ /*
+ * when we get a completion notification from the device, go through
+ * all of the commands blocking on this channel and see if they have
+ * completed. Remove the command and wake up the block thread if they
+ * have. Once we hit a command which is still pending, we are done
+ * polling since commands in a channel complete in order.
+ */
+ mutex_enter(&poll_list->dl_mutex);
+ if (poll_list->dl_cnt != 0) {
+ priv = list_head(&poll_list->dl_list);
+ while (priv != NULL) {
+ atomic_inc_64(&channel->
+ ch_stat.cs_notify_poll.value.ui64);
+ e = channel->ch_cb->cb_cmd_poll(
+ channel->ch_channel_private,
+ priv->pr_cmd);
+ if (e == DCOPY_PENDING) {
+ atomic_inc_64(&channel->
+ ch_stat.cs_notify_pending.value.ui64);
+ break;
+ }
+
+ poll_list->dl_cnt--;
+ list_remove(&poll_list->dl_list, priv);
+
+ mutex_enter(&priv->pr_mutex);
+ priv->pr_wait = B_FALSE;
+ cv_signal(&priv->pr_cv);
+ mutex_exit(&priv->pr_mutex);
+
+ priv = list_head(&poll_list->dl_list);
+ }
+ }
+
+ mutex_exit(&poll_list->dl_mutex);
+}
+
+
+/*
+ * dcopy_stats_init()
+ */
+static int
+dcopy_stats_init(dcopy_handle_t channel)
+{
+#define CHANSTRSIZE 20
+ char chanstr[CHANSTRSIZE];
+ dcopy_stats_t *stats;
+ int instance;
+ char *name;
+
+
+ stats = &channel->ch_stat;
+ name = (char *)ddi_driver_name(channel->ch_device->dc_info.di_dip);
+ instance = ddi_get_instance(channel->ch_device->dc_info.di_dip);
+
+ (void) snprintf(chanstr, CHANSTRSIZE, "channel%d",
+ (uint32_t)channel->ch_info.qc_chan_num);
+
+ channel->ch_kstat = kstat_create(name, instance, chanstr, "misc",
+ KSTAT_TYPE_NAMED, sizeof (dcopy_stats_t) / sizeof (kstat_named_t),
+ KSTAT_FLAG_VIRTUAL);
+ if (channel->ch_kstat == NULL) {
+ return (DCOPY_FAILURE);
+ }
+ channel->ch_kstat->ks_data = stats;
+
+ kstat_named_init(&stats->cs_bytes_xfer, "bytes_xfer",
+ KSTAT_DATA_UINT64);
+ kstat_named_init(&stats->cs_cmd_alloc, "cmd_alloc",
+ KSTAT_DATA_UINT64);
+ kstat_named_init(&stats->cs_cmd_post, "cmd_post",
+ KSTAT_DATA_UINT64);
+ kstat_named_init(&stats->cs_cmd_poll, "cmd_poll",
+ KSTAT_DATA_UINT64);
+ kstat_named_init(&stats->cs_notify_poll, "notify_poll",
+ KSTAT_DATA_UINT64);
+ kstat_named_init(&stats->cs_notify_pending, "notify_pending",
+ KSTAT_DATA_UINT64);
+ kstat_named_init(&stats->cs_id, "id",
+ KSTAT_DATA_UINT64);
+ kstat_named_init(&stats->cs_capabilities, "capabilities",
+ KSTAT_DATA_UINT64);
+
+ kstat_install(channel->ch_kstat);
+
+ channel->ch_stat.cs_id.value.ui64 = channel->ch_info.qc_id;
+ channel->ch_stat.cs_capabilities.value.ui64 =
+ channel->ch_info.qc_capabilities;
+
+ return (DCOPY_SUCCESS);
+}
+
+
+/*
+ * dcopy_stats_fini()
+ */
+static void
+dcopy_stats_fini(dcopy_handle_t channel)
+{
+ kstat_delete(channel->ch_kstat);
+}
+/* *** END OF DEVICE INTERFACE *** */