diff options
author | cvs2svn <cvs2svn@FreeBSD.org> | 2005-02-25 10:38:44 +0000 |
---|---|---|
committer | cvs2svn <cvs2svn@FreeBSD.org> | 2005-02-25 10:38:44 +0000 |
commit | c4170850986b6d1be9c0bc370be680c7d3fb8fb5 (patch) | |
tree | 9dcc274c86477584c21016c9090b0fda5d1b2020 /sys | |
parent | c1f4a77f7b0082c96a9f79a39a19f7870d2cfe7d (diff) |
Notes
Diffstat (limited to 'sys')
-rw-r--r-- | sys/compat/ndis/subr_usbd.c | 156 | ||||
-rw-r--r-- | sys/compat/ndis/usbd_var.h | 56 | ||||
-rw-r--r-- | sys/dev/acpica/acpi_throttle.c | 426 | ||||
-rw-r--r-- | sys/kern/kern_cpu.c | 852 | ||||
-rw-r--r-- | sys/netinet/ip_carp.h | 163 | ||||
-rw-r--r-- | sys/sys/cpu.h | 131 |
6 files changed, 1784 insertions, 0 deletions
diff --git a/sys/compat/ndis/subr_usbd.c b/sys/compat/ndis/subr_usbd.c new file mode 100644 index 0000000000000..a9b30e4a40f66 --- /dev/null +++ b/sys/compat/ndis/subr_usbd.c @@ -0,0 +1,156 @@ +/*- + * Copyright (c) 2005 + * Bill Paul <wpaul@windriver.com>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/unistd.h> +#include <sys/types.h> + +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/module.h> +#include <sys/conf.h> +#include <sys/mbuf.h> +#include <sys/bus.h> + +#include <sys/queue.h> + +#include <compat/ndis/pe_var.h> +#include <compat/ndis/cfg_var.h> +#include <compat/ndis/resource_var.h> +#include <compat/ndis/ntoskrnl_var.h> +#include <compat/ndis/ndis_var.h> +#include <compat/ndis/hal_var.h> +#include <compat/ndis/usbd_var.h> + +static driver_object usbd_driver; + +__stdcall static uint32_t usbd_iodispatch(device_object *, irp *); + +__stdcall static void USBD_GetUSBDIVersion(usbd_version_info *); +__stdcall static void dummy(void); + +int +usbd_libinit(void) +{ + image_patch_table *patch; + + patch = usbd_functbl; + while (patch->ipt_func != NULL) { + windrv_wrap((funcptr)patch->ipt_func, + (funcptr *)&patch->ipt_wrap); + patch++; + } + + /* Create a fake USB driver instance. */ + + windrv_bus_attach(&usbd_driver, "USB Bus"); + + /* Set up our dipatch routine. */ + + usbd_driver.dro_dispatch[IRP_MJ_INTERNAL_DEVICE_CONTROL] = + (driver_dispatch)usbd_iodispatch; + + return(0); +} + +int +usbd_libfini(void) +{ + image_patch_table *patch; + + patch = usbd_functbl; + while (patch->ipt_func != NULL) { + windrv_unwrap(patch->ipt_wrap); + patch++; + } + + free(usbd_driver.dro_drivername.us_buf, M_DEVBUF); + + return(0); +} + +__stdcall static uint32_t +usbd_iodispatch(dobj, ip) + device_object *dobj; + irp *ip; +{ + return(0); +} + +__stdcall static void +USBD_GetUSBDIVersion(ui) + usbd_version_info *ui; +{ + /* Pretend to be Windows XP. */ + + ui->uvi_usbdi_vers = USBDI_VERSION; + ui->uvi_supported_vers = USB_VER_2_0; + + return; +} + +__stdcall static void +dummy(void) +{ + printf("USBD dummy called\n"); + return; +} + +image_patch_table usbd_functbl[] = { + IMPORT_FUNC(USBD_GetUSBDIVersion), +#ifdef notyet + IMPORT_FUNC_MAP(_USBD_ParseConfigurationDescriptorEx@28, + USBD_ParseConfigurationDescriptorEx), + IMPORT_FUNC_MAP(_USBD_CreateConfigurationRequestEx@8, + USBD_CreateConfigurationRequestEx), +#endif + + /* + * This last entry is a catch-all for any function we haven't + * implemented yet. The PE import list patching routine will + * use it for any function that doesn't have an explicit match + * in this table. + */ + + { NULL, (FUNC)dummy, NULL }, + + /* End of list. */ + + { NULL, NULL, NULL } +}; + diff --git a/sys/compat/ndis/usbd_var.h b/sys/compat/ndis/usbd_var.h new file mode 100644 index 0000000000000..8c9f2b3a5854c --- /dev/null +++ b/sys/compat/ndis/usbd_var.h @@ -0,0 +1,56 @@ +/*- + * Copyright (c) 2003 + * Bill Paul <wpaul@windriver.com>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _USBD_VAR_H_ +#define _USBD_VAR_H_ + +#define USBDI_VERSION 0x00000500 +#define USB_VER_1_1 0x00000110 +#define USB_VER_2_0 0x00000200 + +struct usbd_version_info { + uint32_t uvi_usbdi_vers; + uint32_t uvi_supported_vers; +}; + +typedef struct usbd_version_info usbd_version_info; + +extern image_patch_table usbd_functbl[]; + +__BEGIN_DECLS +extern int usbd_libinit(void); +extern int usbd_libfini(void); +__END_DECLS + +#endif /* _USBD_VAR_H_ */ diff --git a/sys/dev/acpica/acpi_throttle.c b/sys/dev/acpica/acpi_throttle.c new file mode 100644 index 0000000000000..b736124db61e1 --- /dev/null +++ b/sys/dev/acpica/acpi_throttle.c @@ -0,0 +1,426 @@ +/*- + * Copyright (c) 2003-2005 Nate Lawson (SDG) + * Copyright (c) 2001 Michael Smith + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/cpu.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/rman.h> + +#include <machine/bus.h> + +#include "acpi.h" +#include <dev/acpica/acpivar.h> +#include <dev/pci/pcivar.h> + +#include "cpufreq_if.h" + +/* + * Throttling provides relative frequency control. It involves modulating + * the clock so that the CPU is active for only a fraction of the normal + * clock cycle. It does not change voltage and so is less efficient than + * other mechanisms. Since it is relative, it can be used in addition to + * absolute cpufreq drivers. We support the ACPI 2.0 specification. + */ + +struct acpi_throttle_softc { + device_t cpu_dev; + ACPI_HANDLE cpu_handle; + uint32_t cpu_p_blk; /* ACPI P_BLK location */ + uint32_t cpu_p_blk_len; /* P_BLK length (must be 6). */ + struct resource *cpu_p_cnt; /* Throttling control register */ + int cpu_p_type; /* Resource type for cpu_p_cnt. */ + uint32_t cpu_thr_state; /* Current throttle setting. */ +}; + +#define THR_GET_REG(reg) \ + (bus_space_read_4(rman_get_bustag((reg)), \ + rman_get_bushandle((reg)), 0)) +#define THR_SET_REG(reg, val) \ + (bus_space_write_4(rman_get_bustag((reg)), \ + rman_get_bushandle((reg)), 0, (val))) + +/* + * Speeds are stored in counts, from 1 to CPU_MAX_SPEED, and + * reported to the user in hundredths of a percent. + */ +#define CPU_MAX_SPEED (1 << cpu_duty_width) +#define CPU_SPEED_PERCENT(x) ((10000 * (x)) / CPU_MAX_SPEED) +#define CPU_SPEED_PRINTABLE(x) (CPU_SPEED_PERCENT(x) / 10), \ + (CPU_SPEED_PERCENT(x) % 10) +#define CPU_P_CNT_THT_EN (1<<4) +#define CPU_QUIRK_NO_THROTTLE (1<<1) /* Throttling is not usable. */ + +#define PCI_VENDOR_INTEL 0x8086 +#define PCI_DEVICE_82371AB_3 0x7113 /* PIIX4 chipset for quirks. */ +#define PCI_REVISION_A_STEP 0 +#define PCI_REVISION_B_STEP 1 + +static uint32_t cpu_duty_offset; /* Offset in P_CNT of throttle val. */ +static uint32_t cpu_duty_width; /* Bit width of throttle value. */ +static int thr_rid; /* Driver-wide resource id. */ +static int thr_quirks; /* Indicate any hardware bugs. */ + +static void acpi_throttle_identify(driver_t *driver, device_t parent); +static int acpi_throttle_probe(device_t dev); +static int acpi_throttle_attach(device_t dev); +static int acpi_throttle_evaluate(struct acpi_throttle_softc *sc); +static int acpi_throttle_quirks(struct acpi_throttle_softc *sc); +static int acpi_thr_settings(device_t dev, struct cf_setting *sets, + int *count); +static int acpi_thr_set(device_t dev, const struct cf_setting *set); +static int acpi_thr_get(device_t dev, struct cf_setting *set); +static int acpi_thr_type(device_t dev, int *type); + +static device_method_t acpi_throttle_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, acpi_throttle_identify), + DEVMETHOD(device_probe, acpi_throttle_probe), + DEVMETHOD(device_attach, acpi_throttle_attach), + + /* cpufreq interface */ + DEVMETHOD(cpufreq_drv_set, acpi_thr_set), + DEVMETHOD(cpufreq_drv_get, acpi_thr_get), + DEVMETHOD(cpufreq_drv_type, acpi_thr_type), + DEVMETHOD(cpufreq_drv_settings, acpi_thr_settings), + {0, 0} +}; + +static driver_t acpi_throttle_driver = { + "acpi_throttle", + acpi_throttle_methods, + sizeof(struct acpi_throttle_softc), +}; + +static devclass_t acpi_throttle_devclass; +DRIVER_MODULE(acpi_throttle, cpu, acpi_throttle_driver, acpi_throttle_devclass, + 0, 0); + +static void +acpi_throttle_identify(driver_t *driver, device_t parent) +{ + ACPI_BUFFER buf; + ACPI_HANDLE handle; + ACPI_OBJECT *obj; + + /* Make sure we're not being doubly invoked. */ + if (device_find_child(parent, "acpi_throttle", -1) != NULL) + return; + + /* Check for a valid duty width and parent CPU type. */ + handle = acpi_get_handle(parent); + if (handle == NULL) + return; + if (AcpiGbl_FADT->DutyWidth == 0 || + acpi_get_type(parent) != ACPI_TYPE_PROCESSOR) + return; + + /* + * Add a child if there's a non-NULL P_BLK and correct length, or + * if the _PTC method is present. + */ + buf.Pointer = NULL; + buf.Length = ACPI_ALLOCATE_BUFFER; + if (ACPI_FAILURE(AcpiEvaluateObject(handle, NULL, NULL, &buf))) + return; + obj = (ACPI_OBJECT *)buf.Pointer; + if ((obj->Processor.PblkAddress && obj->Processor.PblkLength >= 4) || + ACPI_SUCCESS(AcpiEvaluateObject(handle, "_PTC", NULL, NULL))) { + if (BUS_ADD_CHILD(parent, 0, "acpi_throttle", -1) == NULL) + device_printf(parent, "add throttle child failed\n"); + } + AcpiOsFree(obj); +} + +static int +acpi_throttle_probe(device_t dev) +{ + + if (resource_disabled("acpi_throttle", 0)) + return (ENXIO); + + device_set_desc(dev, "ACPI CPU Throttling"); + return (0); +} + +static int +acpi_throttle_attach(device_t dev) +{ + struct acpi_throttle_softc *sc; + ACPI_BUFFER buf; + ACPI_OBJECT *obj; + ACPI_STATUS status; + int error; + + sc = device_get_softc(dev); + sc->cpu_dev = dev; + sc->cpu_handle = acpi_get_handle(dev); + + buf.Pointer = NULL; + buf.Length = ACPI_ALLOCATE_BUFFER; + status = AcpiEvaluateObject(sc->cpu_handle, NULL, NULL, &buf); + if (ACPI_FAILURE(status)) { + device_printf(dev, "attach failed to get Processor obj - %s\n", + AcpiFormatException(status)); + return (ENXIO); + } + obj = (ACPI_OBJECT *)buf.Pointer; + sc->cpu_p_blk = obj->Processor.PblkAddress; + sc->cpu_p_blk_len = obj->Processor.PblkLength; + AcpiOsFree(obj); + + /* If this is the first device probed, check for quirks. */ + if (device_get_unit(dev) == 0) + acpi_throttle_quirks(sc); + + /* Attempt to attach the actual throttling register. */ + error = acpi_throttle_evaluate(sc); + if (error) + return (error); + + /* Everything went ok, register with cpufreq(4). */ + cpufreq_register(dev); + return (0); +} + +static int +acpi_throttle_evaluate(struct acpi_throttle_softc *sc) +{ + uint32_t duty_end; + ACPI_BUFFER buf; + ACPI_OBJECT obj; + ACPI_GENERIC_ADDRESS gas; + ACPI_STATUS status; + + /* Get throttling parameters from the FADT. 0 means not supported. */ + if (device_get_unit(sc->cpu_dev) == 0) { + cpu_duty_offset = AcpiGbl_FADT->DutyOffset; + cpu_duty_width = AcpiGbl_FADT->DutyWidth; + } + if (cpu_duty_width == 0 || (thr_quirks & CPU_QUIRK_NO_THROTTLE) != 0) + return (ENXIO); + + /* Validate the duty offset/width. */ + duty_end = cpu_duty_offset + cpu_duty_width - 1; + if (duty_end > 31) { + device_printf(sc->cpu_dev, + "CLK_VAL field overflows P_CNT register\n"); + return (ENXIO); + } + if (cpu_duty_offset <= 4 && duty_end >= 4) { + device_printf(sc->cpu_dev, + "CLK_VAL field overlaps THT_EN bit\n"); + return (ENXIO); + } + + /* + * If not present, fall back to using the processor's P_BLK to find + * the P_CNT register. + * + * Note that some systems seem to duplicate the P_BLK pointer + * across multiple CPUs, so not getting the resource is not fatal. + */ + buf.Pointer = &obj; + buf.Length = sizeof(obj); + status = AcpiEvaluateObject(sc->cpu_handle, "_PTC", NULL, &buf); + if (ACPI_SUCCESS(status)) { + if (obj.Buffer.Length < sizeof(ACPI_GENERIC_ADDRESS) + 3) { + device_printf(sc->cpu_dev, "_PTC buffer too small\n"); + return (ENXIO); + } + memcpy(&gas, obj.Buffer.Pointer + 3, sizeof(gas)); + acpi_bus_alloc_gas(sc->cpu_dev, &sc->cpu_p_type, &thr_rid, + &gas, &sc->cpu_p_cnt); + if (sc->cpu_p_cnt != NULL && bootverbose) { + device_printf(sc->cpu_dev, "P_CNT from _PTC %#jx\n", + gas.Address); + } + } + + /* If _PTC not present or other failure, try the P_BLK. */ + if (sc->cpu_p_cnt == NULL) { + /* + * The spec says P_BLK must be 6 bytes long. However, some + * systems use it to indicate a fractional set of features + * present so we take anything >= 4. + */ + if (sc->cpu_p_blk_len < 4) + return (ENXIO); + gas.Address = sc->cpu_p_blk; + gas.AddressSpaceId = ACPI_ADR_SPACE_SYSTEM_IO; + gas.RegisterBitWidth = 32; + acpi_bus_alloc_gas(sc->cpu_dev, &sc->cpu_p_type, &thr_rid, + &gas, &sc->cpu_p_cnt); + if (sc->cpu_p_cnt != NULL) { + if (bootverbose) + device_printf(sc->cpu_dev, + "P_CNT from P_BLK %#x\n", sc->cpu_p_blk); + } else { + device_printf(sc->cpu_dev, "failed to attach P_CNT\n"); + return (ENXIO); + } + } + thr_rid++; + + return (0); +} + +static int +acpi_throttle_quirks(struct acpi_throttle_softc *sc) +{ + device_t acpi_dev; + + /* Look for various quirks of the PIIX4 part. */ + acpi_dev = pci_find_device(PCI_VENDOR_INTEL, PCI_DEVICE_82371AB_3); + if (acpi_dev) { + switch (pci_get_revid(acpi_dev)) { + /* + * Disable throttling control on PIIX4 A and B-step. + * See specification changes #13 ("Manual Throttle Duty Cycle") + * and #14 ("Enabling and Disabling Manual Throttle"), plus + * erratum #5 ("STPCLK# Deassertion Time") from the January + * 2002 PIIX4 specification update. Note that few (if any) + * mobile systems ever used this part. + */ + case PCI_REVISION_A_STEP: + case PCI_REVISION_B_STEP: + thr_quirks |= CPU_QUIRK_NO_THROTTLE; + break; + default: + break; + } + } + + return (0); +} + +static int +acpi_thr_settings(device_t dev, struct cf_setting *sets, int *count) +{ + struct acpi_throttle_softc *sc; + int i, speed; + + sc = device_get_softc(dev); + if (sets == NULL || count == NULL) + return (EINVAL); + if (*count < CPU_MAX_SPEED) + return (E2BIG); + + /* Return a list of valid settings for this driver. */ + memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * CPU_MAX_SPEED); + for (i = 0, speed = CPU_MAX_SPEED; speed != 0; i++, speed--) { + sets[i].freq = CPU_SPEED_PERCENT(speed); + sets[i].dev = dev; + } + *count = CPU_MAX_SPEED; + + return (0); +} + +static int +acpi_thr_set(device_t dev, const struct cf_setting *set) +{ + struct acpi_throttle_softc *sc; + uint32_t clk_val, p_cnt, speed; + + if (set == NULL) + return (EINVAL); + sc = device_get_softc(dev); + + /* + * Validate requested state converts to a duty cycle that is an + * integer from [1 .. CPU_MAX_SPEED]. + */ + speed = set->freq * CPU_MAX_SPEED / 10000; + if (speed * 10000 != set->freq * CPU_MAX_SPEED || + speed < 1 || speed > CPU_MAX_SPEED) + return (EINVAL); + + /* If we're at this setting, don't bother applying it again. */ + if (speed == sc->cpu_thr_state) + return (0); + + /* Get the current P_CNT value and disable throttling */ + p_cnt = THR_GET_REG(sc->cpu_p_cnt); + p_cnt &= ~CPU_P_CNT_THT_EN; + THR_SET_REG(sc->cpu_p_cnt, p_cnt); + + /* If we're at maximum speed, that's all */ + if (speed < CPU_MAX_SPEED) { + /* Mask the old CLK_VAL off and OR in the new value */ + clk_val = (CPU_MAX_SPEED - 1) << cpu_duty_offset; + p_cnt &= ~clk_val; + p_cnt |= (speed << cpu_duty_offset); + + /* Write the new P_CNT value and then enable throttling */ + THR_SET_REG(sc->cpu_p_cnt, p_cnt); + p_cnt |= CPU_P_CNT_THT_EN; + THR_SET_REG(sc->cpu_p_cnt, p_cnt); + } + sc->cpu_thr_state = speed; + + return (0); +} + +static int +acpi_thr_get(device_t dev, struct cf_setting *set) +{ + struct acpi_throttle_softc *sc; + uint32_t p_cnt, clk_val; + + if (set == NULL) + return (EINVAL); + sc = device_get_softc(dev); + + /* Get the current throttling setting from P_CNT. */ + p_cnt = THR_GET_REG(sc->cpu_p_cnt); + clk_val = (p_cnt >> cpu_duty_offset) & (CPU_MAX_SPEED - 1); + sc->cpu_thr_state = clk_val; + + memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set)); + set->freq = CPU_SPEED_PERCENT(clk_val); + set->dev = dev; + + return (0); +} + +static int +acpi_thr_type(device_t dev, int *type) +{ + + if (type == NULL) + return (EINVAL); + + *type = CPUFREQ_TYPE_RELATIVE; + return (0); +} diff --git a/sys/kern/kern_cpu.c b/sys/kern/kern_cpu.c new file mode 100644 index 0000000000000..355b258ee9167 --- /dev/null +++ b/sys/kern/kern_cpu.c @@ -0,0 +1,852 @@ +/*- + * Copyright (c) 2004-2005 Nate Lawson (SDG) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/cpu.h> +#include <sys/eventhandler.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/proc.h> +#include <sys/queue.h> +#include <sys/sched.h> +#include <sys/sysctl.h> +#include <sys/systm.h> +#include <sys/sbuf.h> +#include <sys/timetc.h> + +#include "cpufreq_if.h" + +/* + * Common CPU frequency glue code. Drivers for specific hardware can + * attach this interface to allow users to get/set the CPU frequency. + */ + +/* + * Number of levels we can handle. Levels are synthesized from settings + * so for M settings and N drivers, there may be M*N levels. + */ +#define CF_MAX_LEVELS 64 + +struct cpufreq_softc { + struct cf_level curr_level; + int curr_priority; + struct cf_level saved_level; + int saved_priority; + struct cf_level_lst all_levels; + int all_count; + int max_mhz; + device_t dev; + struct sysctl_ctx_list sysctl_ctx; +}; + +struct cf_setting_array { + struct cf_setting sets[MAX_SETTINGS]; + int count; + TAILQ_ENTRY(cf_setting_array) link; +}; + +TAILQ_HEAD(cf_setting_lst, cf_setting_array); + +static int cpufreq_attach(device_t dev); +static int cpufreq_detach(device_t dev); +static void cpufreq_evaluate(void *arg); +static int cf_set_method(device_t dev, const struct cf_level *level, + int priority); +static int cf_get_method(device_t dev, struct cf_level *level); +static int cf_levels_method(device_t dev, struct cf_level *levels, + int *count); +static int cpufreq_insert_abs(struct cpufreq_softc *sc, + struct cf_setting *sets, int count); +static int cpufreq_expand_set(struct cpufreq_softc *sc, + struct cf_setting_array *set_arr); +static struct cf_level *cpufreq_dup_set(struct cpufreq_softc *sc, + struct cf_level *dup, struct cf_setting *set); +static int cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS); +static int cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS); +static int cpufreq_settings_sysctl(SYSCTL_HANDLER_ARGS); + +static device_method_t cpufreq_methods[] = { + DEVMETHOD(device_probe, bus_generic_probe), + DEVMETHOD(device_attach, cpufreq_attach), + DEVMETHOD(device_detach, cpufreq_detach), + + DEVMETHOD(cpufreq_set, cf_set_method), + DEVMETHOD(cpufreq_get, cf_get_method), + DEVMETHOD(cpufreq_levels, cf_levels_method), + {0, 0} +}; +static driver_t cpufreq_driver = { + "cpufreq", cpufreq_methods, sizeof(struct cpufreq_softc) +}; +static devclass_t cpufreq_dc; +DRIVER_MODULE(cpufreq, cpu, cpufreq_driver, cpufreq_dc, 0, 0); + +static eventhandler_tag cf_ev_tag; + +static int +cpufreq_attach(device_t dev) +{ + struct cpufreq_softc *sc; + device_t parent; + int numdevs; + + sc = device_get_softc(dev); + parent = device_get_parent(dev); + sc->dev = dev; + sysctl_ctx_init(&sc->sysctl_ctx); + TAILQ_INIT(&sc->all_levels); + sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN; + sc->saved_level.total_set.freq = CPUFREQ_VAL_UNKNOWN; + sc->max_mhz = CPUFREQ_VAL_UNKNOWN; + + /* + * Only initialize one set of sysctls for all CPUs. In the future, + * if multiple CPUs can have different settings, we can move these + * sysctls to be under every CPU instead of just the first one. + */ + numdevs = devclass_get_count(cpufreq_dc); + if (numdevs > 1) + return (0); + + SYSCTL_ADD_PROC(&sc->sysctl_ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(parent)), + OID_AUTO, "freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0, + cpufreq_curr_sysctl, "I", "Current CPU frequency"); + SYSCTL_ADD_PROC(&sc->sysctl_ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(parent)), + OID_AUTO, "freq_levels", CTLTYPE_STRING | CTLFLAG_RD, sc, 0, + cpufreq_levels_sysctl, "A", "CPU frequency levels"); + cf_ev_tag = EVENTHANDLER_REGISTER(cpufreq_changed, cpufreq_evaluate, + NULL, EVENTHANDLER_PRI_ANY); + + return (0); +} + +static int +cpufreq_detach(device_t dev) +{ + struct cpufreq_softc *sc; + int numdevs; + + sc = device_get_softc(dev); + sysctl_ctx_free(&sc->sysctl_ctx); + + /* Only clean up these resources when the last device is detaching. */ + numdevs = devclass_get_count(cpufreq_dc); + if (numdevs == 1) + EVENTHANDLER_DEREGISTER(cpufreq_changed, cf_ev_tag); + + return (0); +} + +static void +cpufreq_evaluate(void *arg) +{ + /* TODO: Re-evaluate when notified of changes to drivers. */ +} + +static int +cf_set_method(device_t dev, const struct cf_level *level, int priority) +{ + struct cpufreq_softc *sc; + const struct cf_setting *set; + struct pcpu *pc; + int cpu_id, error, i; + + sc = device_get_softc(dev); + + /* + * Check that the TSC isn't being used as a timecounter. + * If it is, then return EBUSY and refuse to change the + * clock speed. + */ + if (strcmp(timecounter->tc_name, "TSC") == 0) + return (EBUSY); + + /* + * If the caller didn't specify a level and one is saved, prepare to + * restore the saved level. If none has been saved, return an error. + * If they did specify one, but the requested level has a lower + * priority, don't allow the new level right now. + */ + if (level == NULL) { + if (sc->saved_level.total_set.freq != CPUFREQ_VAL_UNKNOWN) { + level = &sc->saved_level; + priority = sc->saved_priority; + } else + return (ENXIO); + } else if (priority < sc->curr_priority) + return (EPERM); + + /* If already at this level, just return. */ + if (CPUFREQ_CMP(sc->curr_level.total_set.freq, level->total_set.freq)) + return (0); + + /* First, set the absolute frequency via its driver. */ + set = &level->abs_set; + if (set->dev) { + if (!device_is_attached(set->dev)) { + error = ENXIO; + goto out; + } + + /* Bind to the target CPU before switching, if necessary. */ + cpu_id = PCPU_GET(cpuid); + pc = cpu_get_pcpu(set->dev); + if (cpu_id != pc->pc_cpuid) { + mtx_lock_spin(&sched_lock); + sched_bind(curthread, pc->pc_cpuid); + mtx_unlock_spin(&sched_lock); + } + error = CPUFREQ_DRV_SET(set->dev, set); + if (cpu_id != pc->pc_cpuid) { + mtx_lock_spin(&sched_lock); + sched_unbind(curthread); + mtx_unlock_spin(&sched_lock); + } + if (error) { + goto out; + } + } + + /* Next, set any/all relative frequencies via their drivers. */ + for (i = 0; i < level->rel_count; i++) { + set = &level->rel_set[i]; + if (!device_is_attached(set->dev)) { + error = ENXIO; + goto out; + } + + /* Bind to the target CPU before switching, if necessary. */ + cpu_id = PCPU_GET(cpuid); + pc = cpu_get_pcpu(set->dev); + if (cpu_id != pc->pc_cpuid) { + mtx_lock_spin(&sched_lock); + sched_bind(curthread, pc->pc_cpuid); + mtx_unlock_spin(&sched_lock); + } + error = CPUFREQ_DRV_SET(set->dev, set); + if (cpu_id != pc->pc_cpuid) { + mtx_lock_spin(&sched_lock); + sched_unbind(curthread); + mtx_unlock_spin(&sched_lock); + } + if (error) { + /* XXX Back out any successful setting? */ + goto out; + } + } + + /* If we were restoring a saved state, reset it to "unused". */ + if (level == &sc->saved_level) { + sc->saved_level.total_set.freq = CPUFREQ_VAL_UNKNOWN; + sc->saved_priority = 0; + } + + /* + * Before recording the current level, check if we're going to a + * higher priority and have not saved a level yet. If so, save the + * previous level and priority. + */ + if (sc->curr_level.total_set.freq != CPUFREQ_VAL_UNKNOWN && + sc->saved_level.total_set.freq == CPUFREQ_VAL_UNKNOWN && + priority > sc->curr_priority) { + sc->saved_level = sc->curr_level; + sc->saved_priority = sc->curr_priority; + } + sc->curr_level = *level; + sc->curr_priority = priority; + error = 0; + +out: + if (error) + device_printf(set->dev, "set freq failed, err %d\n", error); + return (error); +} + +static int +cf_get_method(device_t dev, struct cf_level *level) +{ + struct cpufreq_softc *sc; + struct cf_level *levels; + struct cf_setting *curr_set, set; + struct pcpu *pc; + device_t *devs; + int count, error, i, numdevs; + uint64_t rate; + + sc = device_get_softc(dev); + curr_set = &sc->curr_level.total_set; + levels = NULL; + + /* If we already know the current frequency, we're done. */ + if (curr_set->freq != CPUFREQ_VAL_UNKNOWN) + goto out; + + /* + * We need to figure out the current level. Loop through every + * driver, getting the current setting. Then, attempt to get a best + * match of settings against each level. + */ + count = CF_MAX_LEVELS; + levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT); + if (levels == NULL) + return (ENOMEM); + error = CPUFREQ_LEVELS(sc->dev, levels, &count); + if (error) { + if (error == E2BIG) + printf("cpufreq: need to increase CF_MAX_LEVELS\n"); + goto out; + } + error = device_get_children(device_get_parent(dev), &devs, &numdevs); + if (error) + goto out; + for (i = 0; i < numdevs && curr_set->freq == CPUFREQ_VAL_UNKNOWN; i++) { + if (!device_is_attached(devs[i])) + continue; + error = CPUFREQ_DRV_GET(devs[i], &set); + if (error) + continue; + for (i = 0; i < count; i++) { + if (CPUFREQ_CMP(set.freq, levels[i].total_set.freq)) { + sc->curr_level = levels[i]; + break; + } + } + } + free(devs, M_TEMP); + if (curr_set->freq != CPUFREQ_VAL_UNKNOWN) + goto out; + + /* + * We couldn't find an exact match, so attempt to estimate and then + * match against a level. + */ + pc = cpu_get_pcpu(dev); + if (pc == NULL) { + error = ENXIO; + goto out; + } + cpu_est_clockrate(pc->pc_cpuid, &rate); + rate /= 1000000; + for (i = 0; i < count; i++) { + if (CPUFREQ_CMP(rate, levels[i].total_set.freq)) { + sc->curr_level = levels[i]; + break; + } + } + +out: + if (levels) + free(levels, M_TEMP); + *level = sc->curr_level; + return (0); +} + +static int +cf_levels_method(device_t dev, struct cf_level *levels, int *count) +{ + struct cf_setting_array *set_arr; + struct cf_setting_lst rel_sets; + struct cpufreq_softc *sc; + struct cf_level *lev; + struct cf_setting *sets; + struct pcpu *pc; + device_t *devs; + int error, i, numdevs, set_count, type; + uint64_t rate; + + if (levels == NULL || count == NULL) + return (EINVAL); + + TAILQ_INIT(&rel_sets); + sc = device_get_softc(dev); + error = device_get_children(device_get_parent(dev), &devs, &numdevs); + if (error) + return (error); + sets = malloc(MAX_SETTINGS * sizeof(*sets), M_TEMP, M_NOWAIT); + if (sets == NULL) { + free(devs, M_TEMP); + return (ENOMEM); + } + + /* Get settings from all cpufreq drivers. */ + for (i = 0; i < numdevs; i++) { + /* Skip devices that aren't ready. */ + if (!device_is_attached(devs[i])) + continue; + + /* + * Get settings, skipping drivers that offer no settings or + * provide settings for informational purposes only. + */ + error = CPUFREQ_DRV_TYPE(devs[i], &type); + if (error || (type & CPUFREQ_FLAG_INFO_ONLY)) + continue; + set_count = MAX_SETTINGS; + error = CPUFREQ_DRV_SETTINGS(devs[i], sets, &set_count); + if (error || set_count == 0) + continue; + + /* Add the settings to our absolute/relative lists. */ + switch (type & CPUFREQ_TYPE_MASK) { + case CPUFREQ_TYPE_ABSOLUTE: + error = cpufreq_insert_abs(sc, sets, set_count); + break; + case CPUFREQ_TYPE_RELATIVE: + set_arr = malloc(sizeof(*set_arr), M_TEMP, M_NOWAIT); + if (set_arr == NULL) { + error = ENOMEM; + goto out; + } + bcopy(sets, set_arr->sets, set_count * sizeof(*sets)); + set_arr->count = set_count; + TAILQ_INSERT_TAIL(&rel_sets, set_arr, link); + break; + default: + error = EINVAL; + break; + } + if (error) + goto out; + } + + /* + * If there are no absolute levels, create a fake one at 100%. We + * then cache the clockrate for later use as our base frequency. + * + * XXX This assumes that the first time through, if we only have + * relative drivers, the CPU is currently running at 100%. + */ + if (TAILQ_EMPTY(&sc->all_levels)) { + if (sc->max_mhz == CPUFREQ_VAL_UNKNOWN) { + pc = cpu_get_pcpu(dev); + cpu_est_clockrate(pc->pc_cpuid, &rate); + sc->max_mhz = rate / 1000000; + } + memset(&sets[0], CPUFREQ_VAL_UNKNOWN, sizeof(*sets)); + sets[0].freq = sc->max_mhz; + sets[0].dev = NULL; + error = cpufreq_insert_abs(sc, sets, 1); + if (error) + goto out; + } + + /* Create a combined list of absolute + relative levels. */ + TAILQ_FOREACH(set_arr, &rel_sets, link) + cpufreq_expand_set(sc, set_arr); + + /* If the caller doesn't have enough space, return the actual count. */ + if (sc->all_count > *count) { + *count = sc->all_count; + error = E2BIG; + goto out; + } + + /* Finally, output the list of levels. */ + i = 0; + TAILQ_FOREACH(lev, &sc->all_levels, link) { + levels[i] = *lev; + i++; + } + *count = sc->all_count; + error = 0; + +out: + /* Clear all levels since we regenerate them each time. */ + while ((lev = TAILQ_FIRST(&sc->all_levels)) != NULL) { + TAILQ_REMOVE(&sc->all_levels, lev, link); + free(lev, M_TEMP); + } + while ((set_arr = TAILQ_FIRST(&rel_sets)) != NULL) { + TAILQ_REMOVE(&rel_sets, set_arr, link); + free(set_arr, M_TEMP); + } + sc->all_count = 0; + free(devs, M_TEMP); + free(sets, M_TEMP); + return (error); +} + +/* + * Create levels for an array of absolute settings and insert them in + * sorted order in the specified list. + */ +static int +cpufreq_insert_abs(struct cpufreq_softc *sc, struct cf_setting *sets, + int count) +{ + struct cf_level_lst *list; + struct cf_level *level, *search; + int i; + + list = &sc->all_levels; + for (i = 0; i < count; i++) { + level = malloc(sizeof(*level), M_TEMP, M_NOWAIT | M_ZERO); + if (level == NULL) + return (ENOMEM); + level->abs_set = sets[i]; + level->total_set = sets[i]; + level->total_set.dev = NULL; + sc->all_count++; + + if (TAILQ_EMPTY(list)) { + TAILQ_INSERT_HEAD(list, level, link); + continue; + } + + TAILQ_FOREACH_REVERSE(search, list, cf_level_lst, link) { + if (sets[i].freq <= search->total_set.freq) { + TAILQ_INSERT_AFTER(list, search, level, link); + break; + } + } + } + return (0); +} + +/* + * Expand a group of relative settings, creating derived levels from them. + */ +static int +cpufreq_expand_set(struct cpufreq_softc *sc, struct cf_setting_array *set_arr) +{ + struct cf_level *fill, *search; + struct cf_setting *set; + int i; + + TAILQ_FOREACH(search, &sc->all_levels, link) { + /* Skip this level if we've already modified it. */ + for (i = 0; i < search->rel_count; i++) { + if (search->rel_set[i].dev == set_arr->sets[0].dev) + break; + } + if (i != search->rel_count) + continue; + + /* Add each setting to the level, duplicating if necessary. */ + for (i = 0; i < set_arr->count; i++) { + set = &set_arr->sets[i]; + + /* + * If this setting is less than 100%, split the level + * into two and add this setting to the new level. + */ + fill = search; + if (set->freq < 10000) + fill = cpufreq_dup_set(sc, search, set); + + /* + * The new level was a duplicate of an existing level + * so we freed it. Go to the next setting. + */ + if (fill == NULL) + continue; + + /* Add this setting to the existing or new level. */ + KASSERT(fill->rel_count < MAX_SETTINGS, + ("cpufreq: too many relative drivers (%d)", + MAX_SETTINGS)); + fill->rel_set[fill->rel_count] = *set; + fill->rel_count++; + } + } + + return (0); +} + +static struct cf_level * +cpufreq_dup_set(struct cpufreq_softc *sc, struct cf_level *dup, + struct cf_setting *set) +{ + struct cf_level_lst *list; + struct cf_level *fill, *itr; + struct cf_setting *fill_set, *itr_set; + int i; + + /* + * Create a new level, copy it from the old one, and update the + * total frequency and power by the percentage specified in the + * relative setting. + */ + fill = malloc(sizeof(*fill), M_TEMP, M_NOWAIT); + if (fill == NULL) + return (NULL); + *fill = *dup; + fill_set = &fill->total_set; + fill_set->freq = + ((uint64_t)fill_set->freq * set->freq) / 10000; + if (fill_set->power != CPUFREQ_VAL_UNKNOWN) { + fill_set->power = ((uint64_t)fill_set->power * set->freq) + / 10000; + } + if (set->lat != CPUFREQ_VAL_UNKNOWN) { + if (fill_set->lat != CPUFREQ_VAL_UNKNOWN) + fill_set->lat += set->lat; + else + fill_set->lat = set->lat; + } + + /* + * If we copied an old level that we already modified (say, at 100%), + * we need to remove that setting before adding this one. Since we + * process each setting array in order, we know any settings for this + * driver will be found at the end. + */ + for (i = fill->rel_count; i != 0; i--) { + if (fill->rel_set[i - 1].dev != set->dev) + break; + fill->rel_count--; + } + + /* + * Insert the new level in sorted order. If we find a duplicate, + * free the new level. We can do this since any existing level will + * be guaranteed to have the same or less settings and thus consume + * less power. For example, a level with one absolute setting of + * 800 Mhz uses less power than one composed of an absolute setting + * of 1600 Mhz and a relative setting at 50%. + */ + list = &sc->all_levels; + if (TAILQ_EMPTY(list)) { + TAILQ_INSERT_HEAD(list, fill, link); + } else { + TAILQ_FOREACH_REVERSE(itr, list, cf_level_lst, link) { + itr_set = &itr->total_set; + if (CPUFREQ_CMP(fill_set->freq, itr_set->freq)) { + free(fill, M_TEMP); + fill = NULL; + break; + } else if (fill_set->freq < itr_set->freq) { + TAILQ_INSERT_AFTER(list, itr, fill, link); + sc->all_count++; + break; + } + } + } + + return (fill); +} + +static int +cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct cpufreq_softc *sc; + struct cf_level *levels; + int count, devcount, error, freq, i, n; + device_t *devs; + + devs = NULL; + sc = oidp->oid_arg1; + levels = malloc(CF_MAX_LEVELS * sizeof(*levels), M_TEMP, M_NOWAIT); + if (levels == NULL) + return (ENOMEM); + + error = CPUFREQ_GET(sc->dev, &levels[0]); + if (error) + goto out; + freq = levels[0].total_set.freq; + error = sysctl_handle_int(oidp, &freq, 0, req); + if (error != 0 || req->newptr == NULL) + goto out; + + /* + * While we only call cpufreq_get() on one device (assuming all + * CPUs have equal levels), we call cpufreq_set() on all CPUs. + * This is needed for some MP systems. + */ + error = devclass_get_devices(cpufreq_dc, &devs, &devcount); + if (error) + goto out; + for (n = 0; n < devcount; n++) { + count = CF_MAX_LEVELS; + error = CPUFREQ_LEVELS(devs[n], levels, &count); + if (error) { + if (error == E2BIG) + printf( + "cpufreq: need to increase CF_MAX_LEVELS\n"); + break; + } + for (i = 0; i < count; i++) { + if (CPUFREQ_CMP(levels[i].total_set.freq, freq)) { + error = CPUFREQ_SET(devs[n], &levels[i], + CPUFREQ_PRIO_USER); + break; + } + } + if (i == count) { + error = EINVAL; + break; + } + } + +out: + if (devs) + free(devs, M_TEMP); + if (levels) + free(levels, M_TEMP); + return (error); +} + +static int +cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct cpufreq_softc *sc; + struct cf_level *levels; + struct cf_setting *set; + struct sbuf sb; + int count, error, i; + + sc = oidp->oid_arg1; + sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND); + + /* Get settings from the device and generate the output string. */ + count = CF_MAX_LEVELS; + levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT); + if (levels == NULL) + return (ENOMEM); + error = CPUFREQ_LEVELS(sc->dev, levels, &count); + if (error) { + if (error == E2BIG) + printf("cpufreq: need to increase CF_MAX_LEVELS\n"); + goto out; + } + if (count) { + for (i = 0; i < count; i++) { + set = &levels[i].total_set; + sbuf_printf(&sb, "%d/%d ", set->freq, set->power); + } + } else + sbuf_cpy(&sb, "0"); + sbuf_trim(&sb); + sbuf_finish(&sb); + error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); + +out: + free(levels, M_TEMP); + sbuf_delete(&sb); + return (error); +} + +static int +cpufreq_settings_sysctl(SYSCTL_HANDLER_ARGS) +{ + device_t dev; + struct cf_setting *sets; + struct sbuf sb; + int error, i, set_count; + + dev = oidp->oid_arg1; + sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND); + + /* Get settings from the device and generate the output string. */ + set_count = MAX_SETTINGS; + sets = malloc(set_count * sizeof(*sets), M_TEMP, M_NOWAIT); + if (sets == NULL) + return (ENOMEM); + error = CPUFREQ_DRV_SETTINGS(dev, sets, &set_count); + if (error) + goto out; + if (set_count) { + for (i = 0; i < set_count; i++) + sbuf_printf(&sb, "%d/%d ", sets[i].freq, sets[i].power); + } else + sbuf_cpy(&sb, "0"); + sbuf_trim(&sb); + sbuf_finish(&sb); + error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); + +out: + free(sets, M_TEMP); + sbuf_delete(&sb); + return (error); +} + +int +cpufreq_register(device_t dev) +{ + struct cpufreq_softc *sc; + device_t cf_dev, cpu_dev; + + /* Add a sysctl to get each driver's settings separately. */ + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "freq_settings", CTLTYPE_STRING | CTLFLAG_RD, dev, 0, + cpufreq_settings_sysctl, "A", "CPU frequency driver settings"); + + /* + * Add only one cpufreq device to each CPU. Currently, all CPUs + * must offer the same levels and be switched at the same time. + */ + cpu_dev = device_get_parent(dev); + if ((cf_dev = device_find_child(cpu_dev, "cpufreq", -1))) { + sc = device_get_softc(cf_dev); + sc->max_mhz = CPUFREQ_VAL_UNKNOWN; + return (0); + } + + /* Add the child device and possibly sysctls. */ + cf_dev = BUS_ADD_CHILD(cpu_dev, 0, "cpufreq", -1); + if (cf_dev == NULL) + return (ENOMEM); + device_quiet(cf_dev); + + return (device_probe_and_attach(cf_dev)); +} + +int +cpufreq_unregister(device_t dev) +{ + device_t cf_dev, *devs; + int cfcount, devcount, error, i, type; + + /* + * If this is the last cpufreq child device, remove the control + * device as well. We identify cpufreq children by calling a method + * they support. + */ + error = device_get_children(device_get_parent(dev), &devs, &devcount); + if (error) + return (error); + cf_dev = device_find_child(device_get_parent(dev), "cpufreq", -1); + cfcount = 0; + for (i = 0; i < devcount; i++) { + if (!device_is_attached(devs[i])) + continue; + if (CPUFREQ_DRV_TYPE(devs[i], &type) == 0) + cfcount++; + } + if (cfcount <= 1) + device_delete_child(device_get_parent(cf_dev), cf_dev); + free(devs, M_TEMP); + + return (0); +} diff --git a/sys/netinet/ip_carp.h b/sys/netinet/ip_carp.h new file mode 100644 index 0000000000000..a050a88e39915 --- /dev/null +++ b/sys/netinet/ip_carp.h @@ -0,0 +1,163 @@ +/* $FreeBSD$ */ +/* $OpenBSD: ip_carp.h,v 1.8 2004/07/29 22:12:15 mcbride Exp $ */ + +/* + * Copyright (c) 2002 Michael Shalayeff. All rights reserved. + * Copyright (c) 2003 Ryan McBride. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR OR HIS RELATIVES BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _IP_CARP_H +#define _IP_CARP_H + +/* + * The CARP header layout is as follows: + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Version| Type | VirtualHostID | AdvSkew | Auth Len | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | AdvBase | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Counter (1) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Counter (2) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SHA-1 HMAC (1) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SHA-1 HMAC (2) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SHA-1 HMAC (3) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SHA-1 HMAC (4) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SHA-1 HMAC (5) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ + +struct carp_header { +#if BYTE_ORDER == LITTLE_ENDIAN + u_int8_t carp_type:4, + carp_version:4; +#endif +#if BYTE_ORDER == BIG_ENDIAN + u_int8_t carp_version:4, + carp_type:4; +#endif + u_int8_t carp_vhid; /* virtual host id */ + u_int8_t carp_advskew; /* advertisement skew */ + u_int8_t carp_authlen; /* size of counter+md, 32bit chunks */ + u_int8_t carp_pad1; /* reserved */ + u_int8_t carp_advbase; /* advertisement interval */ + u_int16_t carp_cksum; + u_int32_t carp_counter[2]; + unsigned char carp_md[20]; /* SHA1 HMAC */ +} __packed; + +#define CARP_DFLTTL 255 + +/* carp_version */ +#define CARP_VERSION 2 + +/* carp_type */ +#define CARP_ADVERTISEMENT 0x01 + +#define CARP_KEY_LEN 20 /* a sha1 hash of a passphrase */ + +/* carp_advbase */ +#define CARP_DFLTINTV 1 + +/* + * Statistics. + */ +struct carpstats { + uint64_t carps_ipackets; /* total input packets, IPv4 */ + uint64_t carps_ipackets6; /* total input packets, IPv6 */ + uint64_t carps_badif; /* wrong interface */ + uint64_t carps_badttl; /* TTL is not CARP_DFLTTL */ + uint64_t carps_hdrops; /* packets shorter than hdr */ + uint64_t carps_badsum; /* bad checksum */ + uint64_t carps_badver; /* bad (incl unsupp) version */ + uint64_t carps_badlen; /* data length does not match */ + uint64_t carps_badauth; /* bad authentication */ + uint64_t carps_badvhid; /* bad VHID */ + uint64_t carps_badaddrs; /* bad address list */ + + uint64_t carps_opackets; /* total output packets, IPv4 */ + uint64_t carps_opackets6; /* total output packets, IPv6 */ + uint64_t carps_onomem; /* no memory for an mbuf */ + uint64_t carps_ostates; /* total state updates sent */ + + uint64_t carps_preempt; /* if enabled, preemptions */ +}; + +/* + * Configuration structure for SIOCSVH SIOCGVH + */ +struct carpreq { + int carpr_state; +#define CARP_STATES "INIT", "BACKUP", "MASTER" +#define CARP_MAXSTATE 2 + int carpr_vhid; + int carpr_advskew; + int carpr_advbase; + unsigned char carpr_key[CARP_KEY_LEN]; +}; +#define SIOCSVH _IOWR('i', 245, struct ifreq) +#define SIOCGVH _IOWR('i', 246, struct ifreq) + +/* + * Names for CARP sysctl objects + */ +#define CARPCTL_ALLOW 1 /* accept incoming CARP packets */ +#define CARPCTL_PREEMPT 2 /* high-pri backup preemption mode */ +#define CARPCTL_LOG 3 /* log bad packets */ +#define CARPCTL_STATS 4 /* statistics (read-only) */ +#define CARPCTL_ARPBALANCE 5 /* balance arp responses */ +#define CARPCTL_MAXID 6 + +#define CARPCTL_NAMES { \ + { 0, 0 }, \ + { "allow", CTLTYPE_INT }, \ + { "preempt", CTLTYPE_INT }, \ + { "log", CTLTYPE_INT }, \ + { "stats", CTLTYPE_STRUCT }, \ + { "arpbalance", CTLTYPE_INT }, \ +} + +#ifdef _KERNEL +void carp_ifdetach (struct ifnet *); +void carp_carpdev_state(void *); +void carp_input (struct mbuf *, int); +int carp6_input (struct mbuf **, int *, int); +int carp_output (struct ifnet *, struct mbuf *, struct sockaddr *, + struct rtentry *); +int carp_iamatch (void *, struct in_ifaddr *, struct in_addr *, + u_int8_t **); +struct ifaddr *carp_iamatch6(void *, struct in6_addr *); +void *carp_macmatch6(void *, struct mbuf *, const struct in6_addr *); +struct ifnet *carp_forus (void *, void *); +#endif +#endif /* _IP_CARP_H */ diff --git a/sys/sys/cpu.h b/sys/sys/cpu.h new file mode 100644 index 0000000000000..e2337ab020f7b --- /dev/null +++ b/sys/sys/cpu.h @@ -0,0 +1,131 @@ +/*- + * Copyright (c) 2005 Nate Lawson (SDG) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _SYS_CPU_H_ +#define _SYS_CPU_H_ + +/* + * CPU device support. + */ + +#define CPU_IVAR_PCPU 1 + +static __inline struct pcpu *cpu_get_pcpu(device_t dev) +{ + uintptr_t v = 0; + BUS_READ_IVAR(device_get_parent(dev), dev, CPU_IVAR_PCPU, &v); + return ((struct pcpu *)v); +} + +/* + * CPU frequency control interface. + */ + +/* Each driver's CPU frequency setting is exported in this format. */ +struct cf_setting { + int freq; /* CPU clock in Mhz or 100ths of a percent. */ + int volts; /* Voltage in mV. */ + int power; /* Power consumed in mW. */ + int lat; /* Transition latency in us. */ + device_t dev; /* Driver providing this setting. */ + int spec[4];/* Driver-specific storage for non-standard info. */ +}; + +/* Maximum number of settings a given driver can have. */ +#define MAX_SETTINGS 24 + +/* A combination of settings is a level. */ +struct cf_level { + struct cf_setting total_set; + struct cf_setting abs_set; + struct cf_setting rel_set[MAX_SETTINGS]; + int rel_count; + TAILQ_ENTRY(cf_level) link; +}; + +TAILQ_HEAD(cf_level_lst, cf_level); + +/* Drivers should set all unknown values to this. */ +#define CPUFREQ_VAL_UNKNOWN (-1) + +/* + * Every driver offers a type of CPU control. Absolute levels are mutually + * exclusive while relative levels modify the current absolute level. There + * may be multiple absolute and relative drivers available on a given + * system. + * + * For example, consider a system with two absolute drivers that provide + * frequency settings of 100, 200 and 300, 400 and a relative driver that + * provides settings of 50%, 100%. The cpufreq core would export frequency + * levels of 50, 100, 150, 200, 300, 400. + * + * The "info only" flag signifies that settings returned by + * CPUFREQ_DRV_SETTINGS cannot be passed to the CPUFREQ_DRV_SET method and + * are only informational. This is for some drivers that can return + * information about settings but rely on another machine-dependent driver + * for actually performing the frequency transition (e.g., ACPI performance + * states of type "functional fixed hardware.") + */ +#define CPUFREQ_TYPE_MASK 0xffff +#define CPUFREQ_TYPE_RELATIVE (1<<0) +#define CPUFREQ_TYPE_ABSOLUTE (1<<1) +#define CPUFREQ_FLAG_INFO_ONLY (1<<16) + +/* + * When setting a level, the caller indicates the priority of this request. + * Priorities determine, among other things, whether a level can be + * overridden by other callers. For example, if the user sets a level but + * the system thermal driver needs to override it for emergency cooling, + * the driver would use a higher priority. Once the event has passed, the + * driver would call cpufreq to resume any previous level. + */ +#define CPUFREQ_PRIO_HIGHEST 1000000 +#define CPUFREQ_PRIO_KERN 1000 +#define CPUFREQ_PRIO_USER 100 +#define CPUFREQ_PRIO_LOWEST 0 + +/* + * Register and unregister a driver with the cpufreq core. Once a driver + * is registered, it must support calls to its CPUFREQ_GET, CPUFREQ_GET_LEVEL, + * and CPUFREQ_SET methods. It must also unregister before returning from + * its DEVICE_DETACH method. + */ +int cpufreq_register(device_t dev); +int cpufreq_unregister(device_t dev); + +/* Allow values to be +/- a bit since sometimes we have to estimate. */ +#define CPUFREQ_CMP(x, y) (abs((x) - (y)) < 25) + +/* + * Machine-dependent functions. + */ + +/* Estimate the current clock rate for the given CPU id. */ +int cpu_est_clockrate(int cpu_id, uint64_t *rate); + +#endif /* !_SYS_CPU_H_ */ |