1*6707Sbrutus /*
2*6707Sbrutus * CDDL HEADER START
3*6707Sbrutus *
4*6707Sbrutus * The contents of this file are subject to the terms of the
5*6707Sbrutus * Common Development and Distribution License (the "License").
6*6707Sbrutus * You may not use this file except in compliance with the License.
7*6707Sbrutus *
8*6707Sbrutus * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9*6707Sbrutus * or http://www.opensolaris.org/os/licensing.
10*6707Sbrutus * See the License for the specific language governing permissions
11*6707Sbrutus * and limitations under the License.
12*6707Sbrutus *
13*6707Sbrutus * When distributing Covered Code, include this CDDL HEADER in each
14*6707Sbrutus * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15*6707Sbrutus * If applicable, add the following below this CDDL HEADER, with the
16*6707Sbrutus * fields enclosed by brackets "[]" replaced with your own identifying
17*6707Sbrutus * information: Portions Copyright [yyyy] [name of copyright owner]
18*6707Sbrutus *
19*6707Sbrutus * CDDL HEADER END
20*6707Sbrutus */
21*6707Sbrutus
22*6707Sbrutus /*
23*6707Sbrutus * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24*6707Sbrutus * Use is subject to license terms.
25*6707Sbrutus */
26*6707Sbrutus
27*6707Sbrutus #pragma ident "%Z%%M% %I% %E% SMI"
28*6707Sbrutus
29*6707Sbrutus /*
30*6707Sbrutus * dcopy.c
31*6707Sbrutus * dcopy misc module
32*6707Sbrutus */
33*6707Sbrutus
34*6707Sbrutus #include <sys/conf.h>
35*6707Sbrutus #include <sys/kmem.h>
36*6707Sbrutus #include <sys/ddi.h>
37*6707Sbrutus #include <sys/sunddi.h>
38*6707Sbrutus #include <sys/modctl.h>
39*6707Sbrutus #include <sys/sysmacros.h>
40*6707Sbrutus #include <sys/atomic.h>
41*6707Sbrutus
42*6707Sbrutus
43*6707Sbrutus #include <sys/dcopy.h>
44*6707Sbrutus #include <sys/dcopy_device.h>
45*6707Sbrutus
46*6707Sbrutus
47*6707Sbrutus /* Number of entries per channel to allocate */
48*6707Sbrutus uint_t dcopy_channel_size = 1024;
49*6707Sbrutus
50*6707Sbrutus
51*6707Sbrutus typedef struct dcopy_list_s {
52*6707Sbrutus list_t dl_list;
53*6707Sbrutus kmutex_t dl_mutex;
54*6707Sbrutus uint_t dl_cnt; /* num entries on list */
55*6707Sbrutus } dcopy_list_t;
56*6707Sbrutus
57*6707Sbrutus /* device state for register/unregister */
58*6707Sbrutus struct dcopy_device_s {
59*6707Sbrutus /* DMA device drivers private pointer */
60*6707Sbrutus void *dc_device_private;
61*6707Sbrutus
62*6707Sbrutus /* to track list of channels from this DMA device */
63*6707Sbrutus dcopy_list_t dc_devchan_list;
64*6707Sbrutus list_node_t dc_device_list_node;
65*6707Sbrutus
66*6707Sbrutus /*
67*6707Sbrutus * dc_removing_cnt track how many channels still have to be freed up
68*6707Sbrutus * before it's safe to allow the DMA device driver to detach.
69*6707Sbrutus */
70*6707Sbrutus uint_t dc_removing_cnt;
71*6707Sbrutus dcopy_device_cb_t *dc_cb;
72*6707Sbrutus
73*6707Sbrutus dcopy_device_info_t dc_info;
74*6707Sbrutus
75*6707Sbrutus };
76*6707Sbrutus
77*6707Sbrutus typedef struct dcopy_stats_s {
78*6707Sbrutus kstat_named_t cs_bytes_xfer;
79*6707Sbrutus kstat_named_t cs_cmd_alloc;
80*6707Sbrutus kstat_named_t cs_cmd_post;
81*6707Sbrutus kstat_named_t cs_cmd_poll;
82*6707Sbrutus kstat_named_t cs_notify_poll;
83*6707Sbrutus kstat_named_t cs_notify_pending;
84*6707Sbrutus kstat_named_t cs_id;
85*6707Sbrutus kstat_named_t cs_capabilities;
86*6707Sbrutus } dcopy_stats_t;
87*6707Sbrutus
88*6707Sbrutus /* DMA channel state */
89*6707Sbrutus struct dcopy_channel_s {
90*6707Sbrutus /* DMA driver channel private pointer */
91*6707Sbrutus void *ch_channel_private;
92*6707Sbrutus
93*6707Sbrutus /* shortcut to device callbacks */
94*6707Sbrutus dcopy_device_cb_t *ch_cb;
95*6707Sbrutus
96*6707Sbrutus /*
97*6707Sbrutus * number of outstanding allocs for this channel. used to track when
98*6707Sbrutus * it's safe to free up this channel so the DMA device driver can
99*6707Sbrutus * detach.
100*6707Sbrutus */
101*6707Sbrutus uint64_t ch_ref_cnt;
102*6707Sbrutus
103*6707Sbrutus /* state for if channel needs to be removed when ch_ref_cnt gets to 0 */
104*6707Sbrutus boolean_t ch_removing;
105*6707Sbrutus
106*6707Sbrutus list_node_t ch_devchan_list_node;
107*6707Sbrutus list_node_t ch_globalchan_list_node;
108*6707Sbrutus
109*6707Sbrutus /*
110*6707Sbrutus * per channel list of commands actively blocking waiting for
111*6707Sbrutus * completion.
112*6707Sbrutus */
113*6707Sbrutus dcopy_list_t ch_poll_list;
114*6707Sbrutus
115*6707Sbrutus /* pointer back to our device */
116*6707Sbrutus struct dcopy_device_s *ch_device;
117*6707Sbrutus
118*6707Sbrutus dcopy_query_channel_t ch_info;
119*6707Sbrutus
120*6707Sbrutus kstat_t *ch_kstat;
121*6707Sbrutus dcopy_stats_t ch_stat;
122*6707Sbrutus };
123*6707Sbrutus
124*6707Sbrutus /*
125*6707Sbrutus * If grabbing both device_list mutex & globalchan_list mutex,
126*6707Sbrutus * Always grab globalchan_list mutex before device_list mutex
127*6707Sbrutus */
128*6707Sbrutus typedef struct dcopy_state_s {
129*6707Sbrutus dcopy_list_t d_device_list;
130*6707Sbrutus dcopy_list_t d_globalchan_list;
131*6707Sbrutus } dcopy_state_t;
132*6707Sbrutus dcopy_state_t *dcopy_statep;
133*6707Sbrutus
134*6707Sbrutus
135*6707Sbrutus /* Module Driver Info */
136*6707Sbrutus static struct modlmisc dcopy_modlmisc = {
137*6707Sbrutus &mod_miscops,
138*6707Sbrutus "dcopy kernel module"
139*6707Sbrutus };
140*6707Sbrutus
141*6707Sbrutus /* Module Linkage */
142*6707Sbrutus static struct modlinkage dcopy_modlinkage = {
143*6707Sbrutus MODREV_1,
144*6707Sbrutus &dcopy_modlmisc,
145*6707Sbrutus NULL
146*6707Sbrutus };
147*6707Sbrutus
148*6707Sbrutus static int dcopy_init();
149*6707Sbrutus static void dcopy_fini();
150*6707Sbrutus
151*6707Sbrutus static int dcopy_list_init(dcopy_list_t *list, size_t node_size,
152*6707Sbrutus offset_t link_offset);
153*6707Sbrutus static void dcopy_list_fini(dcopy_list_t *list);
154*6707Sbrutus static void dcopy_list_push(dcopy_list_t *list, void *list_node);
155*6707Sbrutus static void *dcopy_list_pop(dcopy_list_t *list);
156*6707Sbrutus
157*6707Sbrutus static void dcopy_device_cleanup(dcopy_device_handle_t device,
158*6707Sbrutus boolean_t do_callback);
159*6707Sbrutus
160*6707Sbrutus static int dcopy_stats_init(dcopy_handle_t channel);
161*6707Sbrutus static void dcopy_stats_fini(dcopy_handle_t channel);
162*6707Sbrutus
163*6707Sbrutus
164*6707Sbrutus /*
165*6707Sbrutus * _init()
166*6707Sbrutus */
167*6707Sbrutus int
_init()168*6707Sbrutus _init()
169*6707Sbrutus {
170*6707Sbrutus int e;
171*6707Sbrutus
172*6707Sbrutus e = dcopy_init();
173*6707Sbrutus if (e != 0) {
174*6707Sbrutus return (e);
175*6707Sbrutus }
176*6707Sbrutus
177*6707Sbrutus return (mod_install(&dcopy_modlinkage));
178*6707Sbrutus }
179*6707Sbrutus
180*6707Sbrutus
181*6707Sbrutus /*
182*6707Sbrutus * _info()
183*6707Sbrutus */
184*6707Sbrutus int
_info(struct modinfo * modinfop)185*6707Sbrutus _info(struct modinfo *modinfop)
186*6707Sbrutus {
187*6707Sbrutus return (mod_info(&dcopy_modlinkage, modinfop));
188*6707Sbrutus }
189*6707Sbrutus
190*6707Sbrutus
191*6707Sbrutus /*
192*6707Sbrutus * _fini()
193*6707Sbrutus */
194*6707Sbrutus int
_fini()195*6707Sbrutus _fini()
196*6707Sbrutus {
197*6707Sbrutus int e;
198*6707Sbrutus
199*6707Sbrutus e = mod_remove(&dcopy_modlinkage);
200*6707Sbrutus if (e != 0) {
201*6707Sbrutus return (e);
202*6707Sbrutus }
203*6707Sbrutus
204*6707Sbrutus dcopy_fini();
205*6707Sbrutus
206*6707Sbrutus return (e);
207*6707Sbrutus }
208*6707Sbrutus
209*6707Sbrutus /*
210*6707Sbrutus * dcopy_init()
211*6707Sbrutus */
212*6707Sbrutus static int
dcopy_init()213*6707Sbrutus dcopy_init()
214*6707Sbrutus {
215*6707Sbrutus int e;
216*6707Sbrutus
217*6707Sbrutus
218*6707Sbrutus dcopy_statep = kmem_zalloc(sizeof (*dcopy_statep), KM_SLEEP);
219*6707Sbrutus
220*6707Sbrutus /* Initialize the list we use to track device register/unregister */
221*6707Sbrutus e = dcopy_list_init(&dcopy_statep->d_device_list,
222*6707Sbrutus sizeof (struct dcopy_device_s),
223*6707Sbrutus offsetof(struct dcopy_device_s, dc_device_list_node));
224*6707Sbrutus if (e != DCOPY_SUCCESS) {
225*6707Sbrutus goto dcopyinitfail_device;
226*6707Sbrutus }
227*6707Sbrutus
228*6707Sbrutus /* Initialize the list we use to track all DMA channels */
229*6707Sbrutus e = dcopy_list_init(&dcopy_statep->d_globalchan_list,
230*6707Sbrutus sizeof (struct dcopy_channel_s),
231*6707Sbrutus offsetof(struct dcopy_channel_s, ch_globalchan_list_node));
232*6707Sbrutus if (e != DCOPY_SUCCESS) {
233*6707Sbrutus goto dcopyinitfail_global;
234*6707Sbrutus }
235*6707Sbrutus
236*6707Sbrutus return (0);
237*6707Sbrutus
238*6707Sbrutus dcopyinitfail_cback:
239*6707Sbrutus dcopy_list_fini(&dcopy_statep->d_globalchan_list);
240*6707Sbrutus dcopyinitfail_global:
241*6707Sbrutus dcopy_list_fini(&dcopy_statep->d_device_list);
242*6707Sbrutus dcopyinitfail_device:
243*6707Sbrutus kmem_free(dcopy_statep, sizeof (*dcopy_statep));
244*6707Sbrutus
245*6707Sbrutus return (-1);
246*6707Sbrutus }
247*6707Sbrutus
248*6707Sbrutus
249*6707Sbrutus /*
250*6707Sbrutus * dcopy_fini()
251*6707Sbrutus */
252*6707Sbrutus static void
dcopy_fini()253*6707Sbrutus dcopy_fini()
254*6707Sbrutus {
255*6707Sbrutus /*
256*6707Sbrutus * if mod_remove was successfull, we shouldn't have any
257*6707Sbrutus * devices/channels to worry about.
258*6707Sbrutus */
259*6707Sbrutus ASSERT(list_head(&dcopy_statep->d_globalchan_list.dl_list) == NULL);
260*6707Sbrutus ASSERT(list_head(&dcopy_statep->d_device_list.dl_list) == NULL);
261*6707Sbrutus
262*6707Sbrutus dcopy_list_fini(&dcopy_statep->d_globalchan_list);
263*6707Sbrutus dcopy_list_fini(&dcopy_statep->d_device_list);
264*6707Sbrutus kmem_free(dcopy_statep, sizeof (*dcopy_statep));
265*6707Sbrutus }
266*6707Sbrutus
267*6707Sbrutus
268*6707Sbrutus /* *** EXTERNAL INTERFACE *** */
269*6707Sbrutus /*
270*6707Sbrutus * dcopy_query()
271*6707Sbrutus */
272*6707Sbrutus void
dcopy_query(dcopy_query_t * query)273*6707Sbrutus dcopy_query(dcopy_query_t *query)
274*6707Sbrutus {
275*6707Sbrutus query->dq_version = DCOPY_QUERY_V0;
276*6707Sbrutus query->dq_num_channels = dcopy_statep->d_globalchan_list.dl_cnt;
277*6707Sbrutus }
278*6707Sbrutus
279*6707Sbrutus
280*6707Sbrutus /*
281*6707Sbrutus * dcopy_alloc()
282*6707Sbrutus */
283*6707Sbrutus /*ARGSUSED*/
284*6707Sbrutus int
dcopy_alloc(int flags,dcopy_handle_t * handle)285*6707Sbrutus dcopy_alloc(int flags, dcopy_handle_t *handle)
286*6707Sbrutus {
287*6707Sbrutus dcopy_handle_t channel;
288*6707Sbrutus dcopy_list_t *list;
289*6707Sbrutus
290*6707Sbrutus
291*6707Sbrutus /*
292*6707Sbrutus * we don't use the dcopy_list_* code here because we need to due
293*6707Sbrutus * some non-standard stuff.
294*6707Sbrutus */
295*6707Sbrutus
296*6707Sbrutus list = &dcopy_statep->d_globalchan_list;
297*6707Sbrutus
298*6707Sbrutus /*
299*6707Sbrutus * if nothing is on the channel list, return DCOPY_NORESOURCES. This
300*6707Sbrutus * can happen if there aren't any DMA device registered.
301*6707Sbrutus */
302*6707Sbrutus mutex_enter(&list->dl_mutex);
303*6707Sbrutus channel = list_head(&list->dl_list);
304*6707Sbrutus if (channel == NULL) {
305*6707Sbrutus mutex_exit(&list->dl_mutex);
306*6707Sbrutus return (DCOPY_NORESOURCES);
307*6707Sbrutus }
308*6707Sbrutus
309*6707Sbrutus /*
310*6707Sbrutus * increment the reference count, and pop the channel off the head and
311*6707Sbrutus * push it on the tail. This ensures we rotate through the channels.
312*6707Sbrutus * DMA channels are shared.
313*6707Sbrutus */
314*6707Sbrutus channel->ch_ref_cnt++;
315*6707Sbrutus list_remove(&list->dl_list, channel);
316*6707Sbrutus list_insert_tail(&list->dl_list, channel);
317*6707Sbrutus mutex_exit(&list->dl_mutex);
318*6707Sbrutus
319*6707Sbrutus *handle = (dcopy_handle_t)channel;
320*6707Sbrutus return (DCOPY_SUCCESS);
321*6707Sbrutus }
322*6707Sbrutus
323*6707Sbrutus
324*6707Sbrutus /*
325*6707Sbrutus * dcopy_free()
326*6707Sbrutus */
327*6707Sbrutus void
dcopy_free(dcopy_handle_t * channel)328*6707Sbrutus dcopy_free(dcopy_handle_t *channel)
329*6707Sbrutus {
330*6707Sbrutus dcopy_device_handle_t device;
331*6707Sbrutus dcopy_list_t *list;
332*6707Sbrutus boolean_t cleanup;
333*6707Sbrutus
334*6707Sbrutus
335*6707Sbrutus ASSERT(*channel != NULL);
336*6707Sbrutus
337*6707Sbrutus /*
338*6707Sbrutus * we don't need to add the channel back to the list since we never
339*6707Sbrutus * removed it. decrement the reference count.
340*6707Sbrutus */
341*6707Sbrutus list = &dcopy_statep->d_globalchan_list;
342*6707Sbrutus mutex_enter(&list->dl_mutex);
343*6707Sbrutus (*channel)->ch_ref_cnt--;
344*6707Sbrutus
345*6707Sbrutus /*
346*6707Sbrutus * if we need to remove this channel, and the reference count is down
347*6707Sbrutus * to 0, decrement the number of channels which still need to be
348*6707Sbrutus * removed on the device.
349*6707Sbrutus */
350*6707Sbrutus if ((*channel)->ch_removing && ((*channel)->ch_ref_cnt == 0)) {
351*6707Sbrutus cleanup = B_FALSE;
352*6707Sbrutus device = (*channel)->ch_device;
353*6707Sbrutus mutex_enter(&device->dc_devchan_list.dl_mutex);
354*6707Sbrutus device->dc_removing_cnt--;
355*6707Sbrutus if (device->dc_removing_cnt == 0) {
356*6707Sbrutus cleanup = B_TRUE;
357*6707Sbrutus }
358*6707Sbrutus mutex_exit(&device->dc_devchan_list.dl_mutex);
359*6707Sbrutus }
360*6707Sbrutus mutex_exit(&list->dl_mutex);
361*6707Sbrutus
362*6707Sbrutus /*
363*6707Sbrutus * if there are no channels which still need to be removed, cleanup the
364*6707Sbrutus * device state and call back into the DMA device driver to tell them
365*6707Sbrutus * the device is free.
366*6707Sbrutus */
367*6707Sbrutus if (cleanup) {
368*6707Sbrutus dcopy_device_cleanup(device, B_TRUE);
369*6707Sbrutus }
370*6707Sbrutus
371*6707Sbrutus *channel = NULL;
372*6707Sbrutus }
373*6707Sbrutus
374*6707Sbrutus
375*6707Sbrutus /*
376*6707Sbrutus * dcopy_query_channel()
377*6707Sbrutus */
378*6707Sbrutus void
dcopy_query_channel(dcopy_handle_t channel,dcopy_query_channel_t * query)379*6707Sbrutus dcopy_query_channel(dcopy_handle_t channel, dcopy_query_channel_t *query)
380*6707Sbrutus {
381*6707Sbrutus *query = channel->ch_info;
382*6707Sbrutus }
383*6707Sbrutus
384*6707Sbrutus
385*6707Sbrutus /*
386*6707Sbrutus * dcopy_cmd_alloc()
387*6707Sbrutus */
388*6707Sbrutus int
dcopy_cmd_alloc(dcopy_handle_t handle,int flags,dcopy_cmd_t * cmd)389*6707Sbrutus dcopy_cmd_alloc(dcopy_handle_t handle, int flags, dcopy_cmd_t *cmd)
390*6707Sbrutus {
391*6707Sbrutus dcopy_handle_t channel;
392*6707Sbrutus dcopy_cmd_priv_t priv;
393*6707Sbrutus int e;
394*6707Sbrutus
395*6707Sbrutus
396*6707Sbrutus channel = handle;
397*6707Sbrutus
398*6707Sbrutus atomic_inc_64(&channel->ch_stat.cs_cmd_alloc.value.ui64);
399*6707Sbrutus e = channel->ch_cb->cb_cmd_alloc(channel->ch_channel_private, flags,
400*6707Sbrutus cmd);
401*6707Sbrutus if (e == DCOPY_SUCCESS) {
402*6707Sbrutus priv = (*cmd)->dp_private;
403*6707Sbrutus priv->pr_channel = channel;
404*6707Sbrutus /*
405*6707Sbrutus * we won't initialize the blocking state until we actually
406*6707Sbrutus * need to block.
407*6707Sbrutus */
408*6707Sbrutus priv->pr_block_init = B_FALSE;
409*6707Sbrutus }
410*6707Sbrutus
411*6707Sbrutus return (e);
412*6707Sbrutus }
413*6707Sbrutus
414*6707Sbrutus
415*6707Sbrutus /*
416*6707Sbrutus * dcopy_cmd_free()
417*6707Sbrutus */
418*6707Sbrutus void
dcopy_cmd_free(dcopy_cmd_t * cmd)419*6707Sbrutus dcopy_cmd_free(dcopy_cmd_t *cmd)
420*6707Sbrutus {
421*6707Sbrutus dcopy_handle_t channel;
422*6707Sbrutus dcopy_cmd_priv_t priv;
423*6707Sbrutus
424*6707Sbrutus
425*6707Sbrutus ASSERT(*cmd != NULL);
426*6707Sbrutus
427*6707Sbrutus priv = (*cmd)->dp_private;
428*6707Sbrutus channel = priv->pr_channel;
429*6707Sbrutus
430*6707Sbrutus /* if we initialized the blocking state, clean it up too */
431*6707Sbrutus if (priv->pr_block_init) {
432*6707Sbrutus cv_destroy(&priv->pr_cv);
433*6707Sbrutus mutex_destroy(&priv->pr_mutex);
434*6707Sbrutus }
435*6707Sbrutus
436*6707Sbrutus channel->ch_cb->cb_cmd_free(channel->ch_channel_private, cmd);
437*6707Sbrutus }
438*6707Sbrutus
439*6707Sbrutus
440*6707Sbrutus /*
441*6707Sbrutus * dcopy_cmd_post()
442*6707Sbrutus */
443*6707Sbrutus int
dcopy_cmd_post(dcopy_cmd_t cmd)444*6707Sbrutus dcopy_cmd_post(dcopy_cmd_t cmd)
445*6707Sbrutus {
446*6707Sbrutus dcopy_handle_t channel;
447*6707Sbrutus int e;
448*6707Sbrutus
449*6707Sbrutus
450*6707Sbrutus channel = cmd->dp_private->pr_channel;
451*6707Sbrutus
452*6707Sbrutus atomic_inc_64(&channel->ch_stat.cs_cmd_post.value.ui64);
453*6707Sbrutus if (cmd->dp_cmd == DCOPY_CMD_COPY) {
454*6707Sbrutus atomic_add_64(&channel->ch_stat.cs_bytes_xfer.value.ui64,
455*6707Sbrutus cmd->dp.copy.cc_size);
456*6707Sbrutus }
457*6707Sbrutus e = channel->ch_cb->cb_cmd_post(channel->ch_channel_private, cmd);
458*6707Sbrutus if (e != DCOPY_SUCCESS) {
459*6707Sbrutus return (e);
460*6707Sbrutus }
461*6707Sbrutus
462*6707Sbrutus return (DCOPY_SUCCESS);
463*6707Sbrutus }
464*6707Sbrutus
465*6707Sbrutus
466*6707Sbrutus /*
467*6707Sbrutus * dcopy_cmd_poll()
468*6707Sbrutus */
469*6707Sbrutus int
dcopy_cmd_poll(dcopy_cmd_t cmd,int flags)470*6707Sbrutus dcopy_cmd_poll(dcopy_cmd_t cmd, int flags)
471*6707Sbrutus {
472*6707Sbrutus dcopy_handle_t channel;
473*6707Sbrutus dcopy_cmd_priv_t priv;
474*6707Sbrutus int e;
475*6707Sbrutus
476*6707Sbrutus
477*6707Sbrutus priv = cmd->dp_private;
478*6707Sbrutus channel = priv->pr_channel;
479*6707Sbrutus
480*6707Sbrutus /*
481*6707Sbrutus * if the caller is trying to block, they needed to post the
482*6707Sbrutus * command with DCOPY_CMD_INTR set.
483*6707Sbrutus */
484*6707Sbrutus if ((flags & DCOPY_POLL_BLOCK) && !(cmd->dp_flags & DCOPY_CMD_INTR)) {
485*6707Sbrutus return (DCOPY_FAILURE);
486*6707Sbrutus }
487*6707Sbrutus
488*6707Sbrutus atomic_inc_64(&channel->ch_stat.cs_cmd_poll.value.ui64);
489*6707Sbrutus
490*6707Sbrutus repoll:
491*6707Sbrutus e = channel->ch_cb->cb_cmd_poll(channel->ch_channel_private, cmd);
492*6707Sbrutus if (e == DCOPY_PENDING) {
493*6707Sbrutus /*
494*6707Sbrutus * if the command is still active, and the blocking flag
495*6707Sbrutus * is set.
496*6707Sbrutus */
497*6707Sbrutus if (flags & DCOPY_POLL_BLOCK) {
498*6707Sbrutus
499*6707Sbrutus /*
500*6707Sbrutus * if we haven't initialized the state, do it now. A
501*6707Sbrutus * command can be re-used, so it's possible it's
502*6707Sbrutus * already been initialized.
503*6707Sbrutus */
504*6707Sbrutus if (!priv->pr_block_init) {
505*6707Sbrutus priv->pr_block_init = B_TRUE;
506*6707Sbrutus mutex_init(&priv->pr_mutex, NULL, MUTEX_DRIVER,
507*6707Sbrutus NULL);
508*6707Sbrutus cv_init(&priv->pr_cv, NULL, CV_DRIVER, NULL);
509*6707Sbrutus priv->pr_cmd = cmd;
510*6707Sbrutus }
511*6707Sbrutus
512*6707Sbrutus /* push it on the list for blocking commands */
513*6707Sbrutus priv->pr_wait = B_TRUE;
514*6707Sbrutus dcopy_list_push(&channel->ch_poll_list, priv);
515*6707Sbrutus
516*6707Sbrutus mutex_enter(&priv->pr_mutex);
517*6707Sbrutus /*
518*6707Sbrutus * it's possible we already cleared pr_wait before we
519*6707Sbrutus * grabbed the mutex.
520*6707Sbrutus */
521*6707Sbrutus if (priv->pr_wait) {
522*6707Sbrutus cv_wait(&priv->pr_cv, &priv->pr_mutex);
523*6707Sbrutus }
524*6707Sbrutus mutex_exit(&priv->pr_mutex);
525*6707Sbrutus
526*6707Sbrutus /*
527*6707Sbrutus * the command has completed, go back and poll so we
528*6707Sbrutus * get the status.
529*6707Sbrutus */
530*6707Sbrutus goto repoll;
531*6707Sbrutus }
532*6707Sbrutus }
533*6707Sbrutus
534*6707Sbrutus return (e);
535*6707Sbrutus }
536*6707Sbrutus
537*6707Sbrutus /* *** END OF EXTERNAL INTERFACE *** */
538*6707Sbrutus
539*6707Sbrutus /*
540*6707Sbrutus * dcopy_list_init()
541*6707Sbrutus */
542*6707Sbrutus static int
dcopy_list_init(dcopy_list_t * list,size_t node_size,offset_t link_offset)543*6707Sbrutus dcopy_list_init(dcopy_list_t *list, size_t node_size, offset_t link_offset)
544*6707Sbrutus {
545*6707Sbrutus mutex_init(&list->dl_mutex, NULL, MUTEX_DRIVER, NULL);
546*6707Sbrutus list_create(&list->dl_list, node_size, link_offset);
547*6707Sbrutus list->dl_cnt = 0;
548*6707Sbrutus
549*6707Sbrutus return (DCOPY_SUCCESS);
550*6707Sbrutus }
551*6707Sbrutus
552*6707Sbrutus
553*6707Sbrutus /*
554*6707Sbrutus * dcopy_list_fini()
555*6707Sbrutus */
556*6707Sbrutus static void
dcopy_list_fini(dcopy_list_t * list)557*6707Sbrutus dcopy_list_fini(dcopy_list_t *list)
558*6707Sbrutus {
559*6707Sbrutus list_destroy(&list->dl_list);
560*6707Sbrutus mutex_destroy(&list->dl_mutex);
561*6707Sbrutus }
562*6707Sbrutus
563*6707Sbrutus
564*6707Sbrutus /*
565*6707Sbrutus * dcopy_list_push()
566*6707Sbrutus */
567*6707Sbrutus static void
dcopy_list_push(dcopy_list_t * list,void * list_node)568*6707Sbrutus dcopy_list_push(dcopy_list_t *list, void *list_node)
569*6707Sbrutus {
570*6707Sbrutus mutex_enter(&list->dl_mutex);
571*6707Sbrutus list_insert_tail(&list->dl_list, list_node);
572*6707Sbrutus list->dl_cnt++;
573*6707Sbrutus mutex_exit(&list->dl_mutex);
574*6707Sbrutus }
575*6707Sbrutus
576*6707Sbrutus
577*6707Sbrutus /*
578*6707Sbrutus * dcopy_list_pop()
579*6707Sbrutus */
580*6707Sbrutus static void *
dcopy_list_pop(dcopy_list_t * list)581*6707Sbrutus dcopy_list_pop(dcopy_list_t *list)
582*6707Sbrutus {
583*6707Sbrutus list_node_t *list_node;
584*6707Sbrutus
585*6707Sbrutus mutex_enter(&list->dl_mutex);
586*6707Sbrutus list_node = list_head(&list->dl_list);
587*6707Sbrutus if (list_node == NULL) {
588*6707Sbrutus mutex_exit(&list->dl_mutex);
589*6707Sbrutus return (list_node);
590*6707Sbrutus }
591*6707Sbrutus list->dl_cnt--;
592*6707Sbrutus list_remove(&list->dl_list, list_node);
593*6707Sbrutus mutex_exit(&list->dl_mutex);
594*6707Sbrutus
595*6707Sbrutus return (list_node);
596*6707Sbrutus }
597*6707Sbrutus
598*6707Sbrutus
599*6707Sbrutus /* *** DEVICE INTERFACE *** */
600*6707Sbrutus /*
601*6707Sbrutus * dcopy_device_register()
602*6707Sbrutus */
603*6707Sbrutus int
dcopy_device_register(void * device_private,dcopy_device_info_t * info,dcopy_device_handle_t * handle)604*6707Sbrutus dcopy_device_register(void *device_private, dcopy_device_info_t *info,
605*6707Sbrutus dcopy_device_handle_t *handle)
606*6707Sbrutus {
607*6707Sbrutus struct dcopy_channel_s *channel;
608*6707Sbrutus struct dcopy_device_s *device;
609*6707Sbrutus int e;
610*6707Sbrutus int i;
611*6707Sbrutus
612*6707Sbrutus
613*6707Sbrutus /* initialize the per device state */
614*6707Sbrutus device = kmem_zalloc(sizeof (*device), KM_SLEEP);
615*6707Sbrutus device->dc_device_private = device_private;
616*6707Sbrutus device->dc_info = *info;
617*6707Sbrutus device->dc_removing_cnt = 0;
618*6707Sbrutus device->dc_cb = info->di_cb;
619*6707Sbrutus
620*6707Sbrutus /*
621*6707Sbrutus * we have a per device channel list so we can remove a device in the
622*6707Sbrutus * future.
623*6707Sbrutus */
624*6707Sbrutus e = dcopy_list_init(&device->dc_devchan_list,
625*6707Sbrutus sizeof (struct dcopy_channel_s),
626*6707Sbrutus offsetof(struct dcopy_channel_s, ch_devchan_list_node));
627*6707Sbrutus if (e != DCOPY_SUCCESS) {
628*6707Sbrutus goto registerfail_devchan;
629*6707Sbrutus }
630*6707Sbrutus
631*6707Sbrutus /*
632*6707Sbrutus * allocate state for each channel, allocate the channel, and then add
633*6707Sbrutus * the devices dma channels to the devices channel list.
634*6707Sbrutus */
635*6707Sbrutus for (i = 0; i < info->di_num_dma; i++) {
636*6707Sbrutus channel = kmem_zalloc(sizeof (*channel), KM_SLEEP);
637*6707Sbrutus channel->ch_device = device;
638*6707Sbrutus channel->ch_removing = B_FALSE;
639*6707Sbrutus channel->ch_ref_cnt = 0;
640*6707Sbrutus channel->ch_cb = info->di_cb;
641*6707Sbrutus
642*6707Sbrutus e = info->di_cb->cb_channel_alloc(device_private, channel,
643*6707Sbrutus DCOPY_SLEEP, dcopy_channel_size, &channel->ch_info,
644*6707Sbrutus &channel->ch_channel_private);
645*6707Sbrutus if (e != DCOPY_SUCCESS) {
646*6707Sbrutus kmem_free(channel, sizeof (*channel));
647*6707Sbrutus goto registerfail_alloc;
648*6707Sbrutus }
649*6707Sbrutus
650*6707Sbrutus e = dcopy_stats_init(channel);
651*6707Sbrutus if (e != DCOPY_SUCCESS) {
652*6707Sbrutus info->di_cb->cb_channel_free(
653*6707Sbrutus &channel->ch_channel_private);
654*6707Sbrutus kmem_free(channel, sizeof (*channel));
655*6707Sbrutus goto registerfail_alloc;
656*6707Sbrutus }
657*6707Sbrutus
658*6707Sbrutus e = dcopy_list_init(&channel->ch_poll_list,
659*6707Sbrutus sizeof (struct dcopy_cmd_priv_s),
660*6707Sbrutus offsetof(struct dcopy_cmd_priv_s, pr_poll_list_node));
661*6707Sbrutus if (e != DCOPY_SUCCESS) {
662*6707Sbrutus dcopy_stats_fini(channel);
663*6707Sbrutus info->di_cb->cb_channel_free(
664*6707Sbrutus &channel->ch_channel_private);
665*6707Sbrutus kmem_free(channel, sizeof (*channel));
666*6707Sbrutus goto registerfail_alloc;
667*6707Sbrutus }
668*6707Sbrutus
669*6707Sbrutus dcopy_list_push(&device->dc_devchan_list, channel);
670*6707Sbrutus }
671*6707Sbrutus
672*6707Sbrutus /* add the device to device list */
673*6707Sbrutus dcopy_list_push(&dcopy_statep->d_device_list, device);
674*6707Sbrutus
675*6707Sbrutus /*
676*6707Sbrutus * add the device's dma channels to the global channel list (where
677*6707Sbrutus * dcopy_alloc's come from)
678*6707Sbrutus */
679*6707Sbrutus mutex_enter(&dcopy_statep->d_globalchan_list.dl_mutex);
680*6707Sbrutus mutex_enter(&dcopy_statep->d_device_list.dl_mutex);
681*6707Sbrutus channel = list_head(&device->dc_devchan_list.dl_list);
682*6707Sbrutus while (channel != NULL) {
683*6707Sbrutus list_insert_tail(&dcopy_statep->d_globalchan_list.dl_list,
684*6707Sbrutus channel);
685*6707Sbrutus dcopy_statep->d_globalchan_list.dl_cnt++;
686*6707Sbrutus channel = list_next(&device->dc_devchan_list.dl_list, channel);
687*6707Sbrutus }
688*6707Sbrutus mutex_exit(&dcopy_statep->d_device_list.dl_mutex);
689*6707Sbrutus mutex_exit(&dcopy_statep->d_globalchan_list.dl_mutex);
690*6707Sbrutus
691*6707Sbrutus *handle = device;
692*6707Sbrutus
693*6707Sbrutus /* last call-back into kernel for dcopy KAPI enabled */
694*6707Sbrutus uioa_dcopy_enable();
695*6707Sbrutus
696*6707Sbrutus return (DCOPY_SUCCESS);
697*6707Sbrutus
698*6707Sbrutus registerfail_alloc:
699*6707Sbrutus channel = list_head(&device->dc_devchan_list.dl_list);
700*6707Sbrutus while (channel != NULL) {
701*6707Sbrutus /* remove from the list */
702*6707Sbrutus channel = dcopy_list_pop(&device->dc_devchan_list);
703*6707Sbrutus ASSERT(channel != NULL);
704*6707Sbrutus
705*6707Sbrutus dcopy_list_fini(&channel->ch_poll_list);
706*6707Sbrutus dcopy_stats_fini(channel);
707*6707Sbrutus info->di_cb->cb_channel_free(&channel->ch_channel_private);
708*6707Sbrutus kmem_free(channel, sizeof (*channel));
709*6707Sbrutus }
710*6707Sbrutus
711*6707Sbrutus dcopy_list_fini(&device->dc_devchan_list);
712*6707Sbrutus registerfail_devchan:
713*6707Sbrutus kmem_free(device, sizeof (*device));
714*6707Sbrutus
715*6707Sbrutus return (DCOPY_FAILURE);
716*6707Sbrutus }
717*6707Sbrutus
718*6707Sbrutus
719*6707Sbrutus /*
720*6707Sbrutus * dcopy_device_unregister()
721*6707Sbrutus */
722*6707Sbrutus /*ARGSUSED*/
723*6707Sbrutus int
dcopy_device_unregister(dcopy_device_handle_t * handle)724*6707Sbrutus dcopy_device_unregister(dcopy_device_handle_t *handle)
725*6707Sbrutus {
726*6707Sbrutus struct dcopy_channel_s *channel;
727*6707Sbrutus dcopy_device_handle_t device;
728*6707Sbrutus boolean_t device_busy;
729*6707Sbrutus
730*6707Sbrutus /* first call-back into kernel for dcopy KAPI disable */
731*6707Sbrutus uioa_dcopy_disable();
732*6707Sbrutus
733*6707Sbrutus device = *handle;
734*6707Sbrutus device_busy = B_FALSE;
735*6707Sbrutus
736*6707Sbrutus /*
737*6707Sbrutus * remove the devices dma channels from the global channel list (where
738*6707Sbrutus * dcopy_alloc's come from)
739*6707Sbrutus */
740*6707Sbrutus mutex_enter(&dcopy_statep->d_globalchan_list.dl_mutex);
741*6707Sbrutus mutex_enter(&device->dc_devchan_list.dl_mutex);
742*6707Sbrutus channel = list_head(&device->dc_devchan_list.dl_list);
743*6707Sbrutus while (channel != NULL) {
744*6707Sbrutus /*
745*6707Sbrutus * if the channel has outstanding allocs, mark it as having
746*6707Sbrutus * to be removed and increment the number of channels which
747*6707Sbrutus * need to be removed in the device state too.
748*6707Sbrutus */
749*6707Sbrutus if (channel->ch_ref_cnt != 0) {
750*6707Sbrutus channel->ch_removing = B_TRUE;
751*6707Sbrutus device_busy = B_TRUE;
752*6707Sbrutus device->dc_removing_cnt++;
753*6707Sbrutus }
754*6707Sbrutus dcopy_statep->d_globalchan_list.dl_cnt--;
755*6707Sbrutus list_remove(&dcopy_statep->d_globalchan_list.dl_list, channel);
756*6707Sbrutus channel = list_next(&device->dc_devchan_list.dl_list, channel);
757*6707Sbrutus }
758*6707Sbrutus mutex_exit(&device->dc_devchan_list.dl_mutex);
759*6707Sbrutus mutex_exit(&dcopy_statep->d_globalchan_list.dl_mutex);
760*6707Sbrutus
761*6707Sbrutus /*
762*6707Sbrutus * if there are channels which still need to be removed, we will clean
763*6707Sbrutus * up the device state after they are freed up.
764*6707Sbrutus */
765*6707Sbrutus if (device_busy) {
766*6707Sbrutus return (DCOPY_PENDING);
767*6707Sbrutus }
768*6707Sbrutus
769*6707Sbrutus dcopy_device_cleanup(device, B_FALSE);
770*6707Sbrutus
771*6707Sbrutus *handle = NULL;
772*6707Sbrutus return (DCOPY_SUCCESS);
773*6707Sbrutus }
774*6707Sbrutus
775*6707Sbrutus
776*6707Sbrutus /*
777*6707Sbrutus * dcopy_device_cleanup()
778*6707Sbrutus */
779*6707Sbrutus static void
dcopy_device_cleanup(dcopy_device_handle_t device,boolean_t do_callback)780*6707Sbrutus dcopy_device_cleanup(dcopy_device_handle_t device, boolean_t do_callback)
781*6707Sbrutus {
782*6707Sbrutus struct dcopy_channel_s *channel;
783*6707Sbrutus
784*6707Sbrutus /*
785*6707Sbrutus * remove all the channels in the device list, free them, and clean up
786*6707Sbrutus * the state.
787*6707Sbrutus */
788*6707Sbrutus mutex_enter(&dcopy_statep->d_device_list.dl_mutex);
789*6707Sbrutus channel = list_head(&device->dc_devchan_list.dl_list);
790*6707Sbrutus while (channel != NULL) {
791*6707Sbrutus device->dc_devchan_list.dl_cnt--;
792*6707Sbrutus list_remove(&device->dc_devchan_list.dl_list, channel);
793*6707Sbrutus dcopy_list_fini(&channel->ch_poll_list);
794*6707Sbrutus dcopy_stats_fini(channel);
795*6707Sbrutus channel->ch_cb->cb_channel_free(&channel->ch_channel_private);
796*6707Sbrutus kmem_free(channel, sizeof (*channel));
797*6707Sbrutus channel = list_head(&device->dc_devchan_list.dl_list);
798*6707Sbrutus }
799*6707Sbrutus
800*6707Sbrutus /* remove it from the list of devices */
801*6707Sbrutus list_remove(&dcopy_statep->d_device_list.dl_list, device);
802*6707Sbrutus
803*6707Sbrutus mutex_exit(&dcopy_statep->d_device_list.dl_mutex);
804*6707Sbrutus
805*6707Sbrutus /*
806*6707Sbrutus * notify the DMA device driver that the device is free to be
807*6707Sbrutus * detached.
808*6707Sbrutus */
809*6707Sbrutus if (do_callback) {
810*6707Sbrutus device->dc_cb->cb_unregister_complete(
811*6707Sbrutus device->dc_device_private, DCOPY_SUCCESS);
812*6707Sbrutus }
813*6707Sbrutus
814*6707Sbrutus dcopy_list_fini(&device->dc_devchan_list);
815*6707Sbrutus kmem_free(device, sizeof (*device));
816*6707Sbrutus }
817*6707Sbrutus
818*6707Sbrutus
819*6707Sbrutus /*
820*6707Sbrutus * dcopy_device_channel_notify()
821*6707Sbrutus */
822*6707Sbrutus /*ARGSUSED*/
823*6707Sbrutus void
dcopy_device_channel_notify(dcopy_handle_t handle,int status)824*6707Sbrutus dcopy_device_channel_notify(dcopy_handle_t handle, int status)
825*6707Sbrutus {
826*6707Sbrutus struct dcopy_channel_s *channel;
827*6707Sbrutus dcopy_list_t *poll_list;
828*6707Sbrutus dcopy_cmd_priv_t priv;
829*6707Sbrutus int e;
830*6707Sbrutus
831*6707Sbrutus
832*6707Sbrutus ASSERT(status == DCOPY_COMPLETION);
833*6707Sbrutus channel = handle;
834*6707Sbrutus
835*6707Sbrutus poll_list = &channel->ch_poll_list;
836*6707Sbrutus
837*6707Sbrutus /*
838*6707Sbrutus * when we get a completion notification from the device, go through
839*6707Sbrutus * all of the commands blocking on this channel and see if they have
840*6707Sbrutus * completed. Remove the command and wake up the block thread if they
841*6707Sbrutus * have. Once we hit a command which is still pending, we are done
842*6707Sbrutus * polling since commands in a channel complete in order.
843*6707Sbrutus */
844*6707Sbrutus mutex_enter(&poll_list->dl_mutex);
845*6707Sbrutus if (poll_list->dl_cnt != 0) {
846*6707Sbrutus priv = list_head(&poll_list->dl_list);
847*6707Sbrutus while (priv != NULL) {
848*6707Sbrutus atomic_inc_64(&channel->
849*6707Sbrutus ch_stat.cs_notify_poll.value.ui64);
850*6707Sbrutus e = channel->ch_cb->cb_cmd_poll(
851*6707Sbrutus channel->ch_channel_private,
852*6707Sbrutus priv->pr_cmd);
853*6707Sbrutus if (e == DCOPY_PENDING) {
854*6707Sbrutus atomic_inc_64(&channel->
855*6707Sbrutus ch_stat.cs_notify_pending.value.ui64);
856*6707Sbrutus break;
857*6707Sbrutus }
858*6707Sbrutus
859*6707Sbrutus poll_list->dl_cnt--;
860*6707Sbrutus list_remove(&poll_list->dl_list, priv);
861*6707Sbrutus
862*6707Sbrutus mutex_enter(&priv->pr_mutex);
863*6707Sbrutus priv->pr_wait = B_FALSE;
864*6707Sbrutus cv_signal(&priv->pr_cv);
865*6707Sbrutus mutex_exit(&priv->pr_mutex);
866*6707Sbrutus
867*6707Sbrutus priv = list_head(&poll_list->dl_list);
868*6707Sbrutus }
869*6707Sbrutus }
870*6707Sbrutus
871*6707Sbrutus mutex_exit(&poll_list->dl_mutex);
872*6707Sbrutus }
873*6707Sbrutus
874*6707Sbrutus
875*6707Sbrutus /*
876*6707Sbrutus * dcopy_stats_init()
877*6707Sbrutus */
878*6707Sbrutus static int
dcopy_stats_init(dcopy_handle_t channel)879*6707Sbrutus dcopy_stats_init(dcopy_handle_t channel)
880*6707Sbrutus {
881*6707Sbrutus #define CHANSTRSIZE 20
882*6707Sbrutus char chanstr[CHANSTRSIZE];
883*6707Sbrutus dcopy_stats_t *stats;
884*6707Sbrutus int instance;
885*6707Sbrutus char *name;
886*6707Sbrutus
887*6707Sbrutus
888*6707Sbrutus stats = &channel->ch_stat;
889*6707Sbrutus name = (char *)ddi_driver_name(channel->ch_device->dc_info.di_dip);
890*6707Sbrutus instance = ddi_get_instance(channel->ch_device->dc_info.di_dip);
891*6707Sbrutus
892*6707Sbrutus (void) snprintf(chanstr, CHANSTRSIZE, "channel%d",
893*6707Sbrutus (uint32_t)channel->ch_info.qc_chan_num);
894*6707Sbrutus
895*6707Sbrutus channel->ch_kstat = kstat_create(name, instance, chanstr, "misc",
896*6707Sbrutus KSTAT_TYPE_NAMED, sizeof (dcopy_stats_t) / sizeof (kstat_named_t),
897*6707Sbrutus KSTAT_FLAG_VIRTUAL);
898*6707Sbrutus if (channel->ch_kstat == NULL) {
899*6707Sbrutus return (DCOPY_FAILURE);
900*6707Sbrutus }
901*6707Sbrutus channel->ch_kstat->ks_data = stats;
902*6707Sbrutus
903*6707Sbrutus kstat_named_init(&stats->cs_bytes_xfer, "bytes_xfer",
904*6707Sbrutus KSTAT_DATA_UINT64);
905*6707Sbrutus kstat_named_init(&stats->cs_cmd_alloc, "cmd_alloc",
906*6707Sbrutus KSTAT_DATA_UINT64);
907*6707Sbrutus kstat_named_init(&stats->cs_cmd_post, "cmd_post",
908*6707Sbrutus KSTAT_DATA_UINT64);
909*6707Sbrutus kstat_named_init(&stats->cs_cmd_poll, "cmd_poll",
910*6707Sbrutus KSTAT_DATA_UINT64);
911*6707Sbrutus kstat_named_init(&stats->cs_notify_poll, "notify_poll",
912*6707Sbrutus KSTAT_DATA_UINT64);
913*6707Sbrutus kstat_named_init(&stats->cs_notify_pending, "notify_pending",
914*6707Sbrutus KSTAT_DATA_UINT64);
915*6707Sbrutus kstat_named_init(&stats->cs_id, "id",
916*6707Sbrutus KSTAT_DATA_UINT64);
917*6707Sbrutus kstat_named_init(&stats->cs_capabilities, "capabilities",
918*6707Sbrutus KSTAT_DATA_UINT64);
919*6707Sbrutus
920*6707Sbrutus kstat_install(channel->ch_kstat);
921*6707Sbrutus
922*6707Sbrutus channel->ch_stat.cs_id.value.ui64 = channel->ch_info.qc_id;
923*6707Sbrutus channel->ch_stat.cs_capabilities.value.ui64 =
924*6707Sbrutus channel->ch_info.qc_capabilities;
925*6707Sbrutus
926*6707Sbrutus return (DCOPY_SUCCESS);
927*6707Sbrutus }
928*6707Sbrutus
929*6707Sbrutus
930*6707Sbrutus /*
931*6707Sbrutus * dcopy_stats_fini()
932*6707Sbrutus */
933*6707Sbrutus static void
dcopy_stats_fini(dcopy_handle_t channel)934*6707Sbrutus dcopy_stats_fini(dcopy_handle_t channel)
935*6707Sbrutus {
936*6707Sbrutus kstat_delete(channel->ch_kstat);
937*6707Sbrutus }
938*6707Sbrutus /* *** END OF DEVICE INTERFACE *** */
939