1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2018 Intel Corporation
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted providing that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
19 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
23 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30
31 #include <sys/types.h>
32 #include <sys/sbuf.h>
33 #include <sys/module.h>
34 #include <sys/systm.h>
35 #include <sys/errno.h>
36 #include <sys/param.h>
37 #include <sys/kernel.h>
38 #include <sys/bus.h>
39 #include <sys/cpu.h>
40 #include <sys/smp.h>
41 #include <sys/proc.h>
42 #include <sys/sched.h>
43
44 #include <machine/cpu.h>
45 #include <machine/md_var.h>
46 #include <machine/cputypes.h>
47 #include <machine/specialreg.h>
48
49 #include <contrib/dev/acpica/include/acpi.h>
50
51 #include <dev/acpica/acpivar.h>
52
53 #include <x86/cpufreq/hwpstate_intel_internal.h>
54
55 #include "acpi_if.h"
56 #include "cpufreq_if.h"
57
58 extern uint64_t tsc_freq;
59
60 static int intel_hwpstate_probe(device_t dev);
61 static int intel_hwpstate_attach(device_t dev);
62 static int intel_hwpstate_detach(device_t dev);
63 static int intel_hwpstate_suspend(device_t dev);
64 static int intel_hwpstate_resume(device_t dev);
65
66 static int intel_hwpstate_get(device_t dev, struct cf_setting *cf);
67 static int intel_hwpstate_type(device_t dev, int *type);
68
69 static device_method_t intel_hwpstate_methods[] = {
70 /* Device interface */
71 DEVMETHOD(device_identify, intel_hwpstate_identify),
72 DEVMETHOD(device_probe, intel_hwpstate_probe),
73 DEVMETHOD(device_attach, intel_hwpstate_attach),
74 DEVMETHOD(device_detach, intel_hwpstate_detach),
75 DEVMETHOD(device_suspend, intel_hwpstate_suspend),
76 DEVMETHOD(device_resume, intel_hwpstate_resume),
77
78 /* cpufreq interface */
79 DEVMETHOD(cpufreq_drv_get, intel_hwpstate_get),
80 DEVMETHOD(cpufreq_drv_type, intel_hwpstate_type),
81
82 DEVMETHOD_END
83 };
84
85 struct hwp_softc {
86 device_t dev;
87 bool hwp_notifications;
88 bool hwp_activity_window;
89 bool hwp_pref_ctrl;
90 bool hwp_pkg_ctrl;
91 bool hwp_pkg_ctrl_en;
92 bool hwp_perf_bias;
93 bool hwp_perf_bias_cached;
94
95 uint64_t req; /* Cached copy of HWP_REQUEST */
96 uint64_t hwp_energy_perf_bias; /* Cache PERF_BIAS */
97
98 uint8_t high;
99 uint8_t guaranteed;
100 uint8_t efficient;
101 uint8_t low;
102 };
103
104 static devclass_t hwpstate_intel_devclass;
105 static driver_t hwpstate_intel_driver = {
106 "hwpstate_intel",
107 intel_hwpstate_methods,
108 sizeof(struct hwp_softc),
109 };
110
111 DRIVER_MODULE(hwpstate_intel, cpu, hwpstate_intel_driver,
112 hwpstate_intel_devclass, NULL, NULL);
113 MODULE_VERSION(hwpstate_intel, 1);
114
115 static bool hwpstate_pkg_ctrl_enable = true;
116 SYSCTL_BOOL(_machdep, OID_AUTO, hwpstate_pkg_ctrl, CTLFLAG_RDTUN,
117 &hwpstate_pkg_ctrl_enable, 0,
118 "Set 1 (default) to enable package-level control, 0 to disable");
119
120 static int
121 intel_hwp_dump_sysctl_handler(SYSCTL_HANDLER_ARGS)
122 {
123 device_t dev;
124 struct pcpu *pc;
125 struct sbuf *sb;
126 struct hwp_softc *sc;
127 uint64_t data, data2;
128 int ret;
129
130 sc = (struct hwp_softc *)arg1;
131 dev = sc->dev;
132
133 pc = cpu_get_pcpu(dev);
134 if (pc == NULL)
135 return (ENXIO);
136
137 sb = sbuf_new(NULL, NULL, 1024, SBUF_FIXEDLEN | SBUF_INCLUDENUL);
138 sbuf_putc(sb, '\n');
139 thread_lock(curthread);
140 sched_bind(curthread, pc->pc_cpuid);
141 thread_unlock(curthread);
142
143 rdmsr_safe(MSR_IA32_PM_ENABLE, &data);
144 sbuf_printf(sb, "CPU%d: HWP %sabled\n", pc->pc_cpuid,
145 ((data & 1) ? "En" : "Dis"));
146
147 if (data == 0) {
148 ret = 0;
149 goto out;
150 }
151
152 rdmsr_safe(MSR_IA32_HWP_CAPABILITIES, &data);
153 sbuf_printf(sb, "\tHighest Performance: %03ju\n", data & 0xff);
154 sbuf_printf(sb, "\tGuaranteed Performance: %03ju\n", (data >> 8) & 0xff);
155 sbuf_printf(sb, "\tEfficient Performance: %03ju\n", (data >> 16) & 0xff);
156 sbuf_printf(sb, "\tLowest Performance: %03ju\n", (data >> 24) & 0xff);
157
158 rdmsr_safe(MSR_IA32_HWP_REQUEST, &data);
159 data2 = 0;
160 if (sc->hwp_pkg_ctrl && (data & IA32_HWP_REQUEST_PACKAGE_CONTROL))
161 rdmsr_safe(MSR_IA32_HWP_REQUEST_PKG, &data2);
162
163 sbuf_putc(sb, '\n');
164
165 #define pkg_print(x, name, offset) do { \
166 if (!sc->hwp_pkg_ctrl || (data & x) != 0) \
167 sbuf_printf(sb, "\t%s: %03u\n", name, \
168 (unsigned)(data >> offset) & 0xff); \
169 else \
170 sbuf_printf(sb, "\t%s: %03u\n", name, \
171 (unsigned)(data2 >> offset) & 0xff); \
172 } while (0)
173
174 pkg_print(IA32_HWP_REQUEST_EPP_VALID,
175 "Requested Efficiency Performance Preference", 24);
176 pkg_print(IA32_HWP_REQUEST_DESIRED_VALID,
177 "Requested Desired Performance", 16);
178 pkg_print(IA32_HWP_REQUEST_MAXIMUM_VALID,
179 "Requested Maximum Performance", 8);
180 pkg_print(IA32_HWP_REQUEST_MINIMUM_VALID,
181 "Requested Minimum Performance", 0);
182 #undef pkg_print
183
184 sbuf_putc(sb, '\n');
185
186 out:
187 thread_lock(curthread);
188 sched_unbind(curthread);
189 thread_unlock(curthread);
190
191 ret = sbuf_finish(sb);
192 if (ret == 0)
193 ret = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb));
194 sbuf_delete(sb);
195
196 return (ret);
197 }
198
199 static inline int
200 percent_to_raw(int x)
201 {
202
203 MPASS(x <= 100 && x >= 0);
204 return (0xff * x / 100);
205 }
206
207 /*
208 * Given x * 10 in [0, 1000], round to the integer nearest x.
209 *
210 * This allows round-tripping nice human readable numbers through this
211 * interface. Otherwise, user-provided percentages such as 25, 50, 75 get
212 * rounded down to 24, 49, and 74, which is a bit ugly.
213 */
214 static inline int
215 round10(int xtimes10)
216 {
217 return ((xtimes10 + 5) / 10);
218 }
219
220 static inline int
221 raw_to_percent(int x)
222 {
223 MPASS(x <= 0xff && x >= 0);
224 return (round10(x * 1000 / 0xff));
225 }
226
227 /* Range of MSR_IA32_ENERGY_PERF_BIAS is more limited: 0-0xf. */
228 static inline int
229 percent_to_raw_perf_bias(int x)
230 {
231 /*
232 * Round up so that raw values present as nice round human numbers and
233 * also round-trip to the same raw value.
234 */
235 MPASS(x <= 100 && x >= 0);
236 return (((0xf * x) + 50) / 100);
237 }
238
239 static inline int
240 raw_to_percent_perf_bias(int x)
241 {
242 /* Rounding to nice human numbers despite a step interval of 6.67%. */
243 MPASS(x <= 0xf && x >= 0);
244 return (((x * 20) / 0xf) * 5);
245 }
246
247 static int
248 sysctl_epp_select(SYSCTL_HANDLER_ARGS)
249 {
250 struct hwp_softc *sc;
251 device_t dev;
252 struct pcpu *pc;
253 uint64_t epb;
254 uint32_t val;
255 int ret;
256
257 dev = oidp->oid_arg1;
258 sc = device_get_softc(dev);
259 if (!sc->hwp_pref_ctrl && !sc->hwp_perf_bias)
260 return (ENODEV);
261
262 pc = cpu_get_pcpu(dev);
263 if (pc == NULL)
264 return (ENXIO);
265
266 thread_lock(curthread);
267 sched_bind(curthread, pc->pc_cpuid);
268 thread_unlock(curthread);
269
270 if (sc->hwp_pref_ctrl) {
271 val = (sc->req & IA32_HWP_REQUEST_ENERGY_PERFORMANCE_PREFERENCE) >> 24;
272 val = raw_to_percent(val);
273 } else {
274 /*
275 * If cpuid indicates EPP is not supported, the HWP controller
276 * uses MSR_IA32_ENERGY_PERF_BIAS instead (Intel SDM §14.4.4).
277 * This register is per-core (but not HT).
278 */
279 if (!sc->hwp_perf_bias_cached) {
280 ret = rdmsr_safe(MSR_IA32_ENERGY_PERF_BIAS, &epb);
281 if (ret)
282 goto out;
283 sc->hwp_energy_perf_bias = epb;
284 sc->hwp_perf_bias_cached = true;
285 }
286 val = sc->hwp_energy_perf_bias &
287 IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK;
288 val = raw_to_percent_perf_bias(val);
289 }
290
291 MPASS(val >= 0 && val <= 100);
292
293 ret = sysctl_handle_int(oidp, &val, 0, req);
294 if (ret || req->newptr == NULL)
295 goto out;
296
297 if (val > 100) {
298 ret = EINVAL;
299 goto out;
300 }
301
302 if (sc->hwp_pref_ctrl) {
303 val = percent_to_raw(val);
304
305 sc->req =
306 ((sc->req & ~IA32_HWP_REQUEST_ENERGY_PERFORMANCE_PREFERENCE)
307 | (val << 24u));
308
309 if (sc->hwp_pkg_ctrl_en)
310 ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req);
311 else
312 ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req);
313 } else {
314 val = percent_to_raw_perf_bias(val);
315 MPASS((val & ~IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK) == 0);
316
317 sc->hwp_energy_perf_bias =
318 ((sc->hwp_energy_perf_bias &
319 ~IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK) | val);
320 ret = wrmsr_safe(MSR_IA32_ENERGY_PERF_BIAS,
321 sc->hwp_energy_perf_bias);
322 }
323
324 out:
325 thread_lock(curthread);
326 sched_unbind(curthread);
327 thread_unlock(curthread);
328
329 return (ret);
330 }
331
332 void
333 intel_hwpstate_identify(driver_t *driver, device_t parent)
334 {
335 if (device_find_child(parent, "hwpstate_intel", -1) != NULL)
336 return;
337
338 if (cpu_vendor_id != CPU_VENDOR_INTEL)
339 return;
340
341 if (resource_disabled("hwpstate_intel", 0))
342 return;
343
344 /*
345 * Intel SDM 14.4.1 (HWP Programming Interfaces):
346 * Availability of HWP baseline resource and capability,
347 * CPUID.06H:EAX[bit 7]: If this bit is set, HWP provides several new
348 * architectural MSRs: IA32_PM_ENABLE, IA32_HWP_CAPABILITIES,
349 * IA32_HWP_REQUEST, IA32_HWP_STATUS.
350 */
351 if ((cpu_power_eax & CPUTPM1_HWP) == 0)
352 return;
353
354 if (BUS_ADD_CHILD(parent, 10, "hwpstate_intel", -1) == NULL)
355 return;
356
357 if (bootverbose)
358 device_printf(parent, "hwpstate registered\n");
359 }
360
361 static int
362 intel_hwpstate_probe(device_t dev)
363 {
364
365 device_set_desc(dev, "Intel Speed Shift");
366 return (BUS_PROBE_NOWILDCARD);
367 }
368
369 static int
370 set_autonomous_hwp(struct hwp_softc *sc)
371 {
372 struct pcpu *pc;
373 device_t dev;
374 uint64_t caps;
375 int ret;
376
377 dev = sc->dev;
378
379 pc = cpu_get_pcpu(dev);
380 if (pc == NULL)
381 return (ENXIO);
382
383 thread_lock(curthread);
384 sched_bind(curthread, pc->pc_cpuid);
385 thread_unlock(curthread);
386
387 /* XXX: Many MSRs aren't readable until feature is enabled */
388 ret = wrmsr_safe(MSR_IA32_PM_ENABLE, 1);
389 if (ret) {
390 /*
391 * This is actually a package-level MSR, and only the first
392 * write is not ignored. So it is harmless to enable it across
393 * all devices, and this allows us not to care especially in
394 * which order cores (and packages) are probed. This error
395 * condition should not happen given we gate on the HWP CPUID
396 * feature flag, if the Intel SDM is correct.
397 */
398 device_printf(dev, "Failed to enable HWP for cpu%d (%d)\n",
399 pc->pc_cpuid, ret);
400 goto out;
401 }
402
403 ret = rdmsr_safe(MSR_IA32_HWP_REQUEST, &sc->req);
404 if (ret) {
405 device_printf(dev,
406 "Failed to read HWP request MSR for cpu%d (%d)\n",
407 pc->pc_cpuid, ret);
408 goto out;
409 }
410
411 ret = rdmsr_safe(MSR_IA32_HWP_CAPABILITIES, &caps);
412 if (ret) {
413 device_printf(dev,
414 "Failed to read HWP capabilities MSR for cpu%d (%d)\n",
415 pc->pc_cpuid, ret);
416 goto out;
417 }
418
419 /*
420 * High and low are static; "guaranteed" is dynamic; and efficient is
421 * also dynamic.
422 */
423 sc->high = IA32_HWP_CAPABILITIES_HIGHEST_PERFORMANCE(caps);
424 sc->guaranteed = IA32_HWP_CAPABILITIES_GUARANTEED_PERFORMANCE(caps);
425 sc->efficient = IA32_HWP_CAPABILITIES_EFFICIENT_PERFORMANCE(caps);
426 sc->low = IA32_HWP_CAPABILITIES_LOWEST_PERFORMANCE(caps);
427
428 /* hardware autonomous selection determines the performance target */
429 sc->req &= ~IA32_HWP_DESIRED_PERFORMANCE;
430
431 /* enable HW dynamic selection of window size */
432 sc->req &= ~IA32_HWP_ACTIVITY_WINDOW;
433
434 /* IA32_HWP_REQUEST.Minimum_Performance = IA32_HWP_CAPABILITIES.Lowest_Performance */
435 sc->req &= ~IA32_HWP_MINIMUM_PERFORMANCE;
436 sc->req |= sc->low;
437
438 /* IA32_HWP_REQUEST.Maximum_Performance = IA32_HWP_CAPABILITIES.Highest_Performance. */
439 sc->req &= ~IA32_HWP_REQUEST_MAXIMUM_PERFORMANCE;
440 sc->req |= sc->high << 8;
441
442 /* If supported, request package-level control for this CPU. */
443 if (sc->hwp_pkg_ctrl_en)
444 ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req |
445 IA32_HWP_REQUEST_PACKAGE_CONTROL);
446 else
447 ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req);
448 if (ret) {
449 device_printf(dev,
450 "Failed to setup%s autonomous HWP for cpu%d\n",
451 sc->hwp_pkg_ctrl_en ? " PKG" : "", pc->pc_cpuid);
452 goto out;
453 }
454
455 /* If supported, write the PKG-wide control MSR. */
456 if (sc->hwp_pkg_ctrl_en) {
457 /*
458 * "The structure of the IA32_HWP_REQUEST_PKG MSR
459 * (package-level) is identical to the IA32_HWP_REQUEST MSR
460 * with the exception of the Package Control field, which does
461 * not exist." (Intel SDM §14.4.4)
462 */
463 ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req);
464 if (ret) {
465 device_printf(dev,
466 "Failed to set autonomous HWP for package\n");
467 }
468 }
469
470 out:
471 thread_lock(curthread);
472 sched_unbind(curthread);
473 thread_unlock(curthread);
474
475 return (ret);
476 }
477
478 static int
479 intel_hwpstate_attach(device_t dev)
480 {
481 struct hwp_softc *sc;
482 int ret;
483
484 sc = device_get_softc(dev);
485 sc->dev = dev;
486
487 /* eax */
488 if (cpu_power_eax & CPUTPM1_HWP_NOTIFICATION)
489 sc->hwp_notifications = true;
490 if (cpu_power_eax & CPUTPM1_HWP_ACTIVITY_WINDOW)
491 sc->hwp_activity_window = true;
492 if (cpu_power_eax & CPUTPM1_HWP_PERF_PREF)
493 sc->hwp_pref_ctrl = true;
494 if (cpu_power_eax & CPUTPM1_HWP_PKG)
495 sc->hwp_pkg_ctrl = true;
496
497 /* Allow administrators to disable pkg-level control. */
498 sc->hwp_pkg_ctrl_en = (sc->hwp_pkg_ctrl && hwpstate_pkg_ctrl_enable);
499
500 /* ecx */
501 if (cpu_power_ecx & CPUID_PERF_BIAS)
502 sc->hwp_perf_bias = true;
503
504 ret = set_autonomous_hwp(sc);
505 if (ret)
506 return (ret);
507
508 SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
509 SYSCTL_STATIC_CHILDREN(_debug), OID_AUTO, device_get_nameunit(dev),
510 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_SKIP | CTLFLAG_NEEDGIANT,
511 sc, 0, intel_hwp_dump_sysctl_handler, "A", "");
512
513 SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
514 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
515 "epp", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, dev, 0,
516 sysctl_epp_select, "I",
517 "Efficiency/Performance Preference "
518 "(range from 0, most performant, through 100, most efficient)");
519
520 return (cpufreq_register(dev));
521 }
522
523 static int
524 intel_hwpstate_detach(device_t dev)
525 {
526
527 return (cpufreq_unregister(dev));
528 }
529
530 static int
531 intel_hwpstate_get(device_t dev, struct cf_setting *set)
532 {
533 struct pcpu *pc;
534 uint64_t rate;
535 int ret;
536
537 if (set == NULL)
538 return (EINVAL);
539
540 pc = cpu_get_pcpu(dev);
541 if (pc == NULL)
542 return (ENXIO);
543
544 memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set));
545 set->dev = dev;
546
547 ret = cpu_est_clockrate(pc->pc_cpuid, &rate);
548 if (ret == 0)
549 set->freq = rate / 1000000;
550
551 set->volts = CPUFREQ_VAL_UNKNOWN;
552 set->power = CPUFREQ_VAL_UNKNOWN;
553 set->lat = CPUFREQ_VAL_UNKNOWN;
554
555 return (0);
556 }
557
558 static int
559 intel_hwpstate_type(device_t dev, int *type)
560 {
561 if (type == NULL)
562 return (EINVAL);
563 *type = CPUFREQ_TYPE_ABSOLUTE | CPUFREQ_FLAG_INFO_ONLY | CPUFREQ_FLAG_UNCACHED;
564
565 return (0);
566 }
567
568 static int
569 intel_hwpstate_suspend(device_t dev)
570 {
571 return (0);
572 }
573
574 /*
575 * Redo a subset of set_autonomous_hwp on resume; untested. Without this,
576 * testers observed that on resume MSR_IA32_HWP_REQUEST was bogus.
577 */
578 static int
579 intel_hwpstate_resume(device_t dev)
580 {
581 struct hwp_softc *sc;
582 struct pcpu *pc;
583 int ret;
584
585 sc = device_get_softc(dev);
586
587 pc = cpu_get_pcpu(dev);
588 if (pc == NULL)
589 return (ENXIO);
590
591 thread_lock(curthread);
592 sched_bind(curthread, pc->pc_cpuid);
593 thread_unlock(curthread);
594
595 ret = wrmsr_safe(MSR_IA32_PM_ENABLE, 1);
596 if (ret) {
597 device_printf(dev,
598 "Failed to enable HWP for cpu%d after suspend (%d)\n",
599 pc->pc_cpuid, ret);
600 goto out;
601 }
602
603 if (sc->hwp_pkg_ctrl_en)
604 ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req |
605 IA32_HWP_REQUEST_PACKAGE_CONTROL);
606 else
607 ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req);
608 if (ret) {
609 device_printf(dev,
610 "Failed to set%s autonomous HWP for cpu%d after suspend\n",
611 sc->hwp_pkg_ctrl_en ? " PKG" : "", pc->pc_cpuid);
612 goto out;
613 }
614 if (sc->hwp_pkg_ctrl_en) {
615 ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req);
616 if (ret) {
617 device_printf(dev,
618 "Failed to set autonomous HWP for package after "
619 "suspend\n");
620 goto out;
621 }
622 }
623 if (!sc->hwp_pref_ctrl && sc->hwp_perf_bias_cached) {
624 ret = wrmsr_safe(MSR_IA32_ENERGY_PERF_BIAS,
625 sc->hwp_energy_perf_bias);
626 if (ret) {
627 device_printf(dev,
628 "Failed to set energy perf bias for cpu%d after "
629 "suspend\n", pc->pc_cpuid);
630 }
631 }
632
633 out:
634 thread_lock(curthread);
635 sched_unbind(curthread);
636 thread_unlock(curthread);
637
638 return (ret);
639 }
Cache object: c3d90056359272c29b49cb32cbf0f8c5
|