/* * 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 #include #include #include #include #include #include #include #include /* 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; /* last call-back into kernel for dcopy KAPI enabled */ uioa_dcopy_enable(); 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; /* first call-back into kernel for dcopy KAPI disable */ uioa_dcopy_disable(); 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 *** */