1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25 /*
26 * Copyright (c) 2010, Intel Corporation.
27 * All rights reserved.
28 */
29
30 #include <sys/types.h>
31 #include <sys/param.h>
32 #include <sys/t_lock.h>
33 #include <sys/thread.h>
34 #include <sys/cpuvar.h>
35 #include <sys/x_call.h>
36 #include <sys/xc_levels.h>
37 #include <sys/cpu.h>
38 #include <sys/psw.h>
39 #include <sys/sunddi.h>
40 #include <sys/debug.h>
41 #include <sys/systm.h>
42 #include <sys/archsystm.h>
43 #include <sys/machsystm.h>
44 #include <sys/mutex_impl.h>
45 #include <sys/stack.h>
46 #include <sys/promif.h>
47 #include <sys/x86_archext.h>
48
49 /*
50 * Implementation for cross-processor calls via interprocessor interrupts
51 *
52 * This implementation uses a message passing architecture to allow multiple
53 * concurrent cross calls to be in flight at any given time. We use the cmpxchg
54 * instruction, aka atomic_cas_ptr(), to implement simple efficient work
55 * queues for message passing between CPUs with almost no need for regular
56 * locking. See xc_extract() and xc_insert() below.
57 *
58 * The general idea is that initiating a cross call means putting a message
59 * on a target(s) CPU's work queue. Any synchronization is handled by passing
60 * the message back and forth between initiator and target(s).
61 *
62 * Every CPU has xc_work_cnt, which indicates it has messages to process.
63 * This value is incremented as message traffic is initiated and decremented
64 * with every message that finishes all processing.
65 *
66 * The code needs no mfence or other membar_*() calls. The uses of
67 * atomic_cas_ptr(), atomic_inc_32_nv() and atomic_dec_32() for the message
68 * passing are implemented with LOCK prefix instructions which are
69 * equivalent to mfence.
70 *
71 * One interesting aspect of this implmentation is that it allows 2 or more
72 * CPUs to initiate cross calls to intersecting sets of CPUs at the same time.
73 * The cross call processing by the CPUs will happen in any order with only
74 * a guarantee, for xc_call() and xc_sync(), that an initiator won't return
75 * from cross calls before all slaves have invoked the function.
76 *
77 * The reason for this asynchronous approach is to allow for fast global
78 * TLB shootdowns. If all CPUs, say N, tried to do a global TLB invalidation
79 * on a different Virtual Address at the same time. The old code required
80 * N squared IPIs. With this method, depending on timing, it could happen
81 * with just N IPIs.
82 */
83
84 /*
85 * The default is to not enable collecting counts of IPI information, since
86 * the updating of shared cachelines could cause excess bus traffic.
87 */
88 uint_t xc_collect_enable = 0;
89 uint64_t xc_total_cnt = 0; /* total #IPIs sent for cross calls */
90 uint64_t xc_multi_cnt = 0; /* # times we piggy backed on another IPI */
91
92 /*
93 * Values for message states. Here are the normal transitions. A transition
94 * of "->" happens in the slave cpu and "=>" happens in the master cpu as
95 * the messages are passed back and forth.
96 *
97 * FREE => ASYNC -> DONE => FREE
98 * FREE => CALL -> DONE => FREE
99 * FREE => SYNC -> WAITING => RELEASED -> DONE => FREE
100 *
101 * The interesing one above is ASYNC. You might ask, why not go directly
102 * to FREE, instead of DONE. If it did that, it might be possible to exhaust
103 * the master's xc_free list if a master can generate ASYNC messages faster
104 * then the slave can process them. That could be handled with more complicated
105 * handling. However since nothing important uses ASYNC, I've not bothered.
106 */
107 #define XC_MSG_FREE (0) /* msg in xc_free queue */
108 #define XC_MSG_ASYNC (1) /* msg in slave xc_msgbox */
109 #define XC_MSG_CALL (2) /* msg in slave xc_msgbox */
110 #define XC_MSG_SYNC (3) /* msg in slave xc_msgbox */
111 #define XC_MSG_WAITING (4) /* msg in master xc_msgbox or xc_waiters */
112 #define XC_MSG_RELEASED (5) /* msg in slave xc_msgbox */
113 #define XC_MSG_DONE (6) /* msg in master xc_msgbox */
114
115 /*
116 * We allow for one high priority message at a time to happen in the system.
117 * This is used for panic, kmdb, etc., so no locking is done.
118 */
119 static volatile cpuset_t xc_priority_set_store;
120 static volatile ulong_t *xc_priority_set = CPUSET2BV(xc_priority_set_store);
121 static xc_data_t xc_priority_data;
122
123 /*
124 * Wrappers to avoid C compiler warnings due to volatile. The atomic bit
125 * operations don't accept volatile bit vectors - which is a bit silly.
126 */
127 #define XC_BT_SET(vector, b) BT_ATOMIC_SET((ulong_t *)(vector), (b))
128 #define XC_BT_CLEAR(vector, b) BT_ATOMIC_CLEAR((ulong_t *)(vector), (b))
129
130 /*
131 * Decrement a CPU's work count
132 */
133 static void
134 xc_decrement(struct machcpu *mcpu)
135 {
136 atomic_dec_32(&mcpu->xc_work_cnt);
137 }
138
139 /*
140 * Increment a CPU's work count and return the old value
141 */
142 static int
143 xc_increment(struct machcpu *mcpu)
144 {
145 return (atomic_inc_32_nv(&mcpu->xc_work_cnt) - 1);
146 }
147
148 /*
149 * Put a message into a queue. The insertion is atomic no matter
150 * how many different inserts/extracts to the same queue happen.
151 */
152 static void
153 xc_insert(void *queue, xc_msg_t *msg)
154 {
155 xc_msg_t *old_head;
156
157 /*
158 * FREE messages should only ever be getting inserted into
159 * the xc_master CPUs xc_free queue.
160 */
161 ASSERT(msg->xc_command != XC_MSG_FREE ||
162 cpu[msg->xc_master] == NULL || /* possible only during init */
163 queue == &cpu[msg->xc_master]->cpu_m.xc_free);
164
165 do {
166 old_head = (xc_msg_t *)*(volatile xc_msg_t **)queue;
167 msg->xc_next = old_head;
168 } while (atomic_cas_ptr(queue, old_head, msg) != old_head);
169 }
170
171 /*
172 * Extract a message from a queue. The extraction is atomic only
173 * when just one thread does extractions from the queue.
174 * If the queue is empty, NULL is returned.
175 */
176 static xc_msg_t *
177 xc_extract(xc_msg_t **queue)
178 {
179 xc_msg_t *old_head;
180
181 do {
182 old_head = (xc_msg_t *)*(volatile xc_msg_t **)queue;
183 if (old_head == NULL)
184 return (old_head);
185 } while (atomic_cas_ptr(queue, old_head, old_head->xc_next) !=
186 old_head);
187 old_head->xc_next = NULL;
188 return (old_head);
189 }
190
191 /*
192 * Initialize the machcpu fields used for cross calls
193 */
194 static uint_t xc_initialized = 0;
195
196 void
197 xc_init_cpu(struct cpu *cpup)
198 {
199 xc_msg_t *msg;
200 int c;
201
202 /*
203 * Allocate message buffers for the new CPU.
204 */
205 for (c = 0; c < max_ncpus; ++c) {
206 if (plat_dr_support_cpu()) {
207 /*
208 * Allocate a message buffer for every CPU possible
209 * in system, including our own, and add them to our xc
210 * message queue.
211 */
212 msg = kmem_zalloc(sizeof (*msg), KM_SLEEP);
213 msg->xc_command = XC_MSG_FREE;
214 msg->xc_master = cpup->cpu_id;
215 xc_insert(&cpup->cpu_m.xc_free, msg);
216 } else if (cpu[c] != NULL && cpu[c] != cpup) {
217 /*
218 * Add a new message buffer to each existing CPU's free
219 * list, as well as one for my list for each of them.
220 * Note: cpu0 is statically inserted into cpu[] array,
221 * so need to check cpu[c] isn't cpup itself to avoid
222 * allocating extra message buffers for cpu0.
223 */
224 msg = kmem_zalloc(sizeof (*msg), KM_SLEEP);
225 msg->xc_command = XC_MSG_FREE;
226 msg->xc_master = c;
227 xc_insert(&cpu[c]->cpu_m.xc_free, msg);
228
229 msg = kmem_zalloc(sizeof (*msg), KM_SLEEP);
230 msg->xc_command = XC_MSG_FREE;
231 msg->xc_master = cpup->cpu_id;
232 xc_insert(&cpup->cpu_m.xc_free, msg);
233 }
234 }
235
236 if (!plat_dr_support_cpu()) {
237 /*
238 * Add one for self messages if CPU hotplug is disabled.
239 */
240 msg = kmem_zalloc(sizeof (*msg), KM_SLEEP);
241 msg->xc_command = XC_MSG_FREE;
242 msg->xc_master = cpup->cpu_id;
243 xc_insert(&cpup->cpu_m.xc_free, msg);
244 }
245
246 if (!xc_initialized)
247 xc_initialized = 1;
248 }
249
250 void
251 xc_fini_cpu(struct cpu *cpup)
252 {
253 xc_msg_t *msg;
254
255 ASSERT((cpup->cpu_flags & CPU_READY) == 0);
256 ASSERT(cpup->cpu_m.xc_msgbox == NULL);
257 ASSERT(cpup->cpu_m.xc_work_cnt == 0);
258
259 while ((msg = xc_extract(&cpup->cpu_m.xc_free)) != NULL) {
260 kmem_free(msg, sizeof (*msg));
261 }
262 }
263
264 #define XC_FLUSH_MAX_WAITS 1000
265
266 /* Flush inflight message buffers. */
267 int
268 xc_flush_cpu(struct cpu *cpup)
269 {
270 int i;
271
272 ASSERT((cpup->cpu_flags & CPU_READY) == 0);
273
274 /*
275 * Pause all working CPUs, which ensures that there's no CPU in
276 * function xc_common().
277 * This is used to work around a race condition window in xc_common()
278 * between checking CPU_READY flag and increasing working item count.
279 */
280 pause_cpus(cpup, NULL);
281 start_cpus();
282
283 for (i = 0; i < XC_FLUSH_MAX_WAITS; i++) {
284 if (cpup->cpu_m.xc_work_cnt == 0) {
285 break;
286 }
287 DELAY(1);
288 }
289 for (; i < XC_FLUSH_MAX_WAITS; i++) {
290 if (!BT_TEST(xc_priority_set, cpup->cpu_id)) {
291 break;
292 }
293 DELAY(1);
294 }
295
296 return (i >= XC_FLUSH_MAX_WAITS ? ETIME : 0);
297 }
298
299 /*
300 * X-call message processing routine. Note that this is used by both
301 * senders and recipients of messages.
302 *
303 * We're protected against changing CPUs by either being in a high-priority
304 * interrupt, having preemption disabled or by having a raised SPL.
305 */
306 /*ARGSUSED*/
307 uint_t
308 xc_serv(caddr_t arg1, caddr_t arg2)
309 {
310 struct machcpu *mcpup = &(CPU->cpu_m);
311 xc_msg_t *msg;
312 xc_data_t *data;
313 xc_msg_t *xc_waiters = NULL;
314 uint32_t num_waiting = 0;
315 xc_func_t func;
316 xc_arg_t a1;
317 xc_arg_t a2;
318 xc_arg_t a3;
319 uint_t rc = DDI_INTR_UNCLAIMED;
320
321 while (mcpup->xc_work_cnt != 0) {
322 rc = DDI_INTR_CLAIMED;
323
324 /*
325 * We may have to wait for a message to arrive.
326 */
327 for (msg = NULL; msg == NULL;
328 msg = xc_extract(&mcpup->xc_msgbox)) {
329
330 /*
331 * Alway check for and handle a priority message.
332 */
333 if (BT_TEST(xc_priority_set, CPU->cpu_id)) {
334 func = xc_priority_data.xc_func;
335 a1 = xc_priority_data.xc_a1;
336 a2 = xc_priority_data.xc_a2;
337 a3 = xc_priority_data.xc_a3;
338 XC_BT_CLEAR(xc_priority_set, CPU->cpu_id);
339 xc_decrement(mcpup);
340 func(a1, a2, a3);
341 if (mcpup->xc_work_cnt == 0)
342 return (rc);
343 }
344
345 /*
346 * wait for a message to arrive
347 */
348 SMT_PAUSE();
349 }
350
351
352 /*
353 * process the message
354 */
355 switch (msg->xc_command) {
356
357 /*
358 * ASYNC gives back the message immediately, then we do the
359 * function and return with no more waiting.
360 */
361 case XC_MSG_ASYNC:
362 data = &cpu[msg->xc_master]->cpu_m.xc_data;
363 func = data->xc_func;
364 a1 = data->xc_a1;
365 a2 = data->xc_a2;
366 a3 = data->xc_a3;
367 msg->xc_command = XC_MSG_DONE;
368 xc_insert(&cpu[msg->xc_master]->cpu_m.xc_msgbox, msg);
369 if (func != NULL)
370 (void) (*func)(a1, a2, a3);
371 xc_decrement(mcpup);
372 break;
373
374 /*
375 * SYNC messages do the call, then send it back to the master
376 * in WAITING mode
377 */
378 case XC_MSG_SYNC:
379 data = &cpu[msg->xc_master]->cpu_m.xc_data;
380 if (data->xc_func != NULL)
381 (void) (*data->xc_func)(data->xc_a1,
382 data->xc_a2, data->xc_a3);
383 msg->xc_command = XC_MSG_WAITING;
384 xc_insert(&cpu[msg->xc_master]->cpu_m.xc_msgbox, msg);
385 break;
386
387 /*
388 * WAITING messsages are collected by the master until all
389 * have arrived. Once all arrive, we release them back to
390 * the slaves
391 */
392 case XC_MSG_WAITING:
393 xc_insert(&xc_waiters, msg);
394 if (++num_waiting < mcpup->xc_wait_cnt)
395 break;
396 while ((msg = xc_extract(&xc_waiters)) != NULL) {
397 msg->xc_command = XC_MSG_RELEASED;
398 xc_insert(&cpu[msg->xc_slave]->cpu_m.xc_msgbox,
399 msg);
400 --num_waiting;
401 }
402 if (num_waiting != 0)
403 panic("wrong number waiting");
404 mcpup->xc_wait_cnt = 0;
405 break;
406
407 /*
408 * CALL messages do the function and then, like RELEASE,
409 * send the message is back to master as DONE.
410 */
411 case XC_MSG_CALL:
412 data = &cpu[msg->xc_master]->cpu_m.xc_data;
413 if (data->xc_func != NULL)
414 (void) (*data->xc_func)(data->xc_a1,
415 data->xc_a2, data->xc_a3);
416 /*FALLTHROUGH*/
417 case XC_MSG_RELEASED:
418 msg->xc_command = XC_MSG_DONE;
419 xc_insert(&cpu[msg->xc_master]->cpu_m.xc_msgbox, msg);
420 xc_decrement(mcpup);
421 break;
422
423 /*
424 * DONE means a slave has completely finished up.
425 * Once we collect all the DONE messages, we'll exit
426 * processing too.
427 */
428 case XC_MSG_DONE:
429 msg->xc_command = XC_MSG_FREE;
430 xc_insert(&mcpup->xc_free, msg);
431 xc_decrement(mcpup);
432 break;
433
434 case XC_MSG_FREE:
435 panic("free message 0x%p in msgbox", (void *)msg);
436 break;
437
438 default:
439 panic("bad message 0x%p in msgbox", (void *)msg);
440 break;
441 }
442 }
443 return (rc);
444 }
445
446 /*
447 * Initiate cross call processing.
448 */
449 static void
450 xc_common(
451 xc_func_t func,
452 xc_arg_t arg1,
453 xc_arg_t arg2,
454 xc_arg_t arg3,
455 ulong_t *set,
456 uint_t command)
457 {
458 int c;
459 struct cpu *cpup;
460 xc_msg_t *msg;
461 xc_data_t *data;
462 int cnt;
463 int save_spl;
464
465 if (!xc_initialized) {
466 if (BT_TEST(set, CPU->cpu_id) && (CPU->cpu_flags & CPU_READY) &&
467 func != NULL)
468 (void) (*func)(arg1, arg2, arg3);
469 return;
470 }
471
472 save_spl = splr(ipltospl(XC_HI_PIL));
473
474 /*
475 * fill in cross call data
476 */
477 data = &CPU->cpu_m.xc_data;
478 data->xc_func = func;
479 data->xc_a1 = arg1;
480 data->xc_a2 = arg2;
481 data->xc_a3 = arg3;
482
483 /*
484 * Post messages to all CPUs involved that are CPU_READY
485 */
486 CPU->cpu_m.xc_wait_cnt = 0;
487 for (c = 0; c < max_ncpus; ++c) {
488 if (!BT_TEST(set, c))
489 continue;
490 cpup = cpu[c];
491 if (cpup == NULL || !(cpup->cpu_flags & CPU_READY))
492 continue;
493
494 /*
495 * Fill out a new message.
496 */
497 msg = xc_extract(&CPU->cpu_m.xc_free);
498 if (msg == NULL)
499 panic("Ran out of free xc_msg_t's");
500 msg->xc_command = command;
501 if (msg->xc_master != CPU->cpu_id)
502 panic("msg %p has wrong xc_master", (void *)msg);
503 msg->xc_slave = c;
504
505 /*
506 * Increment my work count for all messages that I'll
507 * transition from DONE to FREE.
508 * Also remember how many XC_MSG_WAITINGs to look for
509 */
510 (void) xc_increment(&CPU->cpu_m);
511 if (command == XC_MSG_SYNC)
512 ++CPU->cpu_m.xc_wait_cnt;
513
514 /*
515 * Increment the target CPU work count then insert the message
516 * in the target msgbox. If I post the first bit of work
517 * for the target to do, send an IPI to the target CPU.
518 */
519 cnt = xc_increment(&cpup->cpu_m);
520 xc_insert(&cpup->cpu_m.xc_msgbox, msg);
521 if (cpup != CPU) {
522 if (cnt == 0) {
523 CPU_STATS_ADDQ(CPU, sys, xcalls, 1);
524 send_dirint(c, XC_HI_PIL);
525 if (xc_collect_enable)
526 ++xc_total_cnt;
527 } else if (xc_collect_enable) {
528 ++xc_multi_cnt;
529 }
530 }
531 }
532
533 /*
534 * Now drop into the message handler until all work is done
535 */
536 (void) xc_serv(NULL, NULL);
537 splx(save_spl);
538 }
539
540 /*
541 * Push out a priority cross call.
542 */
543 static void
544 xc_priority_common(
545 xc_func_t func,
546 xc_arg_t arg1,
547 xc_arg_t arg2,
548 xc_arg_t arg3,
549 ulong_t *set)
550 {
551 int i;
552 int c;
553 struct cpu *cpup;
554
555 /*
556 * Wait briefly for any previous xc_priority to have finished.
557 */
558 for (c = 0; c < max_ncpus; ++c) {
559 cpup = cpu[c];
560 if (cpup == NULL || !(cpup->cpu_flags & CPU_READY))
561 continue;
562
563 /*
564 * The value of 40000 here is from old kernel code. It
565 * really should be changed to some time based value, since
566 * under a hypervisor, there's no guarantee a remote CPU
567 * is even scheduled.
568 */
569 for (i = 0; BT_TEST(xc_priority_set, c) && i < 40000; ++i)
570 SMT_PAUSE();
571
572 /*
573 * Some CPU did not respond to a previous priority request. It's
574 * probably deadlocked with interrupts blocked or some such
575 * problem. We'll just erase the previous request - which was
576 * most likely a kmdb_enter that has already expired - and plow
577 * ahead.
578 */
579 if (BT_TEST(xc_priority_set, c)) {
580 XC_BT_CLEAR(xc_priority_set, c);
581 if (cpup->cpu_m.xc_work_cnt > 0)
582 xc_decrement(&cpup->cpu_m);
583 }
584 }
585
586 /*
587 * fill in cross call data
588 */
589 xc_priority_data.xc_func = func;
590 xc_priority_data.xc_a1 = arg1;
591 xc_priority_data.xc_a2 = arg2;
592 xc_priority_data.xc_a3 = arg3;
593
594 /*
595 * Post messages to all CPUs involved that are CPU_READY
596 * We'll always IPI, plus bang on the xc_msgbox for i86_mwait()
597 */
598 for (c = 0; c < max_ncpus; ++c) {
599 if (!BT_TEST(set, c))
600 continue;
601 cpup = cpu[c];
602 if (cpup == NULL || !(cpup->cpu_flags & CPU_READY) ||
603 cpup == CPU)
604 continue;
605 (void) xc_increment(&cpup->cpu_m);
606 XC_BT_SET(xc_priority_set, c);
607 send_dirint(c, XC_HI_PIL);
608 for (i = 0; i < 10; ++i) {
609 (void) atomic_cas_ptr(&cpup->cpu_m.xc_msgbox,
610 cpup->cpu_m.xc_msgbox, cpup->cpu_m.xc_msgbox);
611 }
612 }
613 }
614
615 /*
616 * Do cross call to all other CPUs with absolutely no waiting or handshaking.
617 * This should only be used for extraordinary operations, like panic(), which
618 * need to work, in some fashion, in a not completely functional system.
619 * All other uses that want minimal waiting should use xc_call_nowait().
620 */
621 void
622 xc_priority(
623 xc_arg_t arg1,
624 xc_arg_t arg2,
625 xc_arg_t arg3,
626 ulong_t *set,
627 xc_func_t func)
628 {
629 extern int IGNORE_KERNEL_PREEMPTION;
630 int save_spl = splr(ipltospl(XC_HI_PIL));
631 int save_kernel_preemption = IGNORE_KERNEL_PREEMPTION;
632
633 IGNORE_KERNEL_PREEMPTION = 1;
634 xc_priority_common((xc_func_t)func, arg1, arg2, arg3, set);
635 IGNORE_KERNEL_PREEMPTION = save_kernel_preemption;
636 splx(save_spl);
637 }
638
639 /*
640 * Wrapper for kmdb to capture other CPUs, causing them to enter the debugger.
641 */
642 void
643 kdi_xc_others(int this_cpu, void (*func)(void))
644 {
645 extern int IGNORE_KERNEL_PREEMPTION;
646 int save_kernel_preemption;
647 cpuset_t set;
648
649 if (!xc_initialized)
650 return;
651
652 save_kernel_preemption = IGNORE_KERNEL_PREEMPTION;
653 IGNORE_KERNEL_PREEMPTION = 1;
654 CPUSET_ALL_BUT(set, this_cpu);
655 xc_priority_common((xc_func_t)func, 0, 0, 0, CPUSET2BV(set));
656 IGNORE_KERNEL_PREEMPTION = save_kernel_preemption;
657 }
658
659
660
661 /*
662 * Invoke function on specified processors. Remotes may continue after
663 * service with no waiting. xc_call_nowait() may return immediately too.
664 */
665 void
666 xc_call_nowait(
667 xc_arg_t arg1,
668 xc_arg_t arg2,
669 xc_arg_t arg3,
670 ulong_t *set,
671 xc_func_t func)
672 {
673 xc_common(func, arg1, arg2, arg3, set, XC_MSG_ASYNC);
674 }
675
676 /*
677 * Invoke function on specified processors. Remotes may continue after
678 * service with no waiting. xc_call() returns only after remotes have finished.
679 */
680 void
681 xc_call(
682 xc_arg_t arg1,
683 xc_arg_t arg2,
684 xc_arg_t arg3,
685 ulong_t *set,
686 xc_func_t func)
687 {
688 xc_common(func, arg1, arg2, arg3, set, XC_MSG_CALL);
689 }
690
691 /*
692 * Invoke function on specified processors. Remotes wait until all have
693 * finished. xc_sync() also waits until all remotes have finished.
694 */
695 void
696 xc_sync(
697 xc_arg_t arg1,
698 xc_arg_t arg2,
699 xc_arg_t arg3,
700 ulong_t *set,
701 xc_func_t func)
702 {
703 xc_common(func, arg1, arg2, arg3, set, XC_MSG_SYNC);
704 }