117169044Sbrutus /* 217169044Sbrutus * CDDL HEADER START 317169044Sbrutus * 417169044Sbrutus * The contents of this file are subject to the terms of the 517169044Sbrutus * Common Development and Distribution License (the "License"). 617169044Sbrutus * You may not use this file except in compliance with the License. 717169044Sbrutus * 817169044Sbrutus * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 917169044Sbrutus * or http://www.opensolaris.org/os/licensing. 1017169044Sbrutus * See the License for the specific language governing permissions 1117169044Sbrutus * and limitations under the License. 1217169044Sbrutus * 1317169044Sbrutus * When distributing Covered Code, include this CDDL HEADER in each 1417169044Sbrutus * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 1517169044Sbrutus * If applicable, add the following below this CDDL HEADER, with the 1617169044Sbrutus * fields enclosed by brackets "[]" replaced with your own identifying 1717169044Sbrutus * information: Portions Copyright [yyyy] [name of copyright owner] 1817169044Sbrutus * 1917169044Sbrutus * CDDL HEADER END 2017169044Sbrutus */ 2117169044Sbrutus 2217169044Sbrutus /* 2317169044Sbrutus * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 2417169044Sbrutus * Use is subject to license terms. 2517169044Sbrutus */ 2617169044Sbrutus 2717169044Sbrutus /* 2817169044Sbrutus * dcopy.c 2917169044Sbrutus * dcopy misc module 3017169044Sbrutus */ 3117169044Sbrutus 3217169044Sbrutus #include <sys/conf.h> 3317169044Sbrutus #include <sys/kmem.h> 3417169044Sbrutus #include <sys/ddi.h> 3517169044Sbrutus #include <sys/sunddi.h> 3617169044Sbrutus #include <sys/modctl.h> 3717169044Sbrutus #include <sys/sysmacros.h> 3817169044Sbrutus #include <sys/atomic.h> 3917169044Sbrutus 4017169044Sbrutus 4117169044Sbrutus #include <sys/dcopy.h> 4217169044Sbrutus #include <sys/dcopy_device.h> 4317169044Sbrutus 4417169044Sbrutus 4517169044Sbrutus /* Number of entries per channel to allocate */ 4617169044Sbrutus uint_t dcopy_channel_size = 1024; 4717169044Sbrutus 4817169044Sbrutus 4917169044Sbrutus typedef struct dcopy_list_s { 5017169044Sbrutus list_t dl_list; 5117169044Sbrutus kmutex_t dl_mutex; 5217169044Sbrutus uint_t dl_cnt; /* num entries on list */ 5317169044Sbrutus } dcopy_list_t; 5417169044Sbrutus 5517169044Sbrutus /* device state for register/unregister */ 5617169044Sbrutus struct dcopy_device_s { 5717169044Sbrutus /* DMA device drivers private pointer */ 5817169044Sbrutus void *dc_device_private; 5917169044Sbrutus 6017169044Sbrutus /* to track list of channels from this DMA device */ 6117169044Sbrutus dcopy_list_t dc_devchan_list; 6217169044Sbrutus list_node_t dc_device_list_node; 6317169044Sbrutus 6417169044Sbrutus /* 6517169044Sbrutus * dc_removing_cnt track how many channels still have to be freed up 6617169044Sbrutus * before it's safe to allow the DMA device driver to detach. 6717169044Sbrutus */ 6817169044Sbrutus uint_t dc_removing_cnt; 6917169044Sbrutus dcopy_device_cb_t *dc_cb; 7017169044Sbrutus 7117169044Sbrutus dcopy_device_info_t dc_info; 7217169044Sbrutus 7317169044Sbrutus }; 7417169044Sbrutus 7517169044Sbrutus typedef struct dcopy_stats_s { 7617169044Sbrutus kstat_named_t cs_bytes_xfer; 7717169044Sbrutus kstat_named_t cs_cmd_alloc; 7817169044Sbrutus kstat_named_t cs_cmd_post; 7917169044Sbrutus kstat_named_t cs_cmd_poll; 8017169044Sbrutus kstat_named_t cs_notify_poll; 8117169044Sbrutus kstat_named_t cs_notify_pending; 8217169044Sbrutus kstat_named_t cs_id; 8317169044Sbrutus kstat_named_t cs_capabilities; 8417169044Sbrutus } dcopy_stats_t; 8517169044Sbrutus 8617169044Sbrutus /* DMA channel state */ 8717169044Sbrutus struct dcopy_channel_s { 8817169044Sbrutus /* DMA driver channel private pointer */ 8917169044Sbrutus void *ch_channel_private; 9017169044Sbrutus 9117169044Sbrutus /* shortcut to device callbacks */ 9217169044Sbrutus dcopy_device_cb_t *ch_cb; 9317169044Sbrutus 9417169044Sbrutus /* 9517169044Sbrutus * number of outstanding allocs for this channel. used to track when 9617169044Sbrutus * it's safe to free up this channel so the DMA device driver can 9717169044Sbrutus * detach. 9817169044Sbrutus */ 9917169044Sbrutus uint64_t ch_ref_cnt; 10017169044Sbrutus 10117169044Sbrutus /* state for if channel needs to be removed when ch_ref_cnt gets to 0 */ 10217169044Sbrutus boolean_t ch_removing; 10317169044Sbrutus 10417169044Sbrutus list_node_t ch_devchan_list_node; 10517169044Sbrutus list_node_t ch_globalchan_list_node; 10617169044Sbrutus 10717169044Sbrutus /* 10817169044Sbrutus * per channel list of commands actively blocking waiting for 10917169044Sbrutus * completion. 11017169044Sbrutus */ 11117169044Sbrutus dcopy_list_t ch_poll_list; 11217169044Sbrutus 11317169044Sbrutus /* pointer back to our device */ 11417169044Sbrutus struct dcopy_device_s *ch_device; 11517169044Sbrutus 11617169044Sbrutus dcopy_query_channel_t ch_info; 11717169044Sbrutus 11817169044Sbrutus kstat_t *ch_kstat; 11917169044Sbrutus dcopy_stats_t ch_stat; 12017169044Sbrutus }; 12117169044Sbrutus 12217169044Sbrutus /* 12317169044Sbrutus * If grabbing both device_list mutex & globalchan_list mutex, 12417169044Sbrutus * Always grab globalchan_list mutex before device_list mutex 12517169044Sbrutus */ 12617169044Sbrutus typedef struct dcopy_state_s { 12717169044Sbrutus dcopy_list_t d_device_list; 12817169044Sbrutus dcopy_list_t d_globalchan_list; 12917169044Sbrutus } dcopy_state_t; 13017169044Sbrutus dcopy_state_t *dcopy_statep; 13117169044Sbrutus 13217169044Sbrutus 13317169044Sbrutus /* Module Driver Info */ 13417169044Sbrutus static struct modlmisc dcopy_modlmisc = { 13517169044Sbrutus &mod_miscops, 13617169044Sbrutus "dcopy kernel module" 13717169044Sbrutus }; 13817169044Sbrutus 13917169044Sbrutus /* Module Linkage */ 14017169044Sbrutus static struct modlinkage dcopy_modlinkage = { 14117169044Sbrutus MODREV_1, 14217169044Sbrutus &dcopy_modlmisc, 14317169044Sbrutus NULL 14417169044Sbrutus }; 14517169044Sbrutus 14617169044Sbrutus static int dcopy_init(); 14717169044Sbrutus static void dcopy_fini(); 14817169044Sbrutus 14917169044Sbrutus static int dcopy_list_init(dcopy_list_t *list, size_t node_size, 15017169044Sbrutus offset_t link_offset); 15117169044Sbrutus static void dcopy_list_fini(dcopy_list_t *list); 15217169044Sbrutus static void dcopy_list_push(dcopy_list_t *list, void *list_node); 15317169044Sbrutus static void *dcopy_list_pop(dcopy_list_t *list); 15417169044Sbrutus 15517169044Sbrutus static void dcopy_device_cleanup(dcopy_device_handle_t device, 15617169044Sbrutus boolean_t do_callback); 15717169044Sbrutus 15817169044Sbrutus static int dcopy_stats_init(dcopy_handle_t channel); 15917169044Sbrutus static void dcopy_stats_fini(dcopy_handle_t channel); 16017169044Sbrutus 16117169044Sbrutus 16217169044Sbrutus /* 16317169044Sbrutus * _init() 16417169044Sbrutus */ 16517169044Sbrutus int 16617169044Sbrutus _init() 16717169044Sbrutus { 16817169044Sbrutus int e; 16917169044Sbrutus 17017169044Sbrutus e = dcopy_init(); 17117169044Sbrutus if (e != 0) { 17217169044Sbrutus return (e); 17317169044Sbrutus } 17417169044Sbrutus 17517169044Sbrutus return (mod_install(&dcopy_modlinkage)); 17617169044Sbrutus } 17717169044Sbrutus 17817169044Sbrutus 17917169044Sbrutus /* 18017169044Sbrutus * _info() 18117169044Sbrutus */ 18217169044Sbrutus int 18317169044Sbrutus _info(struct modinfo *modinfop) 18417169044Sbrutus { 18517169044Sbrutus return (mod_info(&dcopy_modlinkage, modinfop)); 18617169044Sbrutus } 18717169044Sbrutus 18817169044Sbrutus 18917169044Sbrutus /* 19017169044Sbrutus * _fini() 19117169044Sbrutus */ 19217169044Sbrutus int 19317169044Sbrutus _fini() 19417169044Sbrutus { 19517169044Sbrutus int e; 19617169044Sbrutus 19717169044Sbrutus e = mod_remove(&dcopy_modlinkage); 19817169044Sbrutus if (e != 0) { 19917169044Sbrutus return (e); 20017169044Sbrutus } 20117169044Sbrutus 20217169044Sbrutus dcopy_fini(); 20317169044Sbrutus 20417169044Sbrutus return (e); 20517169044Sbrutus } 20617169044Sbrutus 20717169044Sbrutus /* 20817169044Sbrutus * dcopy_init() 20917169044Sbrutus */ 21017169044Sbrutus static int 21117169044Sbrutus dcopy_init() 21217169044Sbrutus { 21317169044Sbrutus int e; 21417169044Sbrutus 21517169044Sbrutus 21617169044Sbrutus dcopy_statep = kmem_zalloc(sizeof (*dcopy_statep), KM_SLEEP); 21717169044Sbrutus 21817169044Sbrutus /* Initialize the list we use to track device register/unregister */ 21917169044Sbrutus e = dcopy_list_init(&dcopy_statep->d_device_list, 22017169044Sbrutus sizeof (struct dcopy_device_s), 22117169044Sbrutus offsetof(struct dcopy_device_s, dc_device_list_node)); 22217169044Sbrutus if (e != DCOPY_SUCCESS) { 22317169044Sbrutus goto dcopyinitfail_device; 22417169044Sbrutus } 22517169044Sbrutus 22617169044Sbrutus /* Initialize the list we use to track all DMA channels */ 22717169044Sbrutus e = dcopy_list_init(&dcopy_statep->d_globalchan_list, 22817169044Sbrutus sizeof (struct dcopy_channel_s), 22917169044Sbrutus offsetof(struct dcopy_channel_s, ch_globalchan_list_node)); 23017169044Sbrutus if (e != DCOPY_SUCCESS) { 23117169044Sbrutus goto dcopyinitfail_global; 23217169044Sbrutus } 23317169044Sbrutus 23417169044Sbrutus return (0); 23517169044Sbrutus 23617169044Sbrutus dcopyinitfail_cback: 23717169044Sbrutus dcopy_list_fini(&dcopy_statep->d_globalchan_list); 23817169044Sbrutus dcopyinitfail_global: 23917169044Sbrutus dcopy_list_fini(&dcopy_statep->d_device_list); 24017169044Sbrutus dcopyinitfail_device: 24117169044Sbrutus kmem_free(dcopy_statep, sizeof (*dcopy_statep)); 24217169044Sbrutus 24317169044Sbrutus return (-1); 24417169044Sbrutus } 24517169044Sbrutus 24617169044Sbrutus 24717169044Sbrutus /* 24817169044Sbrutus * dcopy_fini() 24917169044Sbrutus */ 25017169044Sbrutus static void 25117169044Sbrutus dcopy_fini() 25217169044Sbrutus { 25317169044Sbrutus /* 25417169044Sbrutus * if mod_remove was successfull, we shouldn't have any 25517169044Sbrutus * devices/channels to worry about. 25617169044Sbrutus */ 25717169044Sbrutus ASSERT(list_head(&dcopy_statep->d_globalchan_list.dl_list) == NULL); 25817169044Sbrutus ASSERT(list_head(&dcopy_statep->d_device_list.dl_list) == NULL); 25917169044Sbrutus 26017169044Sbrutus dcopy_list_fini(&dcopy_statep->d_globalchan_list); 26117169044Sbrutus dcopy_list_fini(&dcopy_statep->d_device_list); 26217169044Sbrutus kmem_free(dcopy_statep, sizeof (*dcopy_statep)); 26317169044Sbrutus } 26417169044Sbrutus 26517169044Sbrutus 26617169044Sbrutus /* *** EXTERNAL INTERFACE *** */ 26717169044Sbrutus /* 26817169044Sbrutus * dcopy_query() 26917169044Sbrutus */ 27017169044Sbrutus void 27117169044Sbrutus dcopy_query(dcopy_query_t *query) 27217169044Sbrutus { 27317169044Sbrutus query->dq_version = DCOPY_QUERY_V0; 27417169044Sbrutus query->dq_num_channels = dcopy_statep->d_globalchan_list.dl_cnt; 27517169044Sbrutus } 27617169044Sbrutus 27717169044Sbrutus 27817169044Sbrutus /* 27917169044Sbrutus * dcopy_alloc() 28017169044Sbrutus */ 28117169044Sbrutus /*ARGSUSED*/ 28217169044Sbrutus int 28317169044Sbrutus dcopy_alloc(int flags, dcopy_handle_t *handle) 28417169044Sbrutus { 28517169044Sbrutus dcopy_handle_t channel; 28617169044Sbrutus dcopy_list_t *list; 28717169044Sbrutus 28817169044Sbrutus 28917169044Sbrutus /* 29017169044Sbrutus * we don't use the dcopy_list_* code here because we need to due 29117169044Sbrutus * some non-standard stuff. 29217169044Sbrutus */ 29317169044Sbrutus 29417169044Sbrutus list = &dcopy_statep->d_globalchan_list; 29517169044Sbrutus 29617169044Sbrutus /* 29717169044Sbrutus * if nothing is on the channel list, return DCOPY_NORESOURCES. This 29817169044Sbrutus * can happen if there aren't any DMA device registered. 29917169044Sbrutus */ 30017169044Sbrutus mutex_enter(&list->dl_mutex); 30117169044Sbrutus channel = list_head(&list->dl_list); 30217169044Sbrutus if (channel == NULL) { 30317169044Sbrutus mutex_exit(&list->dl_mutex); 30417169044Sbrutus return (DCOPY_NORESOURCES); 30517169044Sbrutus } 30617169044Sbrutus 30717169044Sbrutus /* 30817169044Sbrutus * increment the reference count, and pop the channel off the head and 30917169044Sbrutus * push it on the tail. This ensures we rotate through the channels. 31017169044Sbrutus * DMA channels are shared. 31117169044Sbrutus */ 31217169044Sbrutus channel->ch_ref_cnt++; 31317169044Sbrutus list_remove(&list->dl_list, channel); 31417169044Sbrutus list_insert_tail(&list->dl_list, channel); 31517169044Sbrutus mutex_exit(&list->dl_mutex); 31617169044Sbrutus 31717169044Sbrutus *handle = (dcopy_handle_t)channel; 31817169044Sbrutus return (DCOPY_SUCCESS); 31917169044Sbrutus } 32017169044Sbrutus 32117169044Sbrutus 32217169044Sbrutus /* 32317169044Sbrutus * dcopy_free() 32417169044Sbrutus */ 32517169044Sbrutus void 32617169044Sbrutus dcopy_free(dcopy_handle_t *channel) 32717169044Sbrutus { 32817169044Sbrutus dcopy_device_handle_t device; 32917169044Sbrutus dcopy_list_t *list; 330*b798e010SRichard Lowe boolean_t cleanup = B_FALSE; 33117169044Sbrutus 33217169044Sbrutus 33317169044Sbrutus ASSERT(*channel != NULL); 33417169044Sbrutus 33517169044Sbrutus /* 33617169044Sbrutus * we don't need to add the channel back to the list since we never 33717169044Sbrutus * removed it. decrement the reference count. 33817169044Sbrutus */ 33917169044Sbrutus list = &dcopy_statep->d_globalchan_list; 34017169044Sbrutus mutex_enter(&list->dl_mutex); 34117169044Sbrutus (*channel)->ch_ref_cnt--; 34217169044Sbrutus 34317169044Sbrutus /* 34417169044Sbrutus * if we need to remove this channel, and the reference count is down 34517169044Sbrutus * to 0, decrement the number of channels which still need to be 34617169044Sbrutus * removed on the device. 34717169044Sbrutus */ 34817169044Sbrutus if ((*channel)->ch_removing && ((*channel)->ch_ref_cnt == 0)) { 34917169044Sbrutus device = (*channel)->ch_device; 35017169044Sbrutus mutex_enter(&device->dc_devchan_list.dl_mutex); 35117169044Sbrutus device->dc_removing_cnt--; 35217169044Sbrutus if (device->dc_removing_cnt == 0) { 35317169044Sbrutus cleanup = B_TRUE; 35417169044Sbrutus } 35517169044Sbrutus mutex_exit(&device->dc_devchan_list.dl_mutex); 35617169044Sbrutus } 35717169044Sbrutus mutex_exit(&list->dl_mutex); 35817169044Sbrutus 35917169044Sbrutus /* 36017169044Sbrutus * if there are no channels which still need to be removed, cleanup the 36117169044Sbrutus * device state and call back into the DMA device driver to tell them 36217169044Sbrutus * the device is free. 36317169044Sbrutus */ 36417169044Sbrutus if (cleanup) { 36517169044Sbrutus dcopy_device_cleanup(device, B_TRUE); 36617169044Sbrutus } 36717169044Sbrutus 36817169044Sbrutus *channel = NULL; 36917169044Sbrutus } 37017169044Sbrutus 37117169044Sbrutus 37217169044Sbrutus /* 37317169044Sbrutus * dcopy_query_channel() 37417169044Sbrutus */ 37517169044Sbrutus void 37617169044Sbrutus dcopy_query_channel(dcopy_handle_t channel, dcopy_query_channel_t *query) 37717169044Sbrutus { 37817169044Sbrutus *query = channel->ch_info; 37917169044Sbrutus } 38017169044Sbrutus 38117169044Sbrutus 38217169044Sbrutus /* 38317169044Sbrutus * dcopy_cmd_alloc() 38417169044Sbrutus */ 38517169044Sbrutus int 38617169044Sbrutus dcopy_cmd_alloc(dcopy_handle_t handle, int flags, dcopy_cmd_t *cmd) 38717169044Sbrutus { 38817169044Sbrutus dcopy_handle_t channel; 38917169044Sbrutus dcopy_cmd_priv_t priv; 39017169044Sbrutus int e; 39117169044Sbrutus 39217169044Sbrutus 39317169044Sbrutus channel = handle; 39417169044Sbrutus 39517169044Sbrutus atomic_inc_64(&channel->ch_stat.cs_cmd_alloc.value.ui64); 39617169044Sbrutus e = channel->ch_cb->cb_cmd_alloc(channel->ch_channel_private, flags, 39717169044Sbrutus cmd); 39817169044Sbrutus if (e == DCOPY_SUCCESS) { 39917169044Sbrutus priv = (*cmd)->dp_private; 40017169044Sbrutus priv->pr_channel = channel; 40117169044Sbrutus /* 40217169044Sbrutus * we won't initialize the blocking state until we actually 40317169044Sbrutus * need to block. 40417169044Sbrutus */ 40517169044Sbrutus priv->pr_block_init = B_FALSE; 40617169044Sbrutus } 40717169044Sbrutus 40817169044Sbrutus return (e); 40917169044Sbrutus } 41017169044Sbrutus 41117169044Sbrutus 41217169044Sbrutus /* 41317169044Sbrutus * dcopy_cmd_free() 41417169044Sbrutus */ 41517169044Sbrutus void 41617169044Sbrutus dcopy_cmd_free(dcopy_cmd_t *cmd) 41717169044Sbrutus { 41817169044Sbrutus dcopy_handle_t channel; 41917169044Sbrutus dcopy_cmd_priv_t priv; 42017169044Sbrutus 42117169044Sbrutus 42217169044Sbrutus ASSERT(*cmd != NULL); 42317169044Sbrutus 42417169044Sbrutus priv = (*cmd)->dp_private; 42517169044Sbrutus channel = priv->pr_channel; 42617169044Sbrutus 42717169044Sbrutus /* if we initialized the blocking state, clean it up too */ 42817169044Sbrutus if (priv->pr_block_init) { 42917169044Sbrutus cv_destroy(&priv->pr_cv); 43017169044Sbrutus mutex_destroy(&priv->pr_mutex); 43117169044Sbrutus } 43217169044Sbrutus 43317169044Sbrutus channel->ch_cb->cb_cmd_free(channel->ch_channel_private, cmd); 43417169044Sbrutus } 43517169044Sbrutus 43617169044Sbrutus 43717169044Sbrutus /* 43817169044Sbrutus * dcopy_cmd_post() 43917169044Sbrutus */ 44017169044Sbrutus int 44117169044Sbrutus dcopy_cmd_post(dcopy_cmd_t cmd) 44217169044Sbrutus { 44317169044Sbrutus dcopy_handle_t channel; 44417169044Sbrutus int e; 44517169044Sbrutus 44617169044Sbrutus 44717169044Sbrutus channel = cmd->dp_private->pr_channel; 44817169044Sbrutus 44917169044Sbrutus atomic_inc_64(&channel->ch_stat.cs_cmd_post.value.ui64); 45017169044Sbrutus if (cmd->dp_cmd == DCOPY_CMD_COPY) { 45117169044Sbrutus atomic_add_64(&channel->ch_stat.cs_bytes_xfer.value.ui64, 45217169044Sbrutus cmd->dp.copy.cc_size); 45317169044Sbrutus } 45417169044Sbrutus e = channel->ch_cb->cb_cmd_post(channel->ch_channel_private, cmd); 45517169044Sbrutus if (e != DCOPY_SUCCESS) { 45617169044Sbrutus return (e); 45717169044Sbrutus } 45817169044Sbrutus 45917169044Sbrutus return (DCOPY_SUCCESS); 46017169044Sbrutus } 46117169044Sbrutus 46217169044Sbrutus 46317169044Sbrutus /* 46417169044Sbrutus * dcopy_cmd_poll() 46517169044Sbrutus */ 46617169044Sbrutus int 46717169044Sbrutus dcopy_cmd_poll(dcopy_cmd_t cmd, int flags) 46817169044Sbrutus { 46917169044Sbrutus dcopy_handle_t channel; 47017169044Sbrutus dcopy_cmd_priv_t priv; 47117169044Sbrutus int e; 47217169044Sbrutus 47317169044Sbrutus 47417169044Sbrutus priv = cmd->dp_private; 47517169044Sbrutus channel = priv->pr_channel; 47617169044Sbrutus 47717169044Sbrutus /* 47817169044Sbrutus * if the caller is trying to block, they needed to post the 47917169044Sbrutus * command with DCOPY_CMD_INTR set. 48017169044Sbrutus */ 48117169044Sbrutus if ((flags & DCOPY_POLL_BLOCK) && !(cmd->dp_flags & DCOPY_CMD_INTR)) { 48217169044Sbrutus return (DCOPY_FAILURE); 48317169044Sbrutus } 48417169044Sbrutus 48517169044Sbrutus atomic_inc_64(&channel->ch_stat.cs_cmd_poll.value.ui64); 48617169044Sbrutus 48717169044Sbrutus repoll: 48817169044Sbrutus e = channel->ch_cb->cb_cmd_poll(channel->ch_channel_private, cmd); 48917169044Sbrutus if (e == DCOPY_PENDING) { 49017169044Sbrutus /* 49117169044Sbrutus * if the command is still active, and the blocking flag 49217169044Sbrutus * is set. 49317169044Sbrutus */ 49417169044Sbrutus if (flags & DCOPY_POLL_BLOCK) { 49517169044Sbrutus 49617169044Sbrutus /* 49717169044Sbrutus * if we haven't initialized the state, do it now. A 49817169044Sbrutus * command can be re-used, so it's possible it's 49917169044Sbrutus * already been initialized. 50017169044Sbrutus */ 50117169044Sbrutus if (!priv->pr_block_init) { 50217169044Sbrutus priv->pr_block_init = B_TRUE; 50317169044Sbrutus mutex_init(&priv->pr_mutex, NULL, MUTEX_DRIVER, 50417169044Sbrutus NULL); 50517169044Sbrutus cv_init(&priv->pr_cv, NULL, CV_DRIVER, NULL); 50617169044Sbrutus priv->pr_cmd = cmd; 50717169044Sbrutus } 50817169044Sbrutus 50917169044Sbrutus /* push it on the list for blocking commands */ 51017169044Sbrutus priv->pr_wait = B_TRUE; 51117169044Sbrutus dcopy_list_push(&channel->ch_poll_list, priv); 51217169044Sbrutus 51317169044Sbrutus mutex_enter(&priv->pr_mutex); 51417169044Sbrutus /* 51517169044Sbrutus * it's possible we already cleared pr_wait before we 51617169044Sbrutus * grabbed the mutex. 51717169044Sbrutus */ 51817169044Sbrutus if (priv->pr_wait) { 51917169044Sbrutus cv_wait(&priv->pr_cv, &priv->pr_mutex); 52017169044Sbrutus } 52117169044Sbrutus mutex_exit(&priv->pr_mutex); 52217169044Sbrutus 52317169044Sbrutus /* 52417169044Sbrutus * the command has completed, go back and poll so we 52517169044Sbrutus * get the status. 52617169044Sbrutus */ 52717169044Sbrutus goto repoll; 52817169044Sbrutus } 52917169044Sbrutus } 53017169044Sbrutus 53117169044Sbrutus return (e); 53217169044Sbrutus } 53317169044Sbrutus 53417169044Sbrutus /* *** END OF EXTERNAL INTERFACE *** */ 53517169044Sbrutus 53617169044Sbrutus /* 53717169044Sbrutus * dcopy_list_init() 53817169044Sbrutus */ 53917169044Sbrutus static int 54017169044Sbrutus dcopy_list_init(dcopy_list_t *list, size_t node_size, offset_t link_offset) 54117169044Sbrutus { 54217169044Sbrutus mutex_init(&list->dl_mutex, NULL, MUTEX_DRIVER, NULL); 54317169044Sbrutus list_create(&list->dl_list, node_size, link_offset); 54417169044Sbrutus list->dl_cnt = 0; 54517169044Sbrutus 54617169044Sbrutus return (DCOPY_SUCCESS); 54717169044Sbrutus } 54817169044Sbrutus 54917169044Sbrutus 55017169044Sbrutus /* 55117169044Sbrutus * dcopy_list_fini() 55217169044Sbrutus */ 55317169044Sbrutus static void 55417169044Sbrutus dcopy_list_fini(dcopy_list_t *list) 55517169044Sbrutus { 55617169044Sbrutus list_destroy(&list->dl_list); 55717169044Sbrutus mutex_destroy(&list->dl_mutex); 55817169044Sbrutus } 55917169044Sbrutus 56017169044Sbrutus 56117169044Sbrutus /* 56217169044Sbrutus * dcopy_list_push() 56317169044Sbrutus */ 56417169044Sbrutus static void 56517169044Sbrutus dcopy_list_push(dcopy_list_t *list, void *list_node) 56617169044Sbrutus { 56717169044Sbrutus mutex_enter(&list->dl_mutex); 56817169044Sbrutus list_insert_tail(&list->dl_list, list_node); 56917169044Sbrutus list->dl_cnt++; 57017169044Sbrutus mutex_exit(&list->dl_mutex); 57117169044Sbrutus } 57217169044Sbrutus 57317169044Sbrutus 57417169044Sbrutus /* 57517169044Sbrutus * dcopy_list_pop() 57617169044Sbrutus */ 57717169044Sbrutus static void * 57817169044Sbrutus dcopy_list_pop(dcopy_list_t *list) 57917169044Sbrutus { 58017169044Sbrutus list_node_t *list_node; 58117169044Sbrutus 58217169044Sbrutus mutex_enter(&list->dl_mutex); 58317169044Sbrutus list_node = list_head(&list->dl_list); 58417169044Sbrutus if (list_node == NULL) { 58517169044Sbrutus mutex_exit(&list->dl_mutex); 58617169044Sbrutus return (list_node); 58717169044Sbrutus } 58817169044Sbrutus list->dl_cnt--; 58917169044Sbrutus list_remove(&list->dl_list, list_node); 59017169044Sbrutus mutex_exit(&list->dl_mutex); 59117169044Sbrutus 59217169044Sbrutus return (list_node); 59317169044Sbrutus } 59417169044Sbrutus 59517169044Sbrutus 59617169044Sbrutus /* *** DEVICE INTERFACE *** */ 59717169044Sbrutus /* 59817169044Sbrutus * dcopy_device_register() 59917169044Sbrutus */ 60017169044Sbrutus int 60117169044Sbrutus dcopy_device_register(void *device_private, dcopy_device_info_t *info, 60217169044Sbrutus dcopy_device_handle_t *handle) 60317169044Sbrutus { 60417169044Sbrutus struct dcopy_channel_s *channel; 60517169044Sbrutus struct dcopy_device_s *device; 60617169044Sbrutus int e; 60717169044Sbrutus int i; 60817169044Sbrutus 60917169044Sbrutus 61017169044Sbrutus /* initialize the per device state */ 61117169044Sbrutus device = kmem_zalloc(sizeof (*device), KM_SLEEP); 61217169044Sbrutus device->dc_device_private = device_private; 61317169044Sbrutus device->dc_info = *info; 61417169044Sbrutus device->dc_removing_cnt = 0; 61517169044Sbrutus device->dc_cb = info->di_cb; 61617169044Sbrutus 61717169044Sbrutus /* 61817169044Sbrutus * we have a per device channel list so we can remove a device in the 61917169044Sbrutus * future. 62017169044Sbrutus */ 62117169044Sbrutus e = dcopy_list_init(&device->dc_devchan_list, 62217169044Sbrutus sizeof (struct dcopy_channel_s), 62317169044Sbrutus offsetof(struct dcopy_channel_s, ch_devchan_list_node)); 62417169044Sbrutus if (e != DCOPY_SUCCESS) { 62517169044Sbrutus goto registerfail_devchan; 62617169044Sbrutus } 62717169044Sbrutus 62817169044Sbrutus /* 62917169044Sbrutus * allocate state for each channel, allocate the channel, and then add 63017169044Sbrutus * the devices dma channels to the devices channel list. 63117169044Sbrutus */ 63217169044Sbrutus for (i = 0; i < info->di_num_dma; i++) { 63317169044Sbrutus channel = kmem_zalloc(sizeof (*channel), KM_SLEEP); 63417169044Sbrutus channel->ch_device = device; 63517169044Sbrutus channel->ch_removing = B_FALSE; 63617169044Sbrutus channel->ch_ref_cnt = 0; 63717169044Sbrutus channel->ch_cb = info->di_cb; 63817169044Sbrutus 63917169044Sbrutus e = info->di_cb->cb_channel_alloc(device_private, channel, 64017169044Sbrutus DCOPY_SLEEP, dcopy_channel_size, &channel->ch_info, 64117169044Sbrutus &channel->ch_channel_private); 64217169044Sbrutus if (e != DCOPY_SUCCESS) { 64317169044Sbrutus kmem_free(channel, sizeof (*channel)); 64417169044Sbrutus goto registerfail_alloc; 64517169044Sbrutus } 64617169044Sbrutus 64717169044Sbrutus e = dcopy_stats_init(channel); 64817169044Sbrutus if (e != DCOPY_SUCCESS) { 64917169044Sbrutus info->di_cb->cb_channel_free( 65017169044Sbrutus &channel->ch_channel_private); 65117169044Sbrutus kmem_free(channel, sizeof (*channel)); 65217169044Sbrutus goto registerfail_alloc; 65317169044Sbrutus } 65417169044Sbrutus 65517169044Sbrutus e = dcopy_list_init(&channel->ch_poll_list, 65617169044Sbrutus sizeof (struct dcopy_cmd_priv_s), 65717169044Sbrutus offsetof(struct dcopy_cmd_priv_s, pr_poll_list_node)); 65817169044Sbrutus if (e != DCOPY_SUCCESS) { 65917169044Sbrutus dcopy_stats_fini(channel); 66017169044Sbrutus info->di_cb->cb_channel_free( 66117169044Sbrutus &channel->ch_channel_private); 66217169044Sbrutus kmem_free(channel, sizeof (*channel)); 66317169044Sbrutus goto registerfail_alloc; 66417169044Sbrutus } 66517169044Sbrutus 66617169044Sbrutus dcopy_list_push(&device->dc_devchan_list, channel); 66717169044Sbrutus } 66817169044Sbrutus 66917169044Sbrutus /* add the device to device list */ 67017169044Sbrutus dcopy_list_push(&dcopy_statep->d_device_list, device); 67117169044Sbrutus 67217169044Sbrutus /* 67317169044Sbrutus * add the device's dma channels to the global channel list (where 67417169044Sbrutus * dcopy_alloc's come from) 67517169044Sbrutus */ 67617169044Sbrutus mutex_enter(&dcopy_statep->d_globalchan_list.dl_mutex); 67717169044Sbrutus mutex_enter(&dcopy_statep->d_device_list.dl_mutex); 67817169044Sbrutus channel = list_head(&device->dc_devchan_list.dl_list); 67917169044Sbrutus while (channel != NULL) { 68017169044Sbrutus list_insert_tail(&dcopy_statep->d_globalchan_list.dl_list, 68117169044Sbrutus channel); 68217169044Sbrutus dcopy_statep->d_globalchan_list.dl_cnt++; 68317169044Sbrutus channel = list_next(&device->dc_devchan_list.dl_list, channel); 68417169044Sbrutus } 68517169044Sbrutus mutex_exit(&dcopy_statep->d_device_list.dl_mutex); 68617169044Sbrutus mutex_exit(&dcopy_statep->d_globalchan_list.dl_mutex); 68717169044Sbrutus 68817169044Sbrutus *handle = device; 68917169044Sbrutus 69017169044Sbrutus /* last call-back into kernel for dcopy KAPI enabled */ 69117169044Sbrutus uioa_dcopy_enable(); 69217169044Sbrutus 69317169044Sbrutus return (DCOPY_SUCCESS); 69417169044Sbrutus 69517169044Sbrutus registerfail_alloc: 69617169044Sbrutus channel = list_head(&device->dc_devchan_list.dl_list); 69717169044Sbrutus while (channel != NULL) { 69817169044Sbrutus /* remove from the list */ 69917169044Sbrutus channel = dcopy_list_pop(&device->dc_devchan_list); 70017169044Sbrutus ASSERT(channel != NULL); 70117169044Sbrutus 70217169044Sbrutus dcopy_list_fini(&channel->ch_poll_list); 70317169044Sbrutus dcopy_stats_fini(channel); 70417169044Sbrutus info->di_cb->cb_channel_free(&channel->ch_channel_private); 70517169044Sbrutus kmem_free(channel, sizeof (*channel)); 70617169044Sbrutus } 70717169044Sbrutus 70817169044Sbrutus dcopy_list_fini(&device->dc_devchan_list); 70917169044Sbrutus registerfail_devchan: 71017169044Sbrutus kmem_free(device, sizeof (*device)); 71117169044Sbrutus 71217169044Sbrutus return (DCOPY_FAILURE); 71317169044Sbrutus } 71417169044Sbrutus 71517169044Sbrutus 71617169044Sbrutus /* 71717169044Sbrutus * dcopy_device_unregister() 71817169044Sbrutus */ 71917169044Sbrutus /*ARGSUSED*/ 72017169044Sbrutus int 72117169044Sbrutus dcopy_device_unregister(dcopy_device_handle_t *handle) 72217169044Sbrutus { 72317169044Sbrutus struct dcopy_channel_s *channel; 72417169044Sbrutus dcopy_device_handle_t device; 72517169044Sbrutus boolean_t device_busy; 72617169044Sbrutus 72717169044Sbrutus /* first call-back into kernel for dcopy KAPI disable */ 72817169044Sbrutus uioa_dcopy_disable(); 72917169044Sbrutus 73017169044Sbrutus device = *handle; 73117169044Sbrutus device_busy = B_FALSE; 73217169044Sbrutus 73317169044Sbrutus /* 73417169044Sbrutus * remove the devices dma channels from the global channel list (where 73517169044Sbrutus * dcopy_alloc's come from) 73617169044Sbrutus */ 73717169044Sbrutus mutex_enter(&dcopy_statep->d_globalchan_list.dl_mutex); 73817169044Sbrutus mutex_enter(&device->dc_devchan_list.dl_mutex); 73917169044Sbrutus channel = list_head(&device->dc_devchan_list.dl_list); 74017169044Sbrutus while (channel != NULL) { 74117169044Sbrutus /* 74217169044Sbrutus * if the channel has outstanding allocs, mark it as having 74317169044Sbrutus * to be removed and increment the number of channels which 74417169044Sbrutus * need to be removed in the device state too. 74517169044Sbrutus */ 74617169044Sbrutus if (channel->ch_ref_cnt != 0) { 74717169044Sbrutus channel->ch_removing = B_TRUE; 74817169044Sbrutus device_busy = B_TRUE; 74917169044Sbrutus device->dc_removing_cnt++; 75017169044Sbrutus } 75117169044Sbrutus dcopy_statep->d_globalchan_list.dl_cnt--; 75217169044Sbrutus list_remove(&dcopy_statep->d_globalchan_list.dl_list, channel); 75317169044Sbrutus channel = list_next(&device->dc_devchan_list.dl_list, channel); 75417169044Sbrutus } 75517169044Sbrutus mutex_exit(&device->dc_devchan_list.dl_mutex); 75617169044Sbrutus mutex_exit(&dcopy_statep->d_globalchan_list.dl_mutex); 75717169044Sbrutus 75817169044Sbrutus /* 75917169044Sbrutus * if there are channels which still need to be removed, we will clean 76017169044Sbrutus * up the device state after they are freed up. 76117169044Sbrutus */ 76217169044Sbrutus if (device_busy) { 76317169044Sbrutus return (DCOPY_PENDING); 76417169044Sbrutus } 76517169044Sbrutus 76617169044Sbrutus dcopy_device_cleanup(device, B_FALSE); 76717169044Sbrutus 76817169044Sbrutus *handle = NULL; 76917169044Sbrutus return (DCOPY_SUCCESS); 77017169044Sbrutus } 77117169044Sbrutus 77217169044Sbrutus 77317169044Sbrutus /* 77417169044Sbrutus * dcopy_device_cleanup() 77517169044Sbrutus */ 77617169044Sbrutus static void 77717169044Sbrutus dcopy_device_cleanup(dcopy_device_handle_t device, boolean_t do_callback) 77817169044Sbrutus { 77917169044Sbrutus struct dcopy_channel_s *channel; 78017169044Sbrutus 78117169044Sbrutus /* 78217169044Sbrutus * remove all the channels in the device list, free them, and clean up 78317169044Sbrutus * the state. 78417169044Sbrutus */ 78517169044Sbrutus mutex_enter(&dcopy_statep->d_device_list.dl_mutex); 78617169044Sbrutus channel = list_head(&device->dc_devchan_list.dl_list); 78717169044Sbrutus while (channel != NULL) { 78817169044Sbrutus device->dc_devchan_list.dl_cnt--; 78917169044Sbrutus list_remove(&device->dc_devchan_list.dl_list, channel); 79017169044Sbrutus dcopy_list_fini(&channel->ch_poll_list); 79117169044Sbrutus dcopy_stats_fini(channel); 79217169044Sbrutus channel->ch_cb->cb_channel_free(&channel->ch_channel_private); 79317169044Sbrutus kmem_free(channel, sizeof (*channel)); 79417169044Sbrutus channel = list_head(&device->dc_devchan_list.dl_list); 79517169044Sbrutus } 79617169044Sbrutus 79717169044Sbrutus /* remove it from the list of devices */ 79817169044Sbrutus list_remove(&dcopy_statep->d_device_list.dl_list, device); 79917169044Sbrutus 80017169044Sbrutus mutex_exit(&dcopy_statep->d_device_list.dl_mutex); 80117169044Sbrutus 80217169044Sbrutus /* 80317169044Sbrutus * notify the DMA device driver that the device is free to be 80417169044Sbrutus * detached. 80517169044Sbrutus */ 80617169044Sbrutus if (do_callback) { 80717169044Sbrutus device->dc_cb->cb_unregister_complete( 80817169044Sbrutus device->dc_device_private, DCOPY_SUCCESS); 80917169044Sbrutus } 81017169044Sbrutus 81117169044Sbrutus dcopy_list_fini(&device->dc_devchan_list); 81217169044Sbrutus kmem_free(device, sizeof (*device)); 81317169044Sbrutus } 81417169044Sbrutus 81517169044Sbrutus 81617169044Sbrutus /* 81717169044Sbrutus * dcopy_device_channel_notify() 81817169044Sbrutus */ 81917169044Sbrutus /*ARGSUSED*/ 82017169044Sbrutus void 82117169044Sbrutus dcopy_device_channel_notify(dcopy_handle_t handle, int status) 82217169044Sbrutus { 82317169044Sbrutus struct dcopy_channel_s *channel; 82417169044Sbrutus dcopy_list_t *poll_list; 82517169044Sbrutus dcopy_cmd_priv_t priv; 82617169044Sbrutus int e; 82717169044Sbrutus 82817169044Sbrutus 82917169044Sbrutus ASSERT(status == DCOPY_COMPLETION); 83017169044Sbrutus channel = handle; 83117169044Sbrutus 83217169044Sbrutus poll_list = &channel->ch_poll_list; 83317169044Sbrutus 83417169044Sbrutus /* 83517169044Sbrutus * when we get a completion notification from the device, go through 83617169044Sbrutus * all of the commands blocking on this channel and see if they have 83717169044Sbrutus * completed. Remove the command and wake up the block thread if they 83817169044Sbrutus * have. Once we hit a command which is still pending, we are done 83917169044Sbrutus * polling since commands in a channel complete in order. 84017169044Sbrutus */ 84117169044Sbrutus mutex_enter(&poll_list->dl_mutex); 84217169044Sbrutus if (poll_list->dl_cnt != 0) { 84317169044Sbrutus priv = list_head(&poll_list->dl_list); 84417169044Sbrutus while (priv != NULL) { 84517169044Sbrutus atomic_inc_64(&channel-> 84617169044Sbrutus ch_stat.cs_notify_poll.value.ui64); 84717169044Sbrutus e = channel->ch_cb->cb_cmd_poll( 84817169044Sbrutus channel->ch_channel_private, 84917169044Sbrutus priv->pr_cmd); 85017169044Sbrutus if (e == DCOPY_PENDING) { 85117169044Sbrutus atomic_inc_64(&channel-> 85217169044Sbrutus ch_stat.cs_notify_pending.value.ui64); 85317169044Sbrutus break; 85417169044Sbrutus } 85517169044Sbrutus 85617169044Sbrutus poll_list->dl_cnt--; 85717169044Sbrutus list_remove(&poll_list->dl_list, priv); 85817169044Sbrutus 85917169044Sbrutus mutex_enter(&priv->pr_mutex); 86017169044Sbrutus priv->pr_wait = B_FALSE; 86117169044Sbrutus cv_signal(&priv->pr_cv); 86217169044Sbrutus mutex_exit(&priv->pr_mutex); 86317169044Sbrutus 86417169044Sbrutus priv = list_head(&poll_list->dl_list); 86517169044Sbrutus } 86617169044Sbrutus } 86717169044Sbrutus 86817169044Sbrutus mutex_exit(&poll_list->dl_mutex); 86917169044Sbrutus } 87017169044Sbrutus 87117169044Sbrutus 87217169044Sbrutus /* 87317169044Sbrutus * dcopy_stats_init() 87417169044Sbrutus */ 87517169044Sbrutus static int 87617169044Sbrutus dcopy_stats_init(dcopy_handle_t channel) 87717169044Sbrutus { 87817169044Sbrutus #define CHANSTRSIZE 20 87917169044Sbrutus char chanstr[CHANSTRSIZE]; 88017169044Sbrutus dcopy_stats_t *stats; 88117169044Sbrutus int instance; 88217169044Sbrutus char *name; 88317169044Sbrutus 88417169044Sbrutus 88517169044Sbrutus stats = &channel->ch_stat; 88617169044Sbrutus name = (char *)ddi_driver_name(channel->ch_device->dc_info.di_dip); 88717169044Sbrutus instance = ddi_get_instance(channel->ch_device->dc_info.di_dip); 88817169044Sbrutus 88917169044Sbrutus (void) snprintf(chanstr, CHANSTRSIZE, "channel%d", 89017169044Sbrutus (uint32_t)channel->ch_info.qc_chan_num); 89117169044Sbrutus 89217169044Sbrutus channel->ch_kstat = kstat_create(name, instance, chanstr, "misc", 89317169044Sbrutus KSTAT_TYPE_NAMED, sizeof (dcopy_stats_t) / sizeof (kstat_named_t), 89417169044Sbrutus KSTAT_FLAG_VIRTUAL); 89517169044Sbrutus if (channel->ch_kstat == NULL) { 89617169044Sbrutus return (DCOPY_FAILURE); 89717169044Sbrutus } 89817169044Sbrutus channel->ch_kstat->ks_data = stats; 89917169044Sbrutus 90017169044Sbrutus kstat_named_init(&stats->cs_bytes_xfer, "bytes_xfer", 90117169044Sbrutus KSTAT_DATA_UINT64); 90217169044Sbrutus kstat_named_init(&stats->cs_cmd_alloc, "cmd_alloc", 90317169044Sbrutus KSTAT_DATA_UINT64); 90417169044Sbrutus kstat_named_init(&stats->cs_cmd_post, "cmd_post", 90517169044Sbrutus KSTAT_DATA_UINT64); 90617169044Sbrutus kstat_named_init(&stats->cs_cmd_poll, "cmd_poll", 90717169044Sbrutus KSTAT_DATA_UINT64); 90817169044Sbrutus kstat_named_init(&stats->cs_notify_poll, "notify_poll", 90917169044Sbrutus KSTAT_DATA_UINT64); 91017169044Sbrutus kstat_named_init(&stats->cs_notify_pending, "notify_pending", 91117169044Sbrutus KSTAT_DATA_UINT64); 91217169044Sbrutus kstat_named_init(&stats->cs_id, "id", 91317169044Sbrutus KSTAT_DATA_UINT64); 91417169044Sbrutus kstat_named_init(&stats->cs_capabilities, "capabilities", 91517169044Sbrutus KSTAT_DATA_UINT64); 91617169044Sbrutus 91717169044Sbrutus kstat_install(channel->ch_kstat); 91817169044Sbrutus 91917169044Sbrutus channel->ch_stat.cs_id.value.ui64 = channel->ch_info.qc_id; 92017169044Sbrutus channel->ch_stat.cs_capabilities.value.ui64 = 92117169044Sbrutus channel->ch_info.qc_capabilities; 92217169044Sbrutus 92317169044Sbrutus return (DCOPY_SUCCESS); 92417169044Sbrutus } 92517169044Sbrutus 92617169044Sbrutus 92717169044Sbrutus /* 92817169044Sbrutus * dcopy_stats_fini() 92917169044Sbrutus */ 93017169044Sbrutus static void 93117169044Sbrutus dcopy_stats_fini(dcopy_handle_t channel) 93217169044Sbrutus { 93317169044Sbrutus kstat_delete(channel->ch_kstat); 93417169044Sbrutus } 93517169044Sbrutus /* *** END OF DEVICE INTERFACE *** */ 936