diff --git a/Makefile b/Makefile index 5d4adb81a..f7fff16af 100644 --- a/Makefile +++ b/Makefile @@ -459,6 +459,7 @@ drivers-y := drivers/ sound/ net-y := net/ libs-y := lib/ core-y := usr/ +extra-y := extra/ endif # KBUILD_EXTMOD ifeq ($(dot-config),1) @@ -610,12 +611,12 @@ core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ - $(net-y) $(net-m) $(libs-y) $(libs-m))) + $(net-y) $(net-m) $(libs-y) $(libs-m) $(extra-y) $(extra-m))) vmlinux-alldirs := $(sort $(vmlinux-dirs) $(patsubst %/,%,$(filter %/, \ $(init-n) $(init-) \ $(core-n) $(core-) $(drivers-n) $(drivers-) \ - $(net-n) $(net-) $(libs-n) $(libs-)))) + $(net-n) $(net-) $(libs-n) $(libs-) $(extra-n) $(extra-)))) init-y := $(patsubst %/, %/built-in.o, $(init-y)) core-y := $(patsubst %/, %/built-in.o, $(core-y)) @@ -624,6 +625,7 @@ net-y := $(patsubst %/, %/built-in.o, $(net-y)) libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y)) libs-y := $(libs-y1) $(libs-y2) +extra-y := $(patsubst %/, %/built-in.o, $(extra-y)) # Build vmlinux # --------------------------------------------------------------------------- @@ -653,7 +655,7 @@ libs-y := $(libs-y1) $(libs-y2) # System.map is generated to document addresses of all kernel symbols vmlinux-init := $(head-y) $(init-y) -vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y) +vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y) $(extra-y) vmlinux-all := $(vmlinux-init) $(vmlinux-main) vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds export KBUILD_VMLINUX_OBJS := $(vmlinux-all) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index b20441ac0..8888eb216 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1146,3 +1146,6 @@ source "security/Kconfig" source "crypto/Kconfig" source "lib/Kconfig" + +source "extra/Kconfig" + diff --git a/arch/arm/Makefile b/arch/arm/Makefile index e93610a97..4d83edaa6 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -47,12 +47,12 @@ comma = , # Note that GCC does not numerically define an architecture version # macro, but instead defines a whole series of macros which makes # testing for a specific architecture or later rather impossible. -arch-$(CONFIG_CPU_32v7) :=-D__LINUX_ARM_ARCH__=7 $(call cc-option,-march=armv7-a,-march=armv5t -Wa$(comma)-march=armv7-a) +arch-$(CONFIG_CPU_32v7) :=-D__LINUX_ARM_ARCH__=7 $(call cc-option,-fgcse-lm -fgcse-sm -fsched-spec-load -fforce-addr -ffast-math -fsingle-precision-constant -mcpu=cortex-a8 -mfpu=neon -ftree-vectorize -funswitch-loops,-mcpu=cortex-a8 -Wa$(comma)-mcpu=cortex-a8) arch-$(CONFIG_CPU_32v6) :=-D__LINUX_ARM_ARCH__=6 $(call cc-option,-march=armv6,-march=armv5t -Wa$(comma)-march=armv6) # Only override the compiler option if ARMv6. The ARMv6K extensions are # always available in ARMv7 ifeq ($(CONFIG_CPU_32v6),y) -arch-$(CONFIG_CPU_32v6K) :=-D__LINUX_ARM_ARCH__=6 $(call cc-option,-march=armv6k,-march=armv5t -Wa$(comma)-march=armv6k) +arch-$(CONFIG_CPU_32v6K) :=-D__LINUX_ARM_ARCH__=6 $(call cc-option,-march=armv6k,-mtune=arm1176jf-s -Wa$(comma)-march=armv6k -mtune=arm1176jf-s) endif arch-$(CONFIG_CPU_32v5) :=-D__LINUX_ARM_ARCH__=5 $(call cc-option,-march=armv5te,-march=armv4t) arch-$(CONFIG_CPU_32v4T) :=-D__LINUX_ARM_ARCH__=4 -march=armv4t diff --git a/arch/arm/configs/omap_sirloin_3630_defconfig b/arch/arm/configs/omap_sirloin_3630_defconfig index bd4bb5b82..787d1c640 100644 --- a/arch/arm/configs/omap_sirloin_3630_defconfig +++ b/arch/arm/configs/omap_sirloin_3630_defconfig @@ -166,9 +166,7 @@ CONFIG_ARCH_OMAP3=y # # CONFIG_OMAP_DEBUG_POWERDOMAIN is not set # CONFIG_OMAP_DEBUG_CLOCKDOMAIN is not set -CONFIG_OMAP_SMARTREFLEX=y -CONFIG_OMAP_SMARTREFLEX_v15=y -# CONFIG_OMAP_SMARTREFLEX_TESTING is not set +# CONFIG_OMAP_SMARTREFLEX is not set CONFIG_OMAP_RESET_CLOCKS=y CONFIG_OMAP_BOOT_TAG=y CONFIG_OMAP_BOOT_REASON=y @@ -221,15 +219,15 @@ CONFIG_FASTPATH=y # CONFIG_OMAP3_PM=y # CONFIG_OMAP_VOLT_SR_BYPASS is not set -CONFIG_OMAP_VOLT_SR_FORCEUPDATE=y +# CONFIG_OMAP_VOLT_SR_FORCEUPDATE is not set # CONFIG_OMAP_VOLT_SR is not set -# CONFIG_OMAP_VOLT_VSEL is not set +CONFIG_OMAP_VOLT_VSEL=y # CONFIG_OMAP_VOLT_VMODE is not set # CONFIG_OMAP3_VDD1_OPP1 is not set # CONFIG_OMAP3_VDD1_OPP2 is not set # CONFIG_OMAP3_VDD1_OPP3 is not set -# CONFIG_OMAP3_VDD1_OPP4 is not set -CONFIG_OMAP3_VDD1_OPP5=y +CONFIG_OMAP3_VDD1_OPP4=y +# CONFIG_OMAP3_VDD1_OPP5 is not set # CONFIG_OMAP3_VDD2_OPP2 is not set CONFIG_OMAP3_VDD2_OPP3=y # CONFIG_ENABLE_VOLTSCALE_IN_SUSPEND is not set @@ -353,18 +351,30 @@ CONFIG_CPU_FREQ_TABLE=y # CONFIG_CPU_FREQ_DEBUG is not set CONFIG_CPU_FREQ_STAT=y CONFIG_CPU_FREQ_STAT_DETAILS=y +CONFIG_CPU_FREQ_OVERRIDE=y +# CONFIG_CPU_FREQ_OVERRIDE_STRIPOPP is not set # CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set # CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE=y # CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set # CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND_TICKLE is not set # CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set -# CONFIG_CPU_FREQ_GOV_PERFORMANCE is not set -# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_SCREENSTATE=y CONFIG_CPU_FREQ_GOV_ONDEMAND=y CONFIG_CPU_FREQ_GOV_ONDEMAND_TICKLE=y -# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_LAGFREE=y +CONFIG_LAGFREE_MAX_LOAD=50 +CONFIG_LAGFREE_MIN_LOAD=15 +CONFIG_LAGFREE_FREQ_STEP_DOWN=100000 +CONFIG_LAGFREE_FREQ_SLEEP_MAX=300000 +CONFIG_LAGFREE_FREQ_AWAKE_MIN=150000 +CONFIG_LAGFREE_FREQ_STEP_UP_SLEEP_PERCENT=20 +CONFIG_CPU_FREQ_MIN_TICKS=10 +CONFIG_CPU_FREQ_SAMPLING_LATENCY_MULTIPLIER=1000 # # Floating point emulation @@ -1037,7 +1047,8 @@ CONFIG_OMAP3430_1WIRE_PROTOCOL=y # CONFIG_W1_SLAVE_DS2760 is not set CONFIG_W1_SLAVE_DS2784=y # CONFIG_POWER_SUPPLY is not set -# CONFIG_HWMON is not set +CONFIG_HWMON=y +CONFIG_SENSORS_OMAP34XX=y CONFIG_WATCHDOG=y # CONFIG_WATCHDOG_NOWAYOUT is not set @@ -1824,14 +1835,14 @@ CONFIG_CRYPTO_SHA256=y CONFIG_CRYPTO_ECB=m CONFIG_CRYPTO_CBC=y CONFIG_CRYPTO_PCBC=m -# CONFIG_CRYPTO_LRW is not set -# CONFIG_CRYPTO_XTS is not set +CONFIG_CRYPTO_LRW=y +CONFIG_CRYPTO_XTS=y # CONFIG_CRYPTO_CRYPTD is not set CONFIG_CRYPTO_DES=y # CONFIG_CRYPTO_FCRYPT is not set # CONFIG_CRYPTO_BLOWFISH is not set -# CONFIG_CRYPTO_TWOFISH is not set -# CONFIG_CRYPTO_SERPENT is not set +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_SERPENT=y CONFIG_CRYPTO_AES=y # CONFIG_CRYPTO_CAST5 is not set # CONFIG_CRYPTO_CAST6 is not set diff --git a/arch/arm/mach-omap3pe/board-nduid.c b/arch/arm/mach-omap3pe/board-nduid.c index 9780e4e3b..e39ce942a 100644 --- a/arch/arm/mach-omap3pe/board-nduid.c +++ b/arch/arm/mach-omap3pe/board-nduid.c @@ -18,6 +18,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include #include #include #if defined(CONFIG_ARCH_OMAP24XX) @@ -63,5 +64,9 @@ int omap_nduid_get_cpu_id(char *id, unsigned int maxlen) buf[4] = __raw_readl(CONTROL_TAP_DIE_ID_2); buf[5] = __raw_readl(CONTROL_TAP_DIE_ID_3); + printk(KERN_INFO "IDCODE: 0x%04x\n", buf[0]); + printk(KERN_INFO "PROD_ID: 0x%04x\n", buf[1]); + printk(KERN_INFO "DIE_ID: 0x%04x%04x%04x%04x\n", buf[2], buf[3], buf[4], buf[5]); + return sizeof(uint32_t) * 6; } diff --git a/arch/arm/mach-omap3pe/clock.c b/arch/arm/mach-omap3pe/clock.c index cce203a67..a84598259 100644 --- a/arch/arm/mach-omap3pe/clock.c +++ b/arch/arm/mach-omap3pe/clock.c @@ -707,6 +707,13 @@ static void omap3_clk_init_cpufreq_table(struct cpufreq_frequency_table **table) prcm = vdd1_rate_table + ARRAY_SIZE(vdd1_rate_table) -1; for (; prcm->speed; prcm--) { +#ifdef CONFIG_CPU_FREQ_OVERRIDE_STRIPOPP + #ifdef CONFIG_MACH_SIRLOIN_3630 + if((prcm->speed / 1000) < 300000) continue; + #else + if((prcm->speed / 1000) < 500000) continue; + #endif +#endif freq_table[i].index = i; freq_table[i].frequency = prcm->speed / 1000; i++; diff --git a/arch/arm/mach-omap3pe/clock.h b/arch/arm/mach-omap3pe/clock.h index 79b4f6cf7..8a0bce9f3 100644 --- a/arch/arm/mach-omap3pe/clock.h +++ b/arch/arm/mach-omap3pe/clock.h @@ -54,8 +54,8 @@ #define S550M 550000000 #define S625M 625000000 #define S600M 600000000 -#define S800M 800000000 #define S1000M 1000000000 +#define S1200M 1200000000 /* Macro to enable clock control via clock framework */ #define ENABLE_CLOCKCONTROL 1 diff --git a/arch/arm/mach-omap3pe/clock_tree.h b/arch/arm/mach-omap3pe/clock_tree.h index 73f14ea59..347b037e1 100644 --- a/arch/arm/mach-omap3pe/clock_tree.h +++ b/arch/arm/mach-omap3pe/clock_tree.h @@ -91,9 +91,9 @@ static struct vdd_prcm_config vdd1_rate_table[MAX_VDD1_OPP +1] = { /*OPP3*/ {S600M, PRCM_VDD1_OPP3, RATE_IN_343X}, /*OPP4*/ - {S800M, PRCM_VDD1_OPP4, RATE_IN_343X}, + {S1000M, PRCM_VDD1_OPP4, RATE_IN_343X}, /*OPP5*/ - {S1000M, PRCM_VDD1_OPP5, RATE_IN_343X}, + {S1200M, PRCM_VDD1_OPP5, RATE_IN_343X}, }; static struct vdd_prcm_config vdd2_rate_table[MAX_VDD2_OPP +1] = { diff --git a/arch/arm/mach-omap3pe/prcm_opp.c b/arch/arm/mach-omap3pe/prcm_opp.c index f6103b7b6..a6ee46342 100644 --- a/arch/arm/mach-omap3pe/prcm_opp.c +++ b/arch/arm/mach-omap3pe/prcm_opp.c @@ -66,7 +66,7 @@ static u8 mpu_iva2_vdd1_volts [2][PRCM_NO_VDD1_OPPS] = { { 0x1e, 0x24, 0x30, 0x36, 0x3C }, /* OLD 3430 values */ /* Vsel corresponding to unused (OPP1), 1.0125V (OPP2), 1.2V (OPP3), 1.325V (OPP4), 1.375 (OPP5) */ - { 0x21, 0x21, 0x30, 0x3a, 0x3e }, /* NEW 3630 values */ + { 0x21, 0x21, 0x30, 0x3e, 0x44 }, /* NEW 3630 values */ }; static u8 core_l3_vdd2_volts [2][PRCM_NO_VDD2_OPPS] = { /* only 3 OPPs */ @@ -226,7 +226,7 @@ void prcm_set_current_vdd2_opp(u32 opp) u32 omap3_max_vdd1_opp(void) { - return 5; + return 0; /* This function call is used in the bridgedriver. * @@ -332,10 +332,10 @@ static struct dpll_param mpu_dpll_param[2][5][PRCM_NO_VDD1_OPPS] = { /* 26M values */ /* OPP1(150 Mhz) and OPP2(300 Mhz)*/ {{0x12c, 0x0C, 0x07, 0x04}, {0x12c, 0x0C, 0x07, 0x02}, - /* OPP3(600 Mhz) and OPP4(800 Mhz)*/ - {0x12c, 0x0C, 0x07, 0x01}, {0x190, 0x0C, 0x07, 0x01}, - /* OPP5 (1000 Mhz) */ - {0x1f4, 0x0C, 0x07, 0x01} }, + /* OPP3(600 Mhz) and OPP4(1000 Mhz)*/ + {0x12c, 0x0C, 0x07, 0x01}, {0x1f4, 0x0C, 0x07, 0x01}, + /* OPP5 (1200 Mhz) */ + {0x258, 0x0C, 0x07, 0x01} }, /* 38.4M values */ /* OPP1(125 Mhz) and OPP2(250 Mhz)*/ {{0x271, 0x2F, 0x03, 0x04}, {0x271, 0x2F, 0x03, 0x02}, @@ -697,10 +697,10 @@ void prcm_scale_finish(void) valid_rate = clk_round_rate(p_vdd1_clk, S600M); break; case PRCM_VDD1_OPP4: - valid_rate = clk_round_rate(p_vdd1_clk, S800M); + valid_rate = clk_round_rate(p_vdd1_clk, S1000M); break; case PRCM_VDD1_OPP5: - valid_rate = clk_round_rate(p_vdd1_clk, S1000M); + valid_rate = clk_round_rate(p_vdd1_clk, S1200M); break; #else case PRCM_VDD1_OPP2: @@ -732,8 +732,8 @@ static struct vdd1_arm_dsp_freq_d { {150, 90, CO_VDD1_OPP1, PRCM_VDD1_OPP1}, {300, 180, CO_VDD1_OPP2, PRCM_VDD1_OPP2}, {600, 360, CO_VDD1_OPP3, PRCM_VDD1_OPP3}, - {800, 396, CO_VDD1_OPP4, PRCM_VDD1_OPP4}, - {1000, 430, CO_VDD1_OPP5, PRCM_VDD1_OPP5}, + {1000, 396, CO_VDD1_OPP4, PRCM_VDD1_OPP4}, + {1200, 430, CO_VDD1_OPP5, PRCM_VDD1_OPP5}, }; static struct vdd2_core_freq_d { unsigned int freq; @@ -746,7 +746,7 @@ static struct vdd2_core_freq_d { }; static unsigned int rnd_rate_vdd1[5] = { - S150M, S300M, S600M, S800M, S1000M + S150M, S300M, S600M, S1000M, S1200M }; static unsigned int rnd_rate_vdd2[3] = { 0, S100M, S200M @@ -1889,5 +1889,7 @@ int __init prcm_vdd_clk_init(void) return -1; } - +#ifdef CONFIG_CPU_FREQ_OVERRIDE +#include "prcm_opp_ss.c" +#endif diff --git a/arch/arm/mach-omap3pe/prcm_opp_ss.c b/arch/arm/mach-omap3pe/prcm_opp_ss.c new file mode 100644 index 000000000..4bcbbb867 --- /dev/null +++ b/arch/arm/mach-omap3pe/prcm_opp_ss.c @@ -0,0 +1,70 @@ +#ifdef CONFIG_CPU_FREQ_OVERRIDE +void omap_pm_opp_get_volts(u8 vdd1_volts[]) { + #ifdef CONFIG_MACH_SIRLOIN_3630 + memcpy(vdd1_volts,mpu_iva2_vdd1_volts[tidx], + sizeof(mpu_iva2_vdd1_volts[tidx])); + #else + memcpy(vdd1_volts,mpu_iva2_vdd1_volts,sizeof(mpu_iva2_vdd1_volts)); + #endif +} +EXPORT_SYMBOL(omap_pm_opp_get_volts); + +void omap_pm_opp_set_volts(u8 vdd1_volts[]) { + #ifdef CONFIG_MACH_SIRLOIN_3630 + memcpy(mpu_iva2_vdd1_volts[tidx],vdd1_volts, + sizeof(mpu_iva2_vdd1_volts[tidx])); + prcm_do_voltage_scaling(s_current_vdd1_opp, s_current_vdd1_opp-1); + #else + memcpy(mpu_iva2_vdd1_volts,vdd1_volts,sizeof(mpu_iva2_vdd1_volts)); + prcm_do_voltage_scaling(current_vdd1_opp, current_vdd1_opp-1); + #endif +} +EXPORT_SYMBOL(omap_pm_opp_set_volts); + +void omap_pm_opp_get_vdd2_volts(u8 *vdd2_volt) { + #ifdef CONFIG_MACH_SIRLOIN_3630 + *(vdd2_volt)=(u8 )core_l3_vdd2_volts[tidx][2]; + #else + *(vdd2_volt)=(u8 )core_l3_vdd2_volts[2]; + #endif +} +EXPORT_SYMBOL(omap_pm_opp_get_vdd2_volts); + +void omap_pm_opp_set_vdd2_volts(u8 vdd2_volt) { + #ifdef CONFIG_MACH_SIRLOIN_3630 + core_l3_vdd2_volts[tidx][2]=(u8)vdd2_volt; + prcm_do_voltage_scaling(s_current_vdd2_opp, s_current_vdd2_opp-1); + #else + core_l3_vdd2_volts[2]=(u8)vdd2_volt; + prcm_do_voltage_scaling(current_vdd2_opp, current_vdd2_opp-1); + #endif +} +EXPORT_SYMBOL(omap_pm_opp_set_vdd2_volts); + +void omap_pm_opp_get_vdd2_freq(u8 *vdd2_freq) { + *(vdd2_freq)=(u8)vdd2_core_freq[2].freq; +} +EXPORT_SYMBOL(omap_pm_opp_get_vdd2_freq); + +unsigned int prcm_get_current_vdd1_opp_no(void) { + #ifdef CONFIG_MACH_SIRLOIN_3630 + return get_opp_no(s_current_vdd1_opp); + #else + return get_opp_no(current_vdd1_opp); + #endif +} +EXPORT_SYMBOL(prcm_get_current_vdd1_opp_no); + +unsigned short get_vdd1_arm_opp_for_freq(unsigned int freq) +{ + int i; + for (i = 0; i < ARRAY_SIZE(vdd1_arm_dsp_freq); i++) { + if (vdd1_arm_dsp_freq[i].freq_mpu == (freq / 1000)) { + return i+1; + } + } + return 0; +} +EXPORT_SYMBOL(get_vdd1_arm_opp_for_freq); +#endif + diff --git a/arch/arm/plat-omap/cpu-omap.c b/arch/arm/plat-omap/cpu-omap.c index c4fadca38..7cbe8cfc3 100644 --- a/arch/arm/plat-omap/cpu-omap.c +++ b/arch/arm/plat-omap/cpu-omap.c @@ -174,6 +174,7 @@ static int __init omap_cpu_init(struct cpufreq_policy *policy) if (!result) cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + policy->max = 1000000; } else #endif { diff --git a/drivers/Kconfig b/drivers/Kconfig index f4076d9e9..4cc15e9be 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -95,4 +95,5 @@ source "drivers/kvm/Kconfig" source "drivers/uio/Kconfig" source "drivers/virtio/Kconfig" + endmenu diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index 56ef5ac55..d7bd365dc 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -53,6 +53,23 @@ config CPU_FREQ_STAT_DETAILS If in doubt, say N. +config CPU_FREQ_OVERRIDE + bool "Extra on-demand CPU tweaking options" + default y + help + This will give options to tweak CPU settings in-demand. + + If in doubt, say Y. + +config CPU_FREQ_OVERRIDE_STRIPOPP + bool "Strip OPP1 and OPP2 from available frequencies list" + depends on CPU_FREQ_OVERRIDE + default y + help + This will hide 125MHz and 250MHz from scaling_available_frequencies. + + If in doubt, say N. + choice prompt "Default CPUFreq governor" default CPU_FREQ_DEFAULT_GOV_USERSPACE if CPU_FREQ_SA1100 || CPU_FREQ_SA1110 @@ -87,6 +104,15 @@ config CPU_FREQ_DEFAULT_GOV_USERSPACE program shall be able to set the CPU dynamically without having to enable the userspace governor manually. +config CPU_FREQ_DEFAULT_GOV_SCREENSTATE + bool "screenstate" + select CPU_FREQ_GOV_SCREENSTATE + help + Use the CPUFreq governor 'screenstate' as default. This will + scale the CPU frequency down when the LCD is off then scale + back to max speed when LCD is powered on. This also will not + allow to set the CPU frequency manually. + config CPU_FREQ_DEFAULT_GOV_ONDEMAND bool "ondemand" select CPU_FREQ_GOV_ONDEMAND @@ -162,6 +188,16 @@ config CPU_FREQ_GOV_USERSPACE If in doubt, say Y. +config CPU_FREQ_GOV_SCREENSTATE + tristate "'screenstate' governor for frequency scaling" + help + Enable this cpufreq governor to scale when LCD is on/off. + + To compile this driver as a module, choose M here: the + module will be called cpufreq_screenstate. + + If in doubt, say Y. + config CPU_FREQ_GOV_ONDEMAND tristate "'ondemand' cpufreq policy governor" select CPU_FREQ_TABLE @@ -221,4 +257,78 @@ config CPU_FREQ_GOV_CONSERVATIVE If in doubt, say N. +config CPU_FREQ_GOV_LAGFREE + tristate "'lagfree' cpufreq governor" + depends on CPU_FREQ_OVERRIDE + help + 'lagfree' - this driver is rather similar to the 'ondemand' + governor both in its source code and its purpose, the difference is + its optimisation for better suitability in a battery powered + environment. The frequency is gracefully increased and decreased + rather than jumping to 100% when speed is required. + + To compile this driver as a module, choose M here: the + module will be called cpufreq_lagfree. + + For details, take a look at linux/Documentation/cpu-freq. + + If in doubt, say N. + +config LAGFREE_MAX_LOAD + int "Max CPU Load" + default 50 + depends on CPU_FREQ_GOV_LAGFREE + help + CPU freq will be increased if measured load > max_cpu_load; + +config LAGFREE_MIN_LOAD + int "Min CPU Load" + default 15 + depends on CPU_FREQ_GOV_LAGFREE + help + CPU freq will be decrease if measured load < min_cpu_load; + +config LAGFREE_FREQ_STEP_DOWN + int "Frequency Step Down" + default 100000 + depends on CPU_FREQ_GOV_LAGFREE + help + Max freqeuncy delta when ramping down. + +config LAGFREE_FREQ_SLEEP_MAX + int "Max Sleep frequeny" + default 300000 + depends on CPU_FREQ_GOV_LAGFREE + help + Max freqeuncy for screen off. + +config LAGFREE_FREQ_AWAKE_MIN + int "Min Awake frequeny" + default 150000 + depends on CPU_FREQ_GOV_LAGFREE + help + Min freqeuncy for screen on. + +config LAGFREE_FREQ_STEP_UP_SLEEP_PERCENT + int "Freq step up percent sleep" + default 20 + depends on CPU_FREQ_GOV_LAGFREE + help + Frequency percent to step up while screen off. + +config CPU_FREQ_MIN_TICKS + int "Ticks between governor polling interval." + default 10 + help + Minimum number of ticks between polling interval for governors. + + If in doubt, say N. + +config CPU_FREQ_SAMPLING_LATENCY_MULTIPLIER + int "Sampling rate multiplier for governors." + default 1000 + help + Sampling latency rate multiplied by the cpu switch latency. + Affects governor polling. + endif # CPU_FREQ diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index a59dca8b1..410024896 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -5,12 +5,17 @@ obj-$(CONFIG_CPU_FREQ_STAT) += cpufreq_stats.o # CPUfreq governors obj-$(CONFIG_CPU_FREQ_GOV_PERFORMANCE) += cpufreq_performance.o +obj-$(CONFIG_CPU_FREQ_GOV_LAGFREE) += cpufreq_lagfree.o obj-$(CONFIG_CPU_FREQ_GOV_POWERSAVE) += cpufreq_powersave.o obj-$(CONFIG_CPU_FREQ_GOV_USERSPACE) += cpufreq_userspace.o obj-$(CONFIG_CPU_FREQ_GOV_ONDEMAND) += cpufreq_ondemand.o obj-$(CONFIG_CPU_FREQ_GOV_ONDEMAND_TICKLE) += cpufreq_ondemand_tickle.o obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o +obj-$(CONFIG_CPU_FREQ_GOV_SCREENSTATE) += cpufreq_screenstate.o # CPUfreq cross-arch helpers obj-$(CONFIG_CPU_FREQ_TABLE) += freq_table.o +# CPUfreq override +obj-$(CONFIG_CPU_FREQ_OVERRIDE) += cpufreq_override.o + diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 439b6ad32..ce7723080 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -32,6 +32,11 @@ #define dprintk(msg...) cpufreq_debug_printk(CPUFREQ_DEBUG_CORE, \ "cpufreq-core", msg) +#ifdef CONFIG_CPU_FREQ_OVERRIDE +int cpufreq_override_driver_init(void); +void cpufreq_override_driver_exit(void); +#endif + /** * The "cpufreq driver" - the arch- or hardware-dependent low * level driver of CPUFreq support, and its spinlock. This lock @@ -1682,6 +1687,18 @@ static int __cpufreq_set_policy(struct cpufreq_policy *data, return ret; } +#ifdef CONFIG_CPU_FREQ_OVERRIDE +int cpufreq_set_policy(struct cpufreq_policy *policy) +{ + struct cpufreq_policy *data = cpufreq_cpu_get(0); + __cpufreq_set_policy(data,policy); + data->user_policy.min = data->min; + data->user_policy.max = data->max; + cpufreq_cpu_put(data); +} +EXPORT_SYMBOL(cpufreq_set_policy); +#endif + /** * cpufreq_update_policy - re-evaluate an existing cpufreq policy * @cpu: CPU which shall be re-evaluated @@ -1831,6 +1848,10 @@ int cpufreq_register_driver(struct cpufreq_driver *driver_data) cpufreq_debug_enable_ratelimit(); } +#ifdef CONFIG_CPU_FREQ_OVERRIDE + cpufreq_override_driver_init(); +#endif + return (ret); } EXPORT_SYMBOL_GPL(cpufreq_register_driver); @@ -1864,6 +1885,10 @@ int cpufreq_unregister_driver(struct cpufreq_driver *driver) cpufreq_driver = NULL; spin_unlock_irqrestore(&cpufreq_driver_lock, flags); +#ifdef CONFIG_CPU_FREQ_OVERRIDE + cpufreq_override_driver_exit(); +#endif + return 0; } EXPORT_SYMBOL_GPL(cpufreq_unregister_driver); diff --git a/drivers/cpufreq/cpufreq_lagfree.c b/drivers/cpufreq/cpufreq_lagfree.c new file mode 100644 index 000000000..bf79aa723 --- /dev/null +++ b/drivers/cpufreq/cpufreq_lagfree.c @@ -0,0 +1,710 @@ +/* + * drivers/cpufreq/cpufreq_lagfree.c + * + * Copyright (C) 2001 Russell King + * (C) 2003 Venkatesh Pallipadi . + * Jun Nakajima + * (C) 2004 Alexander Clouter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* + * dbs is used in this file as a shortform for demandbased switching + * It helps to keep variable names smaller, simpler + */ + +#define DEF_FREQUENCY_UP_THRESHOLD CONFIG_LAGFREE_MAX_LOAD +#define DEF_FREQUENCY_DOWN_THRESHOLD CONFIG_LAGFREE_MIN_LOAD +#define FREQ_STEP_DOWN CONFIG_LAGFREE_FREQ_STEP_DOWN +#define FREQ_SLEEP_MAX CONFIG_LAGFREE_FREQ_SLEEP_MAX +#define FREQ_AWAKE_MIN CONFIG_LAGFREE_FREQ_AWAKE_MIN +#define FREQ_STEP_UP_SLEEP_PERCENT CONFIG_LAGFREE_FREQ_STEP_UP_SLEEP_PERCENT + +/* + * The polling frequency of this governor depends on the capability of + * the processor. Default polling frequency is 1000 times the transition + * latency of the processor. The governor will work on any processor with + * transition latency <= 10mS, using appropriate sampling + * rate. + * For CPUs with transition latency > 10mS (mostly drivers + * with CPUFREQ_ETERNAL), this governor will not work. + * All times here are in uS. + */ +static unsigned int def_sampling_rate; +extern bool omap_fb_state; + +#define MIN_SAMPLING_RATE_RATIO (2) +/* for correct statistics, we need at least 10 ticks between each measure */ +#define MIN_STAT_SAMPLING_RATE \ + (MIN_SAMPLING_RATE_RATIO * jiffies_to_usecs(CONFIG_CPU_FREQ_MIN_TICKS)) +#define MIN_SAMPLING_RATE \ + (def_sampling_rate / MIN_SAMPLING_RATE_RATIO) +#define MAX_SAMPLING_RATE (500 * def_sampling_rate) +#define DEF_SAMPLING_DOWN_FACTOR (4) +#define MAX_SAMPLING_DOWN_FACTOR (10) +#define TRANSITION_LATENCY_LIMIT (10 * 1000 * 1000) + +static void do_dbs_timer(struct work_struct *work); + +struct cpu_dbs_info_s { + struct cpufreq_policy *cur_policy; + unsigned int prev_cpu_idle_up; + unsigned int prev_cpu_idle_down; + unsigned int enable; + unsigned int down_skip; + unsigned int requested_freq; +}; +static DEFINE_PER_CPU(struct cpu_dbs_info_s, cpu_dbs_info); + +static unsigned int dbs_enable; /* number of CPUs using this policy */ + +/* + * DEADLOCK ALERT! There is a ordering requirement between cpu_hotplug + * lock and dbs_mutex. cpu_hotplug lock should always be held before + * dbs_mutex. If any function that can potentially take cpu_hotplug lock + * (like __cpufreq_driver_target()) is being called with dbs_mutex taken, then + * cpu_hotplug lock should be taken before that. Note that cpu_hotplug lock + * is recursive for the same process. -Venki + */ +static DEFINE_MUTEX (dbs_mutex); +static DECLARE_DELAYED_WORK(dbs_work, do_dbs_timer); + +struct dbs_tuners { + unsigned int sampling_rate; + unsigned int sampling_down_factor; + unsigned int up_threshold; + unsigned int down_threshold; + unsigned int ignore_nice; + unsigned int freq_step_down; + unsigned int freq_sleep_max; + unsigned int freq_awake_min; + unsigned int freq_step_up_sleep_percent; +}; + +static struct dbs_tuners dbs_tuners_ins = { + .up_threshold = DEF_FREQUENCY_UP_THRESHOLD, + .down_threshold = DEF_FREQUENCY_DOWN_THRESHOLD, + .sampling_down_factor = DEF_SAMPLING_DOWN_FACTOR, + .ignore_nice = 1, + .freq_step_down = FREQ_STEP_DOWN, + .freq_sleep_max = FREQ_SLEEP_MAX, + .freq_awake_min = FREQ_AWAKE_MIN, + .freq_step_up_sleep_percent = FREQ_STEP_UP_SLEEP_PERCENT, +}; + +static bool issuspended(void) +{ + // toggle fb state to suspend mode. too lazy to clean up. + return omap_fb_state ? 0 : 1; +} + +static inline unsigned int get_cpu_idle_time(unsigned int cpu) +{ + unsigned int add_nice = 0, ret; + + if (dbs_tuners_ins.ignore_nice) + add_nice = kstat_cpu(cpu).cpustat.nice; + + ret = kstat_cpu(cpu).cpustat.idle + + kstat_cpu(cpu).cpustat.iowait + + add_nice; + + return ret; +} + +/* keep track of frequency transitions */ +static int +dbs_cpufreq_notifier(struct notifier_block *nb, unsigned long val, + void *data) +{ + struct cpufreq_freqs *freq = data; + struct cpu_dbs_info_s *this_dbs_info = &per_cpu(cpu_dbs_info, + freq->cpu); + + if (!this_dbs_info->enable) + return 0; + + this_dbs_info->requested_freq = freq->new; + + return 0; +} + +static struct notifier_block dbs_cpufreq_notifier_block = { + .notifier_call = dbs_cpufreq_notifier +}; + +/************************** sysfs interface ************************/ +static ssize_t show_sampling_rate_max(struct cpufreq_policy *policy, char *buf) +{ + return sprintf (buf, "%u\n", MAX_SAMPLING_RATE); +} + +static ssize_t show_sampling_rate_min(struct cpufreq_policy *policy, char *buf) +{ + return sprintf (buf, "%u\n", MIN_SAMPLING_RATE); +} + +#define define_one_ro(_name) \ +static struct freq_attr _name = \ +__ATTR(_name, 0444, show_##_name, NULL) + +define_one_ro(sampling_rate_max); +define_one_ro(sampling_rate_min); + +/* cpufreq_lagfree Governor Tunables */ +#define show_one(file_name, object) \ +static ssize_t show_##file_name \ +(struct cpufreq_policy *unused, char *buf) \ +{ \ + return sprintf(buf, "%u\n", dbs_tuners_ins.object); \ +} +show_one(sampling_rate, sampling_rate); +show_one(sampling_down_factor, sampling_down_factor); +show_one(up_threshold, up_threshold); +show_one(down_threshold, down_threshold); +show_one(ignore_nice_load, ignore_nice); +show_one(freq_step_down, freq_step_down); +show_one(freq_sleep_max, freq_sleep_max); +show_one(freq_awake_min, freq_awake_min); +show_one(freq_step_up_sleep_percent, freq_step_up_sleep_percent); + +static ssize_t store_sampling_down_factor(struct cpufreq_policy *unused, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf (buf, "%u", &input); + if (ret != 1 || input > MAX_SAMPLING_DOWN_FACTOR || input < 1) + return -EINVAL; + + mutex_lock(&dbs_mutex); + dbs_tuners_ins.sampling_down_factor = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_sampling_rate(struct cpufreq_policy *unused, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf (buf, "%u", &input); + + mutex_lock(&dbs_mutex); + if (ret != 1 || input > MAX_SAMPLING_RATE || input < MIN_SAMPLING_RATE) { + mutex_unlock(&dbs_mutex); + return -EINVAL; + } + + dbs_tuners_ins.sampling_rate = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_up_threshold(struct cpufreq_policy *unused, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf (buf, "%u", &input); + + mutex_lock(&dbs_mutex); + if (ret != 1 || input > 100 || input <= dbs_tuners_ins.down_threshold) { + mutex_unlock(&dbs_mutex); + return -EINVAL; + } + + dbs_tuners_ins.up_threshold = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_down_threshold(struct cpufreq_policy *unused, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf (buf, "%u", &input); + + mutex_lock(&dbs_mutex); + if (ret != 1 || input > 100 || input >= dbs_tuners_ins.up_threshold) { + mutex_unlock(&dbs_mutex); + return -EINVAL; + } + + dbs_tuners_ins.down_threshold = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_ignore_nice_load(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + + unsigned int j; + + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; + + if (input > 1) + input = 1; + + mutex_lock(&dbs_mutex); + if (input == dbs_tuners_ins.ignore_nice) { /* nothing to do */ + mutex_unlock(&dbs_mutex); + return count; + } + dbs_tuners_ins.ignore_nice = input; + + /* we need to re-evaluate prev_cpu_idle_up and prev_cpu_idle_down */ + for_each_online_cpu(j) { + struct cpu_dbs_info_s *j_dbs_info; + j_dbs_info = &per_cpu(cpu_dbs_info, j); + j_dbs_info->prev_cpu_idle_up = get_cpu_idle_time(j); + j_dbs_info->prev_cpu_idle_down = j_dbs_info->prev_cpu_idle_up; + } + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_freq_step_down(struct cpufreq_policy *unused, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf (buf, "%u", &input); + if (input > 100 || input < 1) + return -EINVAL; + + mutex_lock(&dbs_mutex); + dbs_tuners_ins.freq_step_down = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_freq_step_up_sleep_percent(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf (buf, "%u", &input); + if (input > policy->cpuinfo.max_freq || input < 1) + return -EINVAL; + + mutex_lock(&dbs_mutex); + dbs_tuners_ins.freq_step_up_sleep_percent = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_freq_sleep_max(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf (buf, "%u", &input); + if (input > policy->cpuinfo.max_freq || input < policy->cpuinfo.min_freq) + return -EINVAL; + + mutex_lock(&dbs_mutex); + dbs_tuners_ins.freq_sleep_max = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_freq_awake_min(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf (buf, "%u", &input); + if (input > policy->cpuinfo.max_freq || input < policy->cpuinfo.min_freq) + return -EINVAL; + + mutex_lock(&dbs_mutex); + dbs_tuners_ins.freq_awake_min = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +#define define_one_rw(_name) \ +static struct freq_attr _name = \ +__ATTR(_name, 0644, show_##_name, store_##_name) + +define_one_rw(sampling_rate); +define_one_rw(sampling_down_factor); +define_one_rw(up_threshold); +define_one_rw(down_threshold); +define_one_rw(ignore_nice_load); +define_one_rw(freq_step_down); +define_one_rw(freq_sleep_max); +define_one_rw(freq_awake_min); +define_one_rw(freq_step_up_sleep_percent); + +static struct attribute * dbs_attributes[] = { + &sampling_rate_max.attr, + &sampling_rate_min.attr, + &sampling_rate.attr, + &sampling_down_factor.attr, + &up_threshold.attr, + &down_threshold.attr, + &ignore_nice_load.attr, + &freq_step_down.attr, + &freq_sleep_max.attr, + &freq_awake_min.attr, + &freq_step_up_sleep_percent.attr, + NULL +}; + +static struct attribute_group dbs_attr_group = { + .attrs = dbs_attributes, + .name = "lagfree", +}; + +/************************** sysfs end ************************/ + +static void dbs_check_cpu(int cpu) +{ + unsigned int idle_ticks, up_idle_ticks, down_idle_ticks; + unsigned int tmp_idle_ticks, total_idle_ticks; + unsigned int freq_target; + unsigned int freq_down_sampling_rate; + struct cpu_dbs_info_s *this_dbs_info = &per_cpu(cpu_dbs_info, cpu); + struct cpufreq_policy *policy; + + if (!this_dbs_info->enable) + return; + + policy = this_dbs_info->cur_policy; + + /* + * The default safe range is 20% to 80% + * Every sampling_rate, we check + * - If current idle time is less than 20%, then we try to + * increase frequency + * Every sampling_rate*sampling_down_factor, we check + * - If current idle time is more than 80%, then we try to + * decrease frequency + * + * Any frequency increase takes it to the maximum frequency. + * Frequency reduction happens at minimum steps of + * 5% (default) of max_frequency + */ + + /* Check for frequency increase */ + idle_ticks = UINT_MAX; + + /* Check for frequency increase */ + total_idle_ticks = get_cpu_idle_time(cpu); + tmp_idle_ticks = total_idle_ticks - + this_dbs_info->prev_cpu_idle_up; + this_dbs_info->prev_cpu_idle_up = total_idle_ticks; + + if (tmp_idle_ticks < idle_ticks) + idle_ticks = tmp_idle_ticks; + + /* Scale idle ticks by 100 and compare with up and down ticks */ + idle_ticks *= 100; + up_idle_ticks = (100 - dbs_tuners_ins.up_threshold) * + usecs_to_jiffies(dbs_tuners_ins.sampling_rate); + + if (idle_ticks < up_idle_ticks) { + this_dbs_info->down_skip = 0; + this_dbs_info->prev_cpu_idle_down = + this_dbs_info->prev_cpu_idle_up; + + /* if we are already at full speed then break out early */ + if (this_dbs_info->requested_freq == policy->max && !issuspended()) + return; + + //freq_target = (dbs_tuners_ins.freq_step * policy->max) / 100; + if (issuspended()) + freq_target = (dbs_tuners_ins.freq_step_up_sleep_percent * policy->max) / 100; + else + freq_target = policy->max; + + /* max freq cannot be less than 100. But who knows.... */ + if (unlikely(freq_target == 0)) + freq_target = 5; + + this_dbs_info->requested_freq += freq_target; + if (this_dbs_info->requested_freq > policy->max) + this_dbs_info->requested_freq = policy->max; + + //Screen off mode + if (issuspended() && this_dbs_info->requested_freq > dbs_tuners_ins.freq_sleep_max) + this_dbs_info->requested_freq = dbs_tuners_ins.freq_sleep_max; + + //Screen off mode + if (!issuspended() && this_dbs_info->requested_freq < dbs_tuners_ins.freq_awake_min) + this_dbs_info->requested_freq = dbs_tuners_ins.freq_awake_min; + + __cpufreq_driver_target(policy, this_dbs_info->requested_freq, + CPUFREQ_RELATION_H); + return; + } + + /* Check for frequency decrease */ + this_dbs_info->down_skip++; + if (this_dbs_info->down_skip < dbs_tuners_ins.sampling_down_factor) + return; + + /* Check for frequency decrease */ + total_idle_ticks = this_dbs_info->prev_cpu_idle_up; + tmp_idle_ticks = total_idle_ticks - + this_dbs_info->prev_cpu_idle_down; + this_dbs_info->prev_cpu_idle_down = total_idle_ticks; + + if (tmp_idle_ticks < idle_ticks) + idle_ticks = tmp_idle_ticks; + + /* Scale idle ticks by 100 and compare with up and down ticks */ + idle_ticks *= 100; + this_dbs_info->down_skip = 0; + + freq_down_sampling_rate = dbs_tuners_ins.sampling_rate * + dbs_tuners_ins.sampling_down_factor; + down_idle_ticks = (100 - dbs_tuners_ins.down_threshold) * + usecs_to_jiffies(freq_down_sampling_rate); + + if (idle_ticks > down_idle_ticks) { + /* + * if we are already at the lowest speed then break out early + * or if we 'cannot' reduce the speed as the user might want + * freq_target to be zero + */ + if (this_dbs_info->requested_freq == policy->min && issuspended() + /*|| dbs_tuners_ins.freq_step == 0*/) + return; + + //freq_target = (dbs_tuners_ins.freq_step * policy->max) / 100; + freq_target = dbs_tuners_ins.freq_step_down; //policy->max; + + /* max freq cannot be less than 100. But who knows.... */ + if (unlikely(freq_target == 0)) + freq_target = 5; + + // prevent going under 0 + if(freq_target > this_dbs_info->requested_freq) + this_dbs_info->requested_freq = policy->min; + else + this_dbs_info->requested_freq -= freq_target; + + if (this_dbs_info->requested_freq < policy->min) + this_dbs_info->requested_freq = policy->min; + + //Screen on mode + if (!issuspended() && this_dbs_info->requested_freq < dbs_tuners_ins.freq_awake_min) + this_dbs_info->requested_freq = dbs_tuners_ins.freq_awake_min; + + //Screen off mode + if (issuspended() && this_dbs_info->requested_freq > dbs_tuners_ins.freq_sleep_max) + this_dbs_info->requested_freq = dbs_tuners_ins.freq_sleep_max; + + __cpufreq_driver_target(policy, this_dbs_info->requested_freq, + CPUFREQ_RELATION_H); + return; + } +} + +static void do_dbs_timer(struct work_struct *work) +{ + int i; + mutex_lock(&dbs_mutex); + for_each_online_cpu(i) + dbs_check_cpu(i); + schedule_delayed_work(&dbs_work, + usecs_to_jiffies(dbs_tuners_ins.sampling_rate)); + mutex_unlock(&dbs_mutex); +} + +static inline void dbs_timer_init(void) +{ + init_timer_deferrable(&dbs_work.timer); + schedule_delayed_work(&dbs_work, + usecs_to_jiffies(dbs_tuners_ins.sampling_rate)); + return; +} + +static inline void dbs_timer_exit(void) +{ + cancel_delayed_work(&dbs_work); + return; +} + +static int cpufreq_governor_dbs(struct cpufreq_policy *policy, + unsigned int event) +{ + unsigned int cpu = policy->cpu; + struct cpu_dbs_info_s *this_dbs_info; + unsigned int j; + int rc; + + this_dbs_info = &per_cpu(cpu_dbs_info, cpu); + + switch (event) { + case CPUFREQ_GOV_START: + if ((!cpu_online(cpu)) || (!policy->cur)) + return -EINVAL; + + if (this_dbs_info->enable) /* Already enabled */ + break; + + mutex_lock(&dbs_mutex); + + rc = sysfs_create_group(&policy->kobj, &dbs_attr_group); + if (rc) { + mutex_unlock(&dbs_mutex); + return rc; + } + +// for_each_cpu(j, policy->cpus) { + for(j=0 ; j < NR_CPUS ; ++j) { + struct cpu_dbs_info_s *j_dbs_info; + j_dbs_info = &per_cpu(cpu_dbs_info, j); + j_dbs_info->cur_policy = policy; + + j_dbs_info->prev_cpu_idle_up = get_cpu_idle_time(cpu); + j_dbs_info->prev_cpu_idle_down + = j_dbs_info->prev_cpu_idle_up; + } + this_dbs_info->enable = 1; + this_dbs_info->down_skip = 0; + this_dbs_info->requested_freq = policy->cur; + + dbs_enable++; + /* + * Start the timerschedule work, when this governor + * is used for first time + */ + if (dbs_enable == 1) { + unsigned int latency; + /* policy latency is in nS. Convert it to uS first */ + latency = policy->cpuinfo.transition_latency / 1000; + if (latency == 0) + latency = 1; + + def_sampling_rate = 10 * latency * + CONFIG_CPU_FREQ_SAMPLING_LATENCY_MULTIPLIER; + + if (def_sampling_rate < MIN_STAT_SAMPLING_RATE) + def_sampling_rate = MIN_STAT_SAMPLING_RATE; + + dbs_tuners_ins.sampling_rate = def_sampling_rate; + + dbs_timer_init(); + cpufreq_register_notifier( + &dbs_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + } + + mutex_unlock(&dbs_mutex); + break; + + case CPUFREQ_GOV_STOP: + mutex_lock(&dbs_mutex); + this_dbs_info->enable = 0; + sysfs_remove_group(&policy->kobj, &dbs_attr_group); + dbs_enable--; + /* + * Stop the timerschedule work, when this governor + * is used for first time + */ + if (dbs_enable == 0) { + dbs_timer_exit(); + cpufreq_unregister_notifier( + &dbs_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + } + + mutex_unlock(&dbs_mutex); + + break; + + case CPUFREQ_GOV_LIMITS: + mutex_lock(&dbs_mutex); + if (policy->max < this_dbs_info->cur_policy->cur) + __cpufreq_driver_target( + this_dbs_info->cur_policy, + policy->max, CPUFREQ_RELATION_H); + else if (policy->min > this_dbs_info->cur_policy->cur) + __cpufreq_driver_target( + this_dbs_info->cur_policy, + policy->min, CPUFREQ_RELATION_L); + mutex_unlock(&dbs_mutex); + break; + } + return 0; +} + +#ifndef CONFIG_CPU_FREQ_DEFAULT_GOV_LAGFREE +static +#endif +struct cpufreq_governor cpufreq_gov_lagfree = { + .name = "lagfree", + .governor = cpufreq_governor_dbs, + .max_transition_latency = TRANSITION_LATENCY_LIMIT, + .owner = THIS_MODULE, +}; + +static int __init cpufreq_gov_dbs_init(void) +{ + return cpufreq_register_governor(&cpufreq_gov_lagfree); +} + +static void __exit cpufreq_gov_dbs_exit(void) +{ + /* Make sure that the scheduled work is indeed not running */ + flush_scheduled_work(); + + cpufreq_unregister_governor(&cpufreq_gov_lagfree); +} + + +MODULE_AUTHOR ("Emilio López "); +MODULE_DESCRIPTION ("'cpufreq_lagfree' - A dynamic cpufreq governor for " + "Low Latency Frequency Transition capable processors " + "optimised for use in a battery environment" + "Based on conservative by Alexander Clouter"); +MODULE_LICENSE ("GPL"); + +#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_LAGFREE +fs_initcall(cpufreq_gov_dbs_init); +#else +module_init(cpufreq_gov_dbs_init); +#endif +module_exit(cpufreq_gov_dbs_exit); + diff --git a/drivers/cpufreq/cpufreq_ondemand_tickle.c b/drivers/cpufreq/cpufreq_ondemand_tickle.c index 01b6dae32..7ffa86ebd 100644 --- a/drivers/cpufreq/cpufreq_ondemand_tickle.c +++ b/drivers/cpufreq/cpufreq_ondemand_tickle.c @@ -255,6 +255,30 @@ static int get_num_sample_records(void) { return count; } +static bool ss_enabled = 0; +static unsigned int sleep_max_freq = 300000; +extern bool omap_fb_state; + +static int cpufreq_target(struct cpufreq_policy *policy, unsigned int freq, + unsigned int relation) +{ + int retval = -EINVAL; + + if(omap_fb_state || !ss_enabled) { + retval = __cpufreq_driver_target(policy, freq, relation); + } + else { + if(freq <= sleep_max_freq) + retval = __cpufreq_driver_target(policy, freq, + relation); + else + retval = __cpufreq_driver_target(policy, sleep_max_freq, + relation); + } + + return retval; +} + static void *stats_start(struct seq_file *m, loff_t *pos) { struct stats_state *state = kmalloc(sizeof(struct stats_state), GFP_KERNEL); @@ -655,6 +679,16 @@ static ssize_t show_sampling_rate_min(struct cpufreq_policy *policy, char *buf) return sprintf (buf, "%u\n", MIN_SAMPLING_RATE); } +static ssize_t show_screen_off_max_freq(struct cpufreq_policy *unused, char *buf) +{ + return sprintf(buf, "%u\n", sleep_max_freq); +} + +static ssize_t show_screenstate_enable(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%u\n", ss_enabled); +} + #define define_one_ro(_name) \ static struct freq_attr _name = \ __ATTR(_name, 0444, show_##_name, NULL) @@ -811,6 +845,45 @@ static ssize_t store_max_floor_window(struct cpufreq_policy *unuesd, return count; } +static ssize_t store_screen_off_max_freq(struct cpufreq_policy *unuesd, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + + if (ret != 1) + return -EINVAL; + + if (input < 150000 || input > 2000000) { + printk("ondemand-ng: invalid sleep freq\n"); + return -EINVAL; + } + + sleep_max_freq = input; + + return count; +} + +static ssize_t store_screenstate_enable(struct cpufreq_policy *unuesd, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + + if (ret != 1) + return -EINVAL; + + if (input != 0 && input != 1) { + return -EINVAL; + } + + ss_enabled = input; + + return count; +} + #define define_one_rw(_name) \ static struct freq_attr _name = \ __ATTR(_name, 0644, show_##_name, store_##_name) @@ -821,6 +894,8 @@ define_one_rw(ignore_nice_load); define_one_rw(powersave_bias); define_one_rw(max_tickle_window); define_one_rw(max_floor_window); +define_one_rw(screen_off_max_freq); +define_one_rw(screenstate_enable); static struct attribute * dbs_attributes[] = { &sampling_rate_max.attr, @@ -831,6 +906,8 @@ static struct attribute * dbs_attributes[] = { &powersave_bias.attr, &max_tickle_window.attr, &max_floor_window.attr, + &screen_off_max_freq.attr, + &screenstate_enable.attr, NULL }; @@ -1132,7 +1209,7 @@ static void do_floor_state_change(struct work_struct *work) } dbs_info->floor_active = 0; - __cpufreq_driver_target(policy, dbs_info->freq_save, dbs_info->rel_save); + cpufreq_target(policy, dbs_info->freq_save, dbs_info->rel_save); unlock_policy_rwsem_write(cpu); } @@ -1329,7 +1406,7 @@ static void adjust_for_load(struct cpu_dbs_info_s *this_dbs_info) this_dbs_info->rel_save = CPUFREQ_RELATION_H; } - __cpufreq_driver_target(policy, policy->max, + cpufreq_target(policy, policy->max, CPUFREQ_RELATION_H); } else { int freq = powersave_bias_target(policy, policy->max, @@ -1344,7 +1421,7 @@ static void adjust_for_load(struct cpu_dbs_info_s *this_dbs_info) } record_sample(policy->cur, freq, load, policy->cpu); - __cpufreq_driver_target(policy, freq, + cpufreq_target(policy, freq, CPUFREQ_RELATION_L); } return; @@ -1381,7 +1458,7 @@ static void adjust_for_load(struct cpu_dbs_info_s *this_dbs_info) } record_sample(policy->cur, freq_next, load, policy->cpu); - __cpufreq_driver_target(policy, freq_next, + cpufreq_target(policy, freq_next, CPUFREQ_RELATION_L); } else { int freq = powersave_bias_target(policy, freq_next, @@ -1395,7 +1472,7 @@ static void adjust_for_load(struct cpu_dbs_info_s *this_dbs_info) freq = this_dbs_info->freq_floor; } - __cpufreq_driver_target(policy, freq, + cpufreq_target(policy, freq, CPUFREQ_RELATION_L); record_sample(policy->cur, freq, load, policy->cpu); } @@ -1436,7 +1513,7 @@ static void do_dbs_timer(struct work_struct *work) } } else { record_sample(dbs_info->cur_policy->cur, dbs_info->freq_lo, -1, cpu); - __cpufreq_driver_target(dbs_info->cur_policy, + cpufreq_target(dbs_info->cur_policy, dbs_info->freq_lo, CPUFREQ_RELATION_H); } @@ -1636,12 +1713,12 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy, mutex_lock(&dbs_mutex); if (policy->max < this_dbs_info->cur_policy->cur) { record_sample(policy->cur, policy->max, -1, policy->cpu); - __cpufreq_driver_target(this_dbs_info->cur_policy, + cpufreq_target(this_dbs_info->cur_policy, policy->max, CPUFREQ_RELATION_H); } else if (policy->min > this_dbs_info->cur_policy->cur) { record_sample(policy->cur, policy->min, -1, policy->cpu); - __cpufreq_driver_target(this_dbs_info->cur_policy, + cpufreq_target(this_dbs_info->cur_policy, policy->min, CPUFREQ_RELATION_L); } diff --git a/drivers/cpufreq/cpufreq_override.c b/drivers/cpufreq/cpufreq_override.c new file mode 100644 index 000000000..2f7f661cb --- /dev/null +++ b/drivers/cpufreq/cpufreq_override.c @@ -0,0 +1,519 @@ +/* + * drivers/cpufreq/cpufreq_override.c + * + * Marco Benton . + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// VDD1 Vsel max +#define VDD1_VSEL_MAX 112 + +// VDD1 Vsel min +#define VDD1_VSEL_MIN 25 + +// VDD2 Vsel max +#define VDD2_VSEL_MAX 55 + +// VDD2 Vsel min +#define VDD2_VSEL_MIN 33 + +// High temp alarm and cap +#ifdef CONFIG_MACH_SIRLOIN_3630 +#define HIGHTEMP_SCALEBACK 48 +#else +#define HIGHTEMP_SCALEBACK 50 +#endif + +//Reset temp from alarm +#ifdef CONFIG_MACH_SIRLOIN_3630 +#define LOWTEMP_RESET 45 +#else +#define LOWTEMP_RESET 47 +#endif + +// Polling frequency secs +#define BATTERY_POLLING 600 + +// Polling frequency secs +#ifdef CONFIG_MACH_SIRLOIN_3630 +#define TEMP_POLLING 1 +#else +#define TEMP_POLLING 3 +#endif + +// Battery scaleback percent +#define BATTERY_PERCENT 20 + +// Scaleback speed +#ifdef CONFIG_MACH_SIRLOIN_3630 +#define SCALEBACK_SPEED 1000000 +#else +#define SCALEBACK_SPEED 500000 +#endif + +void omap_pm_opp_get_volts(u8 vdd1_volts[]), + omap_pm_opp_set_volts(u8 vdd1_volts[]), + omap_pm_opp_get_vdd2_volts(u8 * vdd2_volt), + omap_pm_opp_set_vdd2_volts(u8 vdd2_volt), + omap_pm_opp_get_vdd2_freq(u8 * vdd2_freq); + +int omap34xx_get_temp(void), + cpufreq_set_policy(struct cpufreq_policy *policy), + ds2784_getpercent(int *ret_percent); + +static inline void + check_temp(struct work_struct *work), + check_battery(struct work_struct *work); + +unsigned short + get_vdd1_arm_opp_for_freq(unsigned int freq); + +struct ovrd { + bool overtemp_alarm; + bool battery_alarm; + u32 prev_maxspeed; + u32 temp_scaleback_high; + u32 temp_scaleback_low; + u32 temp_scaleback_speed; + u32 battery_scaleback_percent; + u32 battery_scaleback_speed; +} ovrdcfg = {0, 0, 0, HIGHTEMP_SCALEBACK, LOWTEMP_RESET, + SCALEBACK_SPEED, BATTERY_PERCENT, + SCALEBACK_SPEED}; + +static struct ovrd *ovrd_policy = &ovrdcfg; + +static DECLARE_DELAYED_WORK(worker, check_battery); +static DECLARE_DELAYED_WORK(worker2, check_temp); + +#define CPUFREQ_OVERRIDE_ATTR(_name,_func) \ +static struct freq_attr _attr_##_name = {\ + .attr = {.name = __stringify(_name), .mode = 0644, }, \ + .show = show_##_func,\ + .store = store_##_func,\ +}; + +#define CPUFREQ_OVERRIDE_ATTR2(_name,_show) \ +static struct freq_attr _attr_##_name = {\ + .attr = {.name = __stringify(_name), .mode = 0444, }, \ + .show = _show,\ +}; + +/*static unsigned int jiffies_to_secs(unsigned long int jifs) { + return jifs / HZ; +} */ + +static unsigned long int secs_to_jiffies(unsigned int secs) +{ + return secs * HZ; +} + +static bool check_freq(unsigned int freq) +{ + if(get_vdd1_arm_opp_for_freq(freq)) return 1; + else return 0; +} + +static void change_freq_low(unsigned int freq) +{ + struct cpufreq_policy new_policy; + cpufreq_get_policy(&new_policy, 0); + ovrd_policy->prev_maxspeed = new_policy.max; + new_policy.max = freq; + cpufreq_set_policy(&new_policy); +} + +static void change_freq_high(void) +{ + struct cpufreq_policy new_policy; + cpufreq_get_policy(&new_policy, 0); + new_policy.max = ovrd_policy->prev_maxspeed; + cpufreq_set_policy(&new_policy); + ovrd_policy->overtemp_alarm = 0; +} + +static inline void check_temp(struct work_struct *work) +{ + u32 cputemp; + + if (ovrd_policy->battery_alarm) + goto out; + + cputemp = omap34xx_get_temp(); // Get CPU temp + + // Check values in case driver hasnt polled + cputemp = (cputemp < 100) ? cputemp : 0; + + if (cputemp > ovrd_policy->temp_scaleback_high) { + if (!ovrd_policy->overtemp_alarm) { + printk("override: CPU temp warning! %dC\n", cputemp); + ovrd_policy->overtemp_alarm = 1; + change_freq_low(ovrd_policy->temp_scaleback_speed); + } + } else { + if ((ovrd_policy->overtemp_alarm) && + (cputemp < ovrd_policy->temp_scaleback_low)) { + printk("override: CPU temp back under control! %dC\n", + cputemp); + ovrd_policy->overtemp_alarm = 0; + change_freq_high(); + } + } + out: + schedule_delayed_work(&worker2, secs_to_jiffies(TEMP_POLLING)); +} + +static inline void check_battery(struct work_struct *work) +{ + int battery_per; + + if (ovrd_policy->overtemp_alarm) + goto out; + + ds2784_getpercent(&battery_per); // Get battery percent left + battery_per = (battery_per > 0) ? battery_per : 100; + + if (battery_per < ovrd_policy->battery_scaleback_percent) { + if (!ovrd_policy->battery_alarm) { + printk("override: battery low! < %d%%\n", battery_per); + ovrd_policy->battery_alarm = 1; + change_freq_low(ovrd_policy->battery_scaleback_speed); + } + } else { + if (ovrd_policy->battery_alarm) { + printk("override: battery OK\n"); + ovrd_policy->battery_alarm = 0; + change_freq_high(); + } + } + + out: + schedule_delayed_work(&worker, secs_to_jiffies(BATTERY_POLLING)); +} + +static ssize_t show_vdd1_vsel_max(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%hu\n", VDD1_VSEL_MAX); +} + +static ssize_t show_vdd1_vsel_min(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%hu\n", VDD1_VSEL_MIN); +} + +static ssize_t show_vdd1_vsel(struct cpufreq_policy *policy, char *buf) +{ + u8 volt[PRCM_NO_VDD1_OPPS]; + + omap_pm_opp_get_volts(volt); + +#ifdef CONFIG_CPU_FREQ_OVERRIDE_STRIPOPP +#if PRCM_NO_VDD1_OPPS > 5 +#ifdef CONFIG_MACH_SIRLOIN_3630 + return sprintf(buf, "%hu %hu %hu %hu %hu %hu\n", volt[6], + volt[5], volt[4], volt[3], volt[2], volt[1]); +#else + return sprintf(buf, "%hu %hu %hu %hu %hu\n", volt[6], + volt[5], volt[4], volt[3], volt[2]); +#endif +#else // PRCM_NO_VDD1_OPPS > 5 +#ifdef CONFIG_MACH_SIRLOIN_3630 + return sprintf(buf, "%hu %hu %hu %hu\n", volt[4], volt[3], + volt[2], volt[1]); +#else + return sprintf(buf, "%hu %hu %hu\n", volt[4], volt[3], volt[2]); +#endif + +#endif +#else // CONFIG_CPU_FREQ_OVERRIDE_STRIPOPP +#if PRCM_NO_VDD1_OPPS > 5 + return sprintf(buf, "%hu %hu %hu %hu %hu %hu %hu\n", volt[6], + volt[5], volt[4], volt[3], volt[2], volt[1], volt[0]); +#else + return sprintf(buf, "%hu %hu %hu %hu %hu\n", volt[4], + volt[3], volt[2], volt[1], volt[0]); +#endif +#endif + return sprintf(buf, "N/A\n"); +} + +static ssize_t show_vdd2_vsel_max(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%hu\n", VDD2_VSEL_MAX); +} + +static ssize_t show_vdd2_vsel_min(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%hu\n", VDD2_VSEL_MIN); +} + +static ssize_t show_vdd2_vsel(struct cpufreq_policy *policy, char *buf) +{ + u8 volt; + + omap_pm_opp_get_vdd2_volts(&volt); + return sprintf(buf, "%hu\n", volt); +} + +static ssize_t show_vdd2_freqs(struct cpufreq_policy *policy, char *buf) +{ + u8 freq; + + omap_pm_opp_get_vdd2_freq(&freq); + return sprintf(buf, "%hu\n", freq); +} + +static ssize_t +store_vdd1_vsel(struct cpufreq_policy *policy, const char *buf, size_t count) +{ + u8 volt[PRCM_NO_VDD1_OPPS], i; + + omap_pm_opp_get_volts(volt); + +#if PRCM_NO_VDD1_OPPS > 5 + i = (sscanf(buf, "%hhu %hhu %hhu %hhu %hhu %hhu %hhu", + &volt[6], &volt[5], &volt[4], + &volt[3], &volt[2], &volt[1], &volt[0])); +#else + i = (sscanf(buf, "%hhu %hhu %hhu %hhu %hhu", + &volt[4], &volt[3], &volt[2], &volt[1], &volt[0])); +#endif + +#ifdef CONFIG_CPU_FREQ_OVERRIDE_STRIPOPP +#ifdef CONFIG_MACH_SIRLOIN_3630 + if (i == (PRCM_NO_VDD1_OPPS - 1)) { +#else // CONFIG_MACH_SIRLOIN_3630 + if (i == (PRCM_NO_VDD1_OPPS - 2)) { +#endif +#else // CONFIG_CPU_FREQ_OVERRIDE_STRIPOPP + if (i == PRCM_NO_VDD1_OPPS) { +#endif + +#ifdef CONFIG_CPU_FREQ_OVERRIDE_STRIPOPP +#ifdef CONFIG_MACH_SIRLOIN_3630 + for (i = 1; i < PRCM_NO_VDD1_OPPS; i++) { +#else // CONFIG_MACH_SIRLOIN_3630 + for (i = 2; i < PRCM_NO_VDD1_OPPS; i++) { +#endif +#else // CONFIG_CPU_FREQ_OVERRIDE_STRIPOPP + for (i = 0; i < PRCM_NO_VDD1_OPPS; i++) { +#endif + if ((volt[i] < VDD1_VSEL_MIN) + || (volt[i] > VDD1_VSEL_MAX)) { + printk("override: invalid vsel\n"); + break; + } + } + if (i == PRCM_NO_VDD1_OPPS) { + omap_pm_opp_set_volts(volt); + } + } else + printk("override: missing vsel values\n"); + + return count; +} + +static ssize_t +store_vdd2_vsel(struct cpufreq_policy *policy, const char *buf, size_t count) +{ + u8 volt; + + if (sscanf(buf, "%hhu", &volt) == 1) { + if ((volt < VDD2_VSEL_MIN) || (volt > VDD2_VSEL_MAX)) { + printk("override: invalid vsel\n"); + } else + omap_pm_opp_set_vdd2_volts(volt); + } else + printk("override: missing vsel values\n"); + + return count; +} + +static ssize_t +show_hightemp_scaleback(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%u\n", ovrd_policy->temp_scaleback_high); +} + +static ssize_t +store_hightemp_scaleback(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned int maxtemp = 0; + + if (sscanf(buf, "%u", &maxtemp) == 1) + ovrd_policy->temp_scaleback_high = (maxtemp < 60) + ? maxtemp : HIGHTEMP_SCALEBACK; + else + printk("override: invalid max temp\n"); + + return count; +} + +static ssize_t +show_battery_scaleback_per(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%u\n", ovrd_policy->battery_scaleback_percent); +} + +static ssize_t +store_battery_scaleback_per(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned int bat = 0; + + if (sscanf(buf, "%u", &bat) == 1) + ovrd_policy->battery_scaleback_percent = (bat < 101) + ? bat : BATTERY_PERCENT; + else + printk("override: invalid battery percentage\n"); + + return count; +} + +static ssize_t +show_battery_scaleback_speed(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%u\n", ovrd_policy->battery_scaleback_speed); +} + +static ssize_t +store_battery_scaleback_speed(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned int bat = 0; + + if (sscanf(buf, "%u", &bat) == 1) + ovrd_policy->battery_scaleback_speed = (check_freq(bat)) + ? bat : SCALEBACK_SPEED; + else + printk("override: invalid battery scaleback speed\n"); + + return count; +} + +static ssize_t +show_lowtemp_reset(struct cpufreq_policy *policy, char *buf) +{ + + return sprintf(buf, "%u\n", ovrd_policy->temp_scaleback_low); +} + +static ssize_t +store_lowtemp_reset(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned int lowtemp = 0; + + if (sscanf(buf, "%u", &lowtemp) == 1) + ovrd_policy->temp_scaleback_low = + (lowtemp < 60) ? lowtemp : LOWTEMP_RESET; + else + printk("override: invalid low temp\n"); + + return count; +} + +static ssize_t +show_temp_scaleback(struct cpufreq_policy *policy, char *buf) +{ + + return sprintf(buf, "%u\n", ovrd_policy->temp_scaleback_speed); +} + +static ssize_t +store_temp_scaleback(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned int tempscaleback = 0; + + if (sscanf(buf, "%u", &tempscaleback) == 1) + ovrd_policy->temp_scaleback_speed = (check_freq(tempscaleback)) + ? tempscaleback : SCALEBACK_SPEED; + else + printk("override: invalid scaleback speed\n"); + + return count; +} + +CPUFREQ_OVERRIDE_ATTR(vdd1_vsel, vdd1_vsel); +CPUFREQ_OVERRIDE_ATTR(vdd2_vsel, vdd2_vsel); +CPUFREQ_OVERRIDE_ATTR(battery_scaleback_percent, battery_scaleback_per); +CPUFREQ_OVERRIDE_ATTR(battery_scaleback_speed, battery_scaleback_speed); +CPUFREQ_OVERRIDE_ATTR2(vdd1_vsel_min, show_vdd1_vsel_min); +CPUFREQ_OVERRIDE_ATTR2(vdd1_vsel_max, show_vdd1_vsel_max); +CPUFREQ_OVERRIDE_ATTR2(vdd2_vsel_min, show_vdd2_vsel_min); +CPUFREQ_OVERRIDE_ATTR2(vdd2_vsel_max, show_vdd2_vsel_max); +CPUFREQ_OVERRIDE_ATTR2(vdd2_freq, show_vdd2_freqs); +CPUFREQ_OVERRIDE_ATTR(cpu_hightemp_alarm, hightemp_scaleback); +CPUFREQ_OVERRIDE_ATTR(cpu_hightemp_reset, lowtemp_reset); +CPUFREQ_OVERRIDE_ATTR(cpu_hightemp_scaleback_speed, temp_scaleback); + +static struct attribute *default_attrs[] = { + &_attr_vdd1_vsel.attr, + &_attr_vdd1_vsel_min.attr, + &_attr_vdd1_vsel_max.attr, + &_attr_vdd2_vsel.attr, + &_attr_vdd2_vsel_min.attr, + &_attr_vdd2_vsel_max.attr, + &_attr_vdd2_freq.attr, + &_attr_cpu_hightemp_alarm.attr, + &_attr_cpu_hightemp_reset.attr, + &_attr_cpu_hightemp_scaleback_speed.attr, + &_attr_battery_scaleback_percent.attr, + &_attr_battery_scaleback_speed.attr, + NULL +}; + +static struct attribute_group override_attr_group = { + .attrs = default_attrs, + .name = "override" +}; + +int cpufreq_override_driver_init(void) +{ + struct cpufreq_policy *data = cpufreq_cpu_get(0); + schedule_delayed_work(&worker, secs_to_jiffies(BATTERY_POLLING)); + schedule_delayed_work(&worker2, secs_to_jiffies(TEMP_POLLING)); + printk("override: initialized!\n"); + return sysfs_create_group(&data->kobj, &override_attr_group); +} + +EXPORT_SYMBOL(cpufreq_override_driver_init); + +void cpufreq_override_driver_exit(void) +{ + struct cpufreq_policy *policy = cpufreq_cpu_get(0); + cancel_delayed_work(&worker); + cancel_delayed_work(&worker2); + sysfs_remove_group(&policy->kobj, &override_attr_group); +} + +EXPORT_SYMBOL(cpufreq_override_driver_exit); + +MODULE_AUTHOR("marco@unixpsycho.com"); +MODULE_DESCRIPTION("'cpufreq_override' - A driver to do cool stuff "); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/cpufreq_screenstate.c b/drivers/cpufreq/cpufreq_screenstate.c new file mode 100644 index 000000000..2e787e8db --- /dev/null +++ b/drivers/cpufreq/cpufreq_screenstate.c @@ -0,0 +1,637 @@ +/* + * linux/drivers/cpufreq/cpufreq_screenstate.c + * + * Marco Benton marco@unixpsycho.com + * + * screenstate v3 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FACTOR_MIN 1 +#define FACTOR_MAX 4 + +// charger poll is in secs converted to jiffies +#define CHPOLLMIN 1 +#define CHPOLLMAX 300 + +// vDemand poll is in msecs converted to jiffies +#define VDPOLLMIN 10 +#define VDPOLLMAX 1500 + +// default charger poll secs +#define CHARGER_POLL 3 + +// default vdemand poll msecs +#define VDEMAND_POLL 100 + +// default ondemand_enabled poll msecs +#define ONDEMAND_POLL 50 + +// default factor +#define VDEMAND_FACTOR 2 + +// screen on and off delay in secs +#define SON_DELAY 1 +#define SOFF_DELAY 1 + +#ifdef CONFIG_MACH_SIRLOIN_3630 +#define SCALEBACK_SPEED 800000 +#else +#define SCALEBACK_SPEED 500000 +#endif + +struct ss_params { + bool cpu_is_managed; + bool lcd_state; + bool vdemand_enabled; + bool charging_state; + bool ch_override; + bool ondemand_enabled; + unsigned int ch_poll; + unsigned int vdemand_poll; + unsigned int ondemand_poll; + unsigned short sfactor; +} sscfg = {0, 1, 1, 0, 0, 0, 0, 0, 0, VDEMAND_FACTOR}; + +static struct ss_params *ss_cfg = &sscfg; + +static unsigned int + opp, last_load; + +static cputime64_t + prev_cpu_wall = 0, prev_cpu_idle = 0; + +int gadget_event_state_current(void), + ds2784_getcurrent(int *ret_current), + set_voltage_level(u8 vdd, u8 vsel); + +unsigned int + prcm_get_current_vdd1_opp_no(void); + +void omap_pm_opp_get_vdd2_volts(u8 * vdd2_volt), + omap_pm_opp_get_volts(u8 vdd1_volts[]); + +unsigned short + get_vdd1_arm_opp_for_freq(unsigned int freq); + +static inline void + check_charger(struct work_struct *work), + check_load(struct work_struct *work), + __cpufreq_gov_screenstate_lcdoff(struct work_struct *work), + __cpufreq_gov_screenstate_lcdon(struct work_struct *work); + +static DEFINE_MUTEX(screenstate_mutex); + +static DECLARE_DELAYED_WORK(worker, check_charger); +static DECLARE_DELAYED_WORK(worker2, check_load); +static DECLARE_DELAYED_WORK(worker3, __cpufreq_gov_screenstate_lcdoff); +static DECLARE_DELAYED_WORK(worker4, __cpufreq_gov_screenstate_lcdon); + +#define CPUFREQ_SCREENSTATE_ATTR(_name) \ +static struct freq_attr _attr_##_name = {\ + .attr = {.name = __stringify(_name), .mode = 0644, }, \ + .show = show_##_name,\ + .store = store_##_name,\ +}; + +static unsigned int jiffies_to_secs(unsigned long int jifs) +{ + return jifs / HZ; +} + +static unsigned long int secs_to_jiffies(unsigned int secs) +{ + return secs * HZ; +} + +static inline cputime64_t get_cpu_idle_time(unsigned int cpu) +{ + cputime64_t idle_time; + cputime64_t cur_jiffies; + cputime64_t busy_time; + + cur_jiffies = jiffies64_to_cputime64(get_jiffies_64()); + busy_time = cputime64_add(kstat_cpu(cpu).cpustat.user, + kstat_cpu(cpu).cpustat.system); + + busy_time = cputime64_add(busy_time, kstat_cpu(cpu).cpustat.irq); + busy_time = cputime64_add(busy_time, kstat_cpu(cpu).cpustat.softirq); + busy_time = cputime64_add(busy_time, kstat_cpu(cpu).cpustat.steal); + + idle_time = cputime64_sub(cur_jiffies, busy_time); + return idle_time; +} + +static void reset_voltage(void) +{ + u8 vdd2_volt, volt[PRCM_NO_VDD1_OPPS]; + + omap_pm_opp_get_volts(volt); + omap_pm_opp_get_vdd2_volts(&vdd2_volt); + set_voltage_level(1, volt[opp - 1]); + set_voltage_level(2, vdd2_volt); +} + +static int screenstate_notifier(struct notifier_block *nb, unsigned long val, + void *data) +{ + opp = prcm_get_current_vdd1_opp_no(); + + return 0; +} + +static struct notifier_block screenstate_notifier_block = { + .notifier_call = screenstate_notifier +}; + +static void change_freq(void) { + struct cpufreq_policy *policy = cpufreq_cpu_get(0); + if (ss_cfg->charging_state) + __cpufreq_driver_target(policy, SCALEBACK_SPEED, + CPUFREQ_RELATION_H); + else + __cpufreq_driver_target(policy, (ss_cfg->lcd_state) + ? policy->max + : policy->min, CPUFREQ_RELATION_H); +} + +static inline void check_charger(struct work_struct *work) +{ + int cur = 0, current_mA = 0; + + ds2784_getcurrent(&cur); + current_mA = gadget_event_state_current(); + if ((cur > 0) && ((current_mA != 100) && (current_mA < 500))) { + // Assume Touchstone + if (!ss_cfg->charging_state) { + ss_cfg->charging_state = 1; + change_freq(); + printk("screenstate: TS found!\n"); + } + } else { + if (current_mA == 1000) { + if (!ss_cfg->charging_state) { + ss_cfg->charging_state = 1; + change_freq(); + printk("screenstate: 1000mA charger found!\n"); + } + } else { + if (ss_cfg->charging_state) { + ss_cfg->charging_state = 0; + change_freq(); + printk("screenstate: charger unplugged!\n"); + } + } + } + schedule_delayed_work(&worker, ss_cfg->ch_poll); + return; +} + +static inline void check_load(struct work_struct *work) +{ + unsigned int tmp_idle_ticks, idle_ticks, total_ticks, load = 0; + cputime64_t total_idle_ticks, cur_jiffies; + struct cpufreq_policy *policy = cpufreq_cpu_get(0); + u8 vdd1_volt, vdd2_volt, volt[PRCM_NO_VDD1_OPPS]; + + if (!opp) + goto out; + + mutex_lock(&screenstate_mutex); + + idle_ticks = UINT_MAX; + cur_jiffies = jiffies64_to_cputime64(get_jiffies_64()); + total_ticks = (unsigned int)cputime64_sub(cur_jiffies, prev_cpu_wall); + prev_cpu_wall = get_jiffies_64(); + + if (!total_ticks) + goto out; + + total_idle_ticks = get_cpu_idle_time(0); + tmp_idle_ticks = (unsigned int)cputime64_sub(total_idle_ticks, + prev_cpu_idle); + prev_cpu_idle = total_idle_ticks; + + if (tmp_idle_ticks < idle_ticks) + idle_ticks = tmp_idle_ticks; + if (likely(total_ticks > idle_ticks)) + load = (100 * (total_ticks - idle_ticks)) / total_ticks; + + if (!last_load) + goto out; + + if (ss_cfg->lcd_state && ss_cfg->ondemand_enabled) + goto on_demand; + + if (!ss_cfg->vdemand_enabled) + goto out; + + omap_pm_opp_get_volts(volt); + omap_pm_opp_get_vdd2_volts(&vdd2_volt); + vdd1_volt = volt[opp - 1]; + + if ((load < 5) && (last_load < 5)) { + set_voltage_level(1, vdd1_volt - (2 * ss_cfg->sfactor) - 2); + set_voltage_level(2, vdd2_volt - (2 * ss_cfg->sfactor) - 2); + goto out; + } + + if ((load < 30) && (last_load > 29)) { + set_voltage_level(1, vdd1_volt - (2 * ss_cfg->sfactor)); + set_voltage_level(2, vdd2_volt - (2 * ss_cfg->sfactor)); + goto out; + } + if (((load > 29) && (load < 70)) && + ((last_load < 30) || (last_load > 69))) { + set_voltage_level(1, vdd1_volt - ss_cfg->sfactor); + set_voltage_level(2, vdd2_volt - ss_cfg->sfactor); + goto out; + } + if ((load > 69) && (last_load < 70)) { + set_voltage_level(1, vdd1_volt); + set_voltage_level(2, vdd2_volt); + goto out; + } + + goto out; + +on_demand: + + if (ss_cfg->charging_state) { + if (policy->cur != SCALEBACK_SPEED) change_freq(); + goto out; + } + + if (((load > 19) && (load < 70)) || ((load < 10) && (last_load > 49))) { + __cpufreq_driver_target(policy, (load > 39) + ? get_arm_freq_for_opp( + get_vdd1_arm_opp_for_freq(policy->max) - 1) + : get_arm_freq_for_opp( + get_vdd1_arm_opp_for_freq(policy->min) + 1), + CPUFREQ_RELATION_L); + goto out; + } + + if ((load < 20) && (last_load < 41)) { + if (policy->cur != policy->min) + __cpufreq_driver_target(policy, policy->min, + CPUFREQ_RELATION_L); + goto out; + } + if ((load > 9)) { + if (policy->cur != policy->max) + __cpufreq_driver_target(policy, policy->max, + CPUFREQ_RELATION_H); + goto out; + } + + out: + last_load = load; + + mutex_unlock(&screenstate_mutex); + schedule_delayed_work(&worker2, + (ss_cfg->lcd_state && ss_cfg->ondemand_enabled) + ? ss_cfg->ondemand_poll : ss_cfg->vdemand_poll); + + return; +} + +static ssize_t show_vdemand_factor(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%hu\n", ss_cfg->sfactor); +} + +static ssize_t store_vdemand_factor(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + u8 i; + + if (sscanf(buf, "%hhu", &i)) { + if ((i < FACTOR_MIN) || (i > FACTOR_MAX)) + printk("screenstate: invalid factor\n"); + else { + ss_cfg->sfactor = i; + } + } else + printk("screenstate: missing factor value\n"); + + return count; +} + +static ssize_t show_vdemand_enable(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%hu\n", ss_cfg->vdemand_enabled); +} + +static ssize_t store_vdemand_enable(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + u8 i; + + if (sscanf(buf, "%hhu", &i)) { + if ((i != 0) && (i != 1)) + printk("screenstate: invalid vdemand bool\n"); + else { + if ((!i) && (ss_cfg->vdemand_enabled)) { + if (!ss_cfg->ondemand_enabled) + cancel_delayed_work(&worker2); + reset_voltage(); + } + if ((i) && (!ss_cfg->vdemand_enabled)) + if (!ss_cfg->ondemand_enabled) + schedule_delayed_work(&worker2, + ss_cfg->vdemand_poll); + ss_cfg->vdemand_enabled = i; + } + } else + printk("screenstate: missing value\n"); + + return count; +} + +static ssize_t show_charger_override(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%hu\n", ss_cfg->ch_override); +} + +static ssize_t store_charger_override(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + u8 i; + + if (sscanf(buf, "%hhu", &i)) { + if ((i != 0) && (i != 1)) + printk("screenstate: invalid chrg bool\n"); + else { + if ((i) && (!ss_cfg->ch_override)) { + ss_cfg->charging_state = 0; + cancel_delayed_work(&worker); + } + if ((!i) && (ss_cfg->ch_override)) + schedule_delayed_work(&worker, ss_cfg->ch_poll); + ss_cfg->ch_override = i; + } + } else + printk("screenstate: missing value\n"); + + return count; +} + +static ssize_t show_charger_poll_rate(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%hu\n", jiffies_to_secs(ss_cfg->ch_poll)); +} + +static ssize_t store_charger_poll_rate(struct cpufreq_policy *policy, + const char *buf, + size_t count) +{ + unsigned int i; + + if (sscanf(buf, "%u", &i)) { + if ((i < CHPOLLMIN) || (i > CHPOLLMAX)) + printk("screenstate: invalid chrg poll value\n"); + else + ss_cfg->ch_poll = secs_to_jiffies(i);; + } else + printk("screenstate: missing value\n"); + + return count; +} + +static ssize_t show_vdemand_poll_rate(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%hu\n", jiffies_to_msecs(ss_cfg->vdemand_poll)); +} + +static ssize_t store_vdemand_poll_rate(struct cpufreq_policy *policy, + const char *buf, + size_t count) +{ + unsigned int i; + + if (sscanf(buf, "%u", &i)) { + if ((i < VDPOLLMIN) || (i > VDPOLLMAX)) + printk("screenstate: invalid poll time\n"); + else + ss_cfg->vdemand_poll = msecs_to_jiffies(i); + } else + printk("screenstate: missing value\n"); + + return count; +} + +static ssize_t show_ondemand_enable(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%hu\n", ss_cfg->ondemand_enabled); +} + +static ssize_t store_ondemand_enable(struct cpufreq_policy *policy, + const char *buf, + size_t count) +{ + u8 i; + + if (sscanf(buf, "%hhu", &i)) { + if ((i != 0) && (i != 1)) + printk("screenstate: invalid entry\n"); + else { + if ((!i) && (ss_cfg->ondemand_enabled)) + if(!ss_cfg->vdemand_enabled) + cancel_delayed_work(&worker2); + if ((i) && (!ss_cfg->ondemand_enabled)) + if(!ss_cfg->vdemand_enabled) + schedule_delayed_work(&worker2, + ss_cfg->ondemand_poll); + //if(i) reset_voltage(); + ss_cfg->ondemand_enabled = i; + printk("screenstate: toggle ondemand\n"); + } + } else + printk("screenstate: missing value\n"); + + return count; +} + +CPUFREQ_SCREENSTATE_ATTR(vdemand_factor); +CPUFREQ_SCREENSTATE_ATTR(vdemand_enable); +CPUFREQ_SCREENSTATE_ATTR(charger_override); +CPUFREQ_SCREENSTATE_ATTR(vdemand_poll_rate); +CPUFREQ_SCREENSTATE_ATTR(charger_poll_rate); +CPUFREQ_SCREENSTATE_ATTR(ondemand_enable); + +static struct attribute *default_attrs[] = { + &_attr_vdemand_factor.attr, + &_attr_vdemand_enable.attr, + &_attr_charger_override.attr, + &_attr_charger_poll_rate.attr, + &_attr_vdemand_poll_rate.attr, + &_attr_ondemand_enable.attr, + NULL +}; + +static struct attribute_group screenstate_attr_group = { + .attrs = default_attrs, + .name = "screenstate-v3", +}; + +static int cpufreq_governor_screenstate(struct cpufreq_policy *policy, + unsigned int event) +{ + int rc; + + switch (event) { + case CPUFREQ_GOV_START: + if (ss_cfg->cpu_is_managed) + break; + + mutex_lock(&screenstate_mutex); + ss_cfg->cpu_is_managed = 1; + ss_cfg->lcd_state = 1; + ss_cfg->charging_state = 0; + ss_cfg->vdemand_enabled = 1; + ss_cfg->ch_override = 0; + ss_cfg->ondemand_enabled = 0; + ss_cfg->sfactor = VDEMAND_FACTOR; + ss_cfg->vdemand_poll = msecs_to_jiffies(VDEMAND_POLL); + ss_cfg->ondemand_poll = msecs_to_jiffies(ONDEMAND_POLL); + ss_cfg->ch_poll = secs_to_jiffies(CHARGER_POLL); + + last_load = 0; + prev_cpu_idle = get_cpu_idle_time(0); + prev_cpu_wall = get_jiffies_64(); + opp = prcm_get_current_vdd1_opp_no(); + cpufreq_register_notifier(&screenstate_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + change_freq(); + + schedule_delayed_work(&worker, ss_cfg->ch_poll); + schedule_delayed_work(&worker2, ss_cfg->vdemand_poll); + + rc = sysfs_create_group(&policy->kobj, &screenstate_attr_group); + if (rc) { + mutex_unlock(&screenstate_mutex); + return rc; + } + + mutex_unlock(&screenstate_mutex); + printk("screenstate: initialized\n"); + break; + case CPUFREQ_GOV_STOP: + mutex_lock(&screenstate_mutex); + ss_cfg->cpu_is_managed = 0; + ss_cfg->lcd_state = 0; + cancel_delayed_work(&worker); + cancel_delayed_work(&worker2); + reset_voltage(); + cpufreq_unregister_notifier(&screenstate_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + sysfs_remove_group(&policy->kobj, &screenstate_attr_group); + mutex_unlock(&screenstate_mutex); + + break; + case CPUFREQ_GOV_LIMITS: + mutex_lock(&screenstate_mutex); + printk("screenstate: policy change\n"); + change_freq(); + mutex_unlock(&screenstate_mutex); + + break; + } + return 0; +} + +struct cpufreq_governor cpufreq_gov_screenstate = { + .name = "screenstate-v3", + .governor = cpufreq_governor_screenstate, + .owner = THIS_MODULE, +}; + +static int __init cpufreq_gov_screenstate_init(void) +{ + return cpufreq_register_governor(&cpufreq_gov_screenstate); +} + +static void __exit cpufreq_gov_screenstate_exit(void) +{ + cpufreq_unregister_governor(&cpufreq_gov_screenstate); +} + +void cpufreq_gov_screenstate_lcdoff(void) +{ + if (ss_cfg->cpu_is_managed) + schedule_delayed_work(&worker3, secs_to_jiffies(SON_DELAY)); +} + +EXPORT_SYMBOL(cpufreq_gov_screenstate_lcdoff); + +static inline void __cpufreq_gov_screenstate_lcdoff(struct work_struct *work) +{ + printk("screenstate: lcd off\n"); + ss_cfg->lcd_state = 0; + change_freq(); +} + +void cpufreq_gov_screenstate_lcdon(void) +{ + if (ss_cfg->cpu_is_managed) + schedule_delayed_work(&worker4, secs_to_jiffies(SOFF_DELAY)); +} + +EXPORT_SYMBOL(cpufreq_gov_screenstate_lcdon); + +static inline void __cpufreq_gov_screenstate_lcdon(struct work_struct *work) +{ + printk("screenstate: lcd on\n"); + ss_cfg->lcd_state = 1; + change_freq(); +} + +unsigned short cpufreq_screenstate_lcd_state(void) +{ + if (ss_cfg->cpu_is_managed) + return ss_cfg->lcd_state; + else + return 0; +} + +EXPORT_SYMBOL(cpufreq_screenstate_lcd_state); + +EXPORT_SYMBOL(cpufreq_gov_screenstate); +MODULE_AUTHOR("marco@unixpsycho.com"); +MODULE_DESCRIPTION("CPUfreq policy governor 'screenstate-v3'"); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_SCREENSTATE +fs_initcall(cpufreq_gov_screenstate_init); +#else +module_init(cpufreq_gov_screenstate_init); +#endif +module_exit(cpufreq_gov_screenstate_exit); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a0445bea9..2da07711d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -754,6 +754,10 @@ config SENSORS_APPLESMC Say Y here if you have an applicable laptop and want to experience the awesome power of applesmc. +config SENSORS_OMAP34XX + tristate "TI OMAP34xx internal temperature sensor" + depends on ARCH_OMAP3 && HIGH_RES_TIMERS + config HWMON_DEBUG_CHIP bool "Hardware Monitoring Chip debugging messages" default n diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 55595f6e1..2f7425a8d 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_SENSORS_VT1211) += vt1211.o obj-$(CONFIG_SENSORS_VT8231) += vt8231.o obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o +obj-$(CONFIG_SENSORS_OMAP34XX) += omap34xx_temp.o ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/hwmon/omap34xx_temp.c b/drivers/hwmon/omap34xx_temp.c new file mode 100644 index 000000000..56c6f1b36 --- /dev/null +++ b/drivers/hwmon/omap34xx_temp.c @@ -0,0 +1,293 @@ +/* + * omap34xx_temp.c - Linux kernel module for OMAP34xx hardware monitoring + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Peter De Schrijver + * + * Inspired by k8temp.c + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_MACH_SIRLOIN_3630 +#define TEMP_SENSOR_SOC BIT(9) +#define TEMP_SENSOR_EOCZ BIT(8) +#else +#define TEMP_SENSOR_SOC BIT(8) +#define TEMP_SENSOR_EOCZ BIT(7) +#endif + +/* minimum delay for EOCZ rise after SOC rise is + * 11 cycles of the 32.768Khz clock */ +#define EOCZ_MIN_RISING_DELAY (11 * 30518) + +/* maximum delay for EOCZ rise after SOC rise is + * 14 cycles of the 32.768Khz clock + * changed to 30 to allow for clock stabilization */ +#define EOCZ_MAX_RISING_DELAY (30 * 30518) + +/* minimum delay for EOCZ falling is + * 36 cycles of the 32.768Khz clock */ +#define EOCZ_MIN_FALLING_DELAY (36 * 30518) + +/* maximum delay for EOCZ falling is + * 40 cycles of the 32.768Khz clock */ +#define EOCZ_MAX_FALLING_DELAY (40 * 30518) + +struct omap34xx_data { + struct device *hwmon_dev; + struct clk *clk_32k; + struct mutex update_lock; + const char *name; + char valid; + unsigned long last_updated; + u32 temp; +}; + +static struct platform_device omap34xx_temp_device = { + .name = "omap34xx_temp", + .id = -1, +}; + +static int adc_to_temp[] = { + -40, -40, -40, -40, -40, -39, -38, -36, -34, -32, -31, -29, -28, -26, + -25, -24, -22, -21, -19, -18, -17, -15, -14, -12, -11, -9, -8, -7, -5, + -4, -2, -1, 0, 1, 3, 4, 5, 7, 8, 10, 11, 13, 14, 15, 17, 18, 20, 21, + 22, 24, 25, 27, 28, 30, 31, 32, 34, 35, 37, 38, 39, 41, 42, 44, 45, + 47, 48, 49, 51, 52, 53, 55, 56, 58, 59, 60, 62, 63, 65, 66, 67, 69, + 70, 72, 73, 74, 76, 77, 79, 80, 81, 83, 84, 85, 87, 88, 89, 91, 92, + 94, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 111, 113, + 114, 116, 117, 118, 120, 121, 122, 124, 124, 125, 125, 125, 125, 125}; + +static inline u32 wait_for_eocz(int min_delay, int max_delay, u32 level) +{ + struct timespec timeout; + ktime_t expire; + u32 temp_sensor_reg; + + level &= 1; + level *= TEMP_SENSOR_EOCZ; + + expire = ktime_add_ns(ktime_get(), max_delay); + timeout = ns_to_timespec(min_delay); + hrtimer_nanosleep(&timeout, NULL, HRTIMER_MODE_REL, CLOCK_MONOTONIC); + do { + temp_sensor_reg = omap_ctrl_readl(OMAP343X_CONTROL_TEMP_SENSOR); + if ((temp_sensor_reg & TEMP_SENSOR_EOCZ) == level) + break; + } while (ktime_us_delta(expire, ktime_get()) > 0); + + return (temp_sensor_reg & TEMP_SENSOR_EOCZ) == level; +} + +static void omap34xx_update(struct omap34xx_data *data) +{ + u32 temp_sensor_reg; + + mutex_lock(&data->update_lock); + omap_ctrl_writel(0, OMAP343X_CONTROL_TEMP_SENSOR); + + + if (!data->valid || time_after(jiffies, data->last_updated + (HZ/4))) { + int meh; + clk_enable(data->clk_32k); + omap_ctrl_writel(0, OMAP343X_CONTROL_TEMP_SENSOR); +#ifdef CONFIG_MACH_SIRLOIN_3630 + temp_sensor_reg = 0x200; +#else + temp_sensor_reg = 0x100; +#endif + __raw_writel(temp_sensor_reg, 0xd8002524); + + if (!wait_for_eocz(EOCZ_MIN_RISING_DELAY, EOCZ_MAX_RISING_DELAY, 1)) + { + __raw_writel(0, 0xd8002524); + data->valid = 0; + goto err; + } + + __raw_writel(0, 0xd8002524); + + if (!wait_for_eocz(EOCZ_MIN_FALLING_DELAY, EOCZ_MAX_FALLING_DELAY, 0)) + { + data->valid = 0; + goto err; + } + data->temp = omap_ctrl_readl(OMAP343X_CONTROL_TEMP_SENSOR) & +#ifdef CONFIG_MACH_SIRLOIN_3630 + ((1<<8) - 1); +#else + ((1<<7) - 1); +#endif + data->last_updated = jiffies; + data->valid = 1; + +err: + //clk_disable(data->clk_32k); //caused adc to hang after first read durring early testing, might be able to re-enable with no ill effects. + meh = 1; //needed to appease the compiler, original statement above + } + + mutex_unlock(&data->update_lock); +} + +int omap34xx_get_temp(void) { + struct omap34xx_data *data = + dev_get_drvdata(&omap34xx_temp_device.dev); + omap34xx_update(data); + return adc_to_temp[data->temp]; +} +EXPORT_SYMBOL(omap34xx_get_temp); + +static ssize_t show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct omap34xx_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + +static ssize_t show_temp_raw(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct omap34xx_data *data = dev_get_drvdata(dev); + + omap34xx_update(data); + + return sprintf(buf, "%d\n", data->temp); +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct omap34xx_data *data = dev_get_drvdata(dev); + + omap34xx_update(data); + + return sprintf(buf, "%d\n", adc_to_temp[data->temp]); +} + +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp1_input_raw, S_IRUGO, show_temp_raw, + NULL, 0, 0); +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static int __devinit omap34xx_temp_probe(void) +{ + int err; + struct omap34xx_data *data; + + err = platform_device_register(&omap34xx_temp_device); + if (err) { + printk(KERN_ERR + "Unable to register omap34xx temperature device\n"); + goto exit; + } + + data = kzalloc(sizeof(struct omap34xx_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit_platform; + } + + dev_set_drvdata(&omap34xx_temp_device.dev, data); + mutex_init(&data->update_lock); + data->name = "omap34xx_temp"; + + data->clk_32k = clk_get(&omap34xx_temp_device.dev, "ts_fck"); + if (IS_ERR(data->clk_32k)) { + err = PTR_ERR(data->clk_32k); + goto exit_free; + } + + err = device_create_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input.dev_attr); + if (err) + goto clock_free; + + err = device_create_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input_raw.dev_attr); + if (err) + goto exit_remove; + + err = device_create_file(&omap34xx_temp_device.dev, &dev_attr_name); + if (err) + goto exit_remove_raw; + + data->hwmon_dev = hwmon_device_register(&omap34xx_temp_device.dev); + + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_all; + } + + return 0; + +exit_remove_all: + device_remove_file(&omap34xx_temp_device.dev, + &dev_attr_name); +exit_remove_raw: + device_remove_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input_raw.dev_attr); +exit_remove: + device_remove_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input.dev_attr); +clock_free: + clk_put(data->clk_32k); + +exit_free: + kfree(data); +exit_platform: + platform_device_unregister(&omap34xx_temp_device); +exit: + return err; +} + +static int __init omap34xx_temp_init(void) +{ + return omap34xx_temp_probe(); +} + +static void __exit omap34xx_temp_exit(void) +{ + struct omap34xx_data *data = + dev_get_drvdata(&omap34xx_temp_device.dev); + + clk_put(data->clk_32k); + hwmon_device_unregister(data->hwmon_dev); + device_remove_file(&omap34xx_temp_device.dev, + &sensor_dev_attr_temp1_input.dev_attr); + device_remove_file(&omap34xx_temp_device.dev, &dev_attr_name); + kfree(data); + platform_device_unregister(&omap34xx_temp_device); +} + +MODULE_AUTHOR("Peter De Schrijver"); +MODULE_DESCRIPTION("Omap34xx temperature sensor"); +MODULE_LICENSE("GPL"); + +module_init(omap34xx_temp_init) +module_exit(omap34xx_temp_exit) + diff --git a/drivers/misc/lowmemnotify.c b/drivers/misc/lowmemnotify.c index 217f33111..42892abc0 100644 --- a/drivers/misc/lowmemnotify.c +++ b/drivers/misc/lowmemnotify.c @@ -194,7 +194,7 @@ unsigned long memnotify_get_free(void) if (other_free > totalreserve_pages) free += other_free - totalreserve_pages; - return free; + return free + nr_swap_pages; } EXPORT_SYMBOL(memnotify_get_free); @@ -206,15 +206,13 @@ EXPORT_SYMBOL(memnotify_get_free); unsigned long memnotify_get_used(void) { unsigned long used_mem; - unsigned long used_swap; unsigned long free_mem; free_mem = memnotify_get_free(); - used_swap = total_swap_pages - nr_swap_pages; - used_mem = totalram_pages - free_mem; + used_mem = totalram_pages + total_swap_pages - free_mem; - return used_mem + used_swap; + return used_mem; } /** @@ -231,7 +229,7 @@ int memnotify_threshold(void) int i; used = memnotify_get_used(); - used_ratio = used * 100 / totalram_pages; + used_ratio = used * 100 / (totalram_pages + total_swap_pages); threshold = THRESHOLD_NORMAL; last_threshold = atomic_read(&memnotify_last_threshold); @@ -351,7 +349,7 @@ meminfo_show(struct class *class, char *buf) int i; used = memnotify_get_used(); - total_mem = totalram_pages; + total_mem = totalram_pages + total_swap_pages; threshold = memnotify_threshold(); last_threshold = atomic_read(&memnotify_last_threshold); @@ -362,7 +360,7 @@ meminfo_show(struct class *class, char *buf) "Used (Mem+Swap): %ldMB\n", MB(used)); len += snprintf(buf+len, PAGE_SIZE, - "Used (Mem): %ldMB\n", MB(totalram_pages-memnotify_get_free())); + "Used (Mem): %ldMB\n", MB(totalram_pages + nr_swap_pages - memnotify_get_free())); len += snprintf(buf+len, PAGE_SIZE, "Used (Swap): %ldMB\n", MB(total_swap_pages - nr_swap_pages)); diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 70af45f20..b1f10e682 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -1986,11 +1986,24 @@ static int check_command(struct fsg_dev *fsg, int cmnd_size, /* Verify the length of the command itself */ if (cmnd_size != fsg->cmnd_size) { - /* Special case workaround: MS-Windows issues REQUEST SENSE - * with cbw->Length == 12 (it should be 6). */ - if (fsg->cmnd[0] == SC_REQUEST_SENSE && fsg->cmnd_size == 12) + /* Special case workaround: There are plenty of buggy SCSI + * implementations. Many have issues with cbw->Length + * field passing a wrong command size. For those cases we + * always try to work around the problem by using the length + * sent by the host side provided it is at least as large + * as the correct command length. + * Examples of such cases would be MS-Windows, which issues + * REQUEST SENSE with cbw->Length == 12 where it should + * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and + * REQUEST SENSE with cbw->Length == 10 where it should + * be 6 as well. + */ + if (cmnd_size <= fsg->cmnd_size) { + DBG(fsg, "%s is buggy! Expected length %d " + "but we got %d\n", name, + cmnd_size, fsg->cmnd_size); cmnd_size = fsg->cmnd_size; - else { + } else { fsg->phase_error = 1; return -EINVAL; } diff --git a/drivers/usb/gadget/gadget_event.c b/drivers/usb/gadget/gadget_event.c index d2f20000a..7b20d6a2d 100644 --- a/drivers/usb/gadget/gadget_event.c +++ b/drivers/usb/gadget/gadget_event.c @@ -362,6 +362,13 @@ gadget_event_power_state_changed(enum gadget_event_source_type source, } EXPORT_SYMBOL(gadget_event_power_state_changed); +#ifdef CONFIG_CPU_FREQ_GOV_SCREENSTATE +int gadget_event_state_current(void) { + return the_state.current_mA; +} +EXPORT_SYMBOL(gadget_event_state_current); +#endif + static int __init init(void) { int ret = 0; diff --git a/drivers/video/omap/lcd_panel.c b/drivers/video/omap/lcd_panel.c index 400bb42f5..c1d8d2e71 100644 --- a/drivers/video/omap/lcd_panel.c +++ b/drivers/video/omap/lcd_panel.c @@ -31,6 +31,11 @@ #include "lcd.h" +#ifdef CONFIG_CPU_FREQ_GOV_SCREENSTATE +void cpufreq_gov_screenstate_lcdon(void); +void cpufreq_gov_screenstate_lcdoff(void); +#endif + #define MOD_NAME "LCD: " #undef MODDEBUG @@ -43,6 +48,9 @@ #define DPRINTK(format,...) #endif +bool omap_fb_state = 1; +EXPORT_SYMBOL(omap_fb_state); + #define DISPLAY_DEVICE_STATE_ON 1 #define DISPLAY_DEVICE_STATE_OFF 0 #define DISPLAY_PANEL_STATE_ON 1 @@ -52,7 +60,6 @@ #define DISPLAY_BACKLIGHT_STATE_ON 1 #define DISPLAY_BACKLIGHT_STATE_OFF 0 - struct lcd_params { struct display_device *disp_dev; struct platform_device *pdev; @@ -112,6 +119,7 @@ static void panel_set_state(struct lcd_params *params, unsigned int state) params->ctrl_ops->ctrl_set_state) { params->ctrl_ops->ctrl_set_state(params->ctrl_dev, DISPLAY_CONTROLLER_STATE_ON); + omap_fb_state = 1; } /* Panel ON */ @@ -126,8 +134,12 @@ static void panel_set_state(struct lcd_params *params, unsigned int state) params->bl_ops->bl_set_state) { params->bl_ops->bl_set_state(params->bl_dev, DISPLAY_BACKLIGHT_STATE_ON); +#ifdef CONFIG_CPU_FREQ_GOV_SCREENSTATE + cpufreq_gov_screenstate_lcdon(); +#endif } params->panel_state = DISPLAY_DEVICE_STATE_ON; + } else { if (params->panel_state == DISPLAY_DEVICE_STATE_OFF) { DPRINTK(" %s: Panel already off, returning...\n", @@ -140,6 +152,7 @@ static void panel_set_state(struct lcd_params *params, unsigned int state) params->bl_ops->bl_set_state) { params->bl_ops->bl_set_state(params->bl_dev, DISPLAY_BACKLIGHT_STATE_OFF); + omap_fb_state = 0; } /* Panel OFF */ @@ -154,6 +167,9 @@ static void panel_set_state(struct lcd_params *params, unsigned int state) params->ctrl_ops->ctrl_set_state) { params->ctrl_ops->ctrl_set_state(params->ctrl_dev, DISPLAY_CONTROLLER_STATE_OFF); +#ifdef CONFIG_CPU_FREQ_GOV_SCREENSTATE + cpufreq_gov_screenstate_lcdoff(); +#endif } params->panel_state = DISPLAY_DEVICE_STATE_OFF; } diff --git a/drivers/w1/slaves/w1_ds2784.c b/drivers/w1/slaves/w1_ds2784.c index a7a8bd1be..cd75b3944 100644 --- a/drivers/w1/slaves/w1_ds2784.c +++ b/drivers/w1/slaves/w1_ds2784.c @@ -1133,11 +1133,20 @@ static DEVICE_ATTR(mac, S_IRUGO|S_IWUSR, show_ds2784_mac, store_ds2784_mac); */ static struct device *battery_device = NULL; +#ifdef CONFIG_CPU_FREQ_OVERRIDE +int ds2784_getpercent(int *ret_percent) +{ + if (!battery_device) return -1; + return ds2784_getpercent_dev(battery_device, ret_percent); +} +EXPORT_SYMBOL(ds2784_getpercent); +#else static int ds2784_getpercent(int *ret_percent) { if (!battery_device) return -1; return ds2784_getpercent_dev(battery_device, ret_percent); } +#endif static int ds2784_getvoltage(int *ret_voltage) { @@ -1151,11 +1160,20 @@ static int ds2784_gettemperature(int *ret_temperature) return ds2784_gettemperature_dev(battery_device, ret_temperature); } +#ifdef CONFIG_CPU_FREQ_GOV_SCREENSTATE +int ds2784_getcurrent(int *ret_current) +{ + if (!battery_device) return -1; + return ds2784_getcurrent_dev(battery_device, ret_current); +} +EXPORT_SYMBOL(ds2784_getcurrent); +#else static int ds2784_getcurrent(int *ret_current) { if (!battery_device) return -1; return ds2784_getcurrent_dev(battery_device, ret_current); } +#endif static struct battery_ops ds2784_battery_ops = { .get_percent = ds2784_getpercent, diff --git a/extra/Kconfig b/extra/Kconfig new file mode 100644 index 000000000..ccb2f9160 --- /dev/null +++ b/extra/Kconfig @@ -0,0 +1,15 @@ +menuconfig COMPCACHE_DEV + bool "Compcache drivers" + select LZO_COMPRESS + select LZO_DECOMPRESS + default y + +if COMPCACHE_DEV +config COMPCACHE + tristate "Page cache compression support" + default m +config XVMALLOC + tristate "xvmalloc" + default m + +endif diff --git a/extra/Makefile b/extra/Makefile new file mode 100644 index 000000000..468b566b9 --- /dev/null +++ b/extra/Makefile @@ -0,0 +1,5 @@ +EXTRA_CFLAGS := -DCONFIG_BLK_DEV_RAMZSWAP_STATS + +obj-$(CONFIG_COMPCACHE) += ramzswap.o +obj-$(CONFIG_XVMALLOC) += xvmalloc.o + diff --git a/extra/compat.h b/extra/compat.h new file mode 100644 index 000000000..5cf4f3d46 --- /dev/null +++ b/extra/compat.h @@ -0,0 +1,38 @@ +#ifndef _RAMZSWAP_COMPAT_H_ +#define _RAMZSWAP_COMPAT_H_ + +#include + +/* Uncomment this if you are using swap free notify patch */ +#define CONFIG_SWAP_NOTIFIERS + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,23) +#define BIO_IO_ERROR(bio) bio_io_error(bio, PAGE_SIZE) +#define BIO_ENDIO(bio, error) bio_endio(bio, PAGE_SIZE, error) +#else +#define BIO_IO_ERROR(bio) bio_io_error(bio) +#define BIO_ENDIO(bio, error) bio_endio(bio, error) +#endif + +#ifndef pr_err +#define pr_err(fmt, arg...) \ + printk(KERN_ERR fmt, ##arg) +#endif + +#ifndef pr_warning +#define pr_warning(fmt, arg...) \ + printk(KERN_WARNING fmt, ##arg) +#endif + +#ifndef pr_info +#define pr_info(fmt, arg...) \ + printk(KERN_ERR fmt, ##arg) +#endif + +#endif + +#ifndef _XVMALLOC_COMPAT_H_ +#define _XVMALLOC_COMPAT_H_ + +#endif + diff --git a/extra/compcache b/extra/compcache new file mode 100644 index 000000000..91f8e64ef --- /dev/null +++ b/extra/compcache @@ -0,0 +1,12 @@ +# -*- mode: shell-script; -*- +start on stopped finish +stop on runlevel [!2] +console none +script + insmod /media/crypofs/apps/com.unixpsycho.kernel/xvmalloc.ko + insmod /media/crypofs/apps/com.unixpsycho.kernel/ramzswap.ko backing_swap=/dev/mapper/store-swap + sleep 3 + swapoff -a + swapon /dev/ramzswap0 -p 1 +end script + diff --git a/extra/ramzswap.c b/extra/ramzswap.c new file mode 100644 index 000000000..58851366f --- /dev/null +++ b/extra/ramzswap.c @@ -0,0 +1,1135 @@ +/* + * Compressed RAM based swap device + * + * Copyright (C) 2008, 2009 Nitin Gupta + * + * This RAM based block device acts as swap disk. + * Pages swapped to this device are compressed and + * stored in memory. + * + * Released under the terms of GNU General Public License Version 2.0 + * + * Project home: http://compcache.googlecode.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include "ramzswap.h" + +/* Globals */ +static struct ramzswap rzs; +static struct ramzswap_stats stats; +/* + * Pages that compress to larger than this size are + * forwarded to backing swap, if present or stored + * uncompressed in memory otherwise. + */ +static unsigned int MAX_CPAGE_SIZE; + +/* Module params (documentation at end) */ +static unsigned long disksize_kb; +static unsigned long memlimit_kb; +static char *backing_swap; + +static int __init ramzswap_init(void); +static struct block_device_operations ramzswap_devops = { + .owner = THIS_MODULE, +}; + +static int test_flag(u32 index, enum rzs_pageflags flag) +{ + return rzs.table[index].flags & BIT(flag); +} + +static void set_flag(u32 index, enum rzs_pageflags flag) +{ + rzs.table[index].flags |= BIT(flag); +} + +static void clear_flag(u32 index, enum rzs_pageflags flag) +{ + rzs.table[index].flags &= ~BIT(flag); +} + +static int page_zero_filled(void *ptr) +{ + u32 pos; + u64 *page; + + page = (u64 *)ptr; + + for (pos = 0; pos != PAGE_SIZE / sizeof(*page); pos++) { + if (page[pos]) + return 0; + } + + return 1; +} + +/* + * Given pair, provide a dereferencable pointer. + */ +static void *get_ptr_atomic(u32 pagenum, u16 offset, enum km_type type) +{ + unsigned char *page; + + page = kmap_atomic(pfn_to_page(pagenum), type); + return page + offset; +} + +static void put_ptr_atomic(void *ptr, enum km_type type) +{ + kunmap_atomic(ptr, type); +} + +static void ramzswap_flush_dcache_page(struct page *page) +{ +#ifdef CONFIG_ARM + int flag = 0; + /* + * Ugly hack to get flush_dcache_page() work on ARM. + * page_mapping(page) == NULL after clearing this swap cache flag. + * Without clearing this flag, flush_dcache_page() will simply set + * "PG_dcache_dirty" bit and return. + */ + if (PageSwapCache(page)) { + flag = 1; + ClearPageSwapCache(page); + } +#endif + flush_dcache_page(page); +#ifdef CONFIG_ARM + if (flag) + SetPageSwapCache(page); +#endif +} + +#if defined(STATS) +static struct proc_dir_entry *proc; + +static int proc_ramzswap_read(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len; + size_t succ_writes, mem_used; + unsigned int good_compress_perc = 0, no_compress_perc = 0; + + mem_used = xv_get_total_size_bytes(rzs.mem_pool) + + (stats.pages_expand << PAGE_SHIFT); + + if (off > 0) { + *eof = 1; + return 0; + } + +#define K(x) ((x) >> 10) + /* Basic stats */ + len = sprintf(page, + "DiskSize: %8zu kB\n", + (size_t)(K(rzs.disksize))); + + if (rzs.backing_swap) { + /* This must always be less than ComprDataSize */ + len += sprintf(page + len, + "MemLimit: %8zu kB\n", + K(rzs.memlimit)); + } + + succ_writes = stats.num_writes - stats.failed_writes; + + if (succ_writes && stats.pages_stored) { + good_compress_perc = stats.good_compress * 100 + / stats.pages_stored; + no_compress_perc = stats.pages_expand * 100 + / stats.pages_stored; + } + + /* Extended stats */ + len += sprintf(page + len, + "NumReads: %8llu\n" + "NumWrites: %8llu\n" + "FailedReads: %8llu\n" + "FailedWrites: %8llu\n" + "InvalidIO: %8llu\n" + "NotifyFree: %8llu\n" + "ZeroPages: %8u\n" + "GoodCompress: %8u %%\n" + "NoCompress: %8u %%\n" + "PagesStored: %8u\n" + "PagesUsed: %8zu\n" + "OrigDataSize: %8zu kB\n" + "ComprDataSize: %8zu kB\n" + "MemUsedTotal: %8zu kB\n", + stats.num_reads, + stats.num_writes, + stats.failed_reads, + stats.failed_writes, + stats.invalid_io, + stats.notify_free, + stats.pages_zero, + good_compress_perc, + no_compress_perc, + stats.pages_stored, + mem_used >> PAGE_SHIFT, + (size_t)(K(stats.pages_stored << PAGE_SHIFT)), + (size_t)(K(stats.compr_size)), + (size_t)(K(mem_used))); + + if (rzs.backing_swap) { + /* This must always be less than ComprDataSize */ + len += sprintf(page + len, + "BDevNumReads: %8llu\n" + "BDevNumWrites: %8llu\n", + stats.bdev_num_reads, + stats.bdev_num_writes); + } + + return len; +} +#endif /* STATS */ + +/* + * Check if value of backing_swap module param is sane. + * Claim this device and set ramzswap size equal to + * size of this block device. + */ +static int setup_backing_swap(void) +{ + int error = 0; + struct inode *inode; + struct file *swap_file; + struct address_space *mapping; + struct block_device *bdev = NULL; + + if (backing_swap == NULL) { + pr_debug(C "backing_swap param not given\n"); + goto out; + } + + pr_info(C "Using backing swap device: %s\n", backing_swap); + + swap_file = filp_open(backing_swap, O_RDWR | O_LARGEFILE, 0); + if (IS_ERR(swap_file)) { + pr_err(C "Error opening backing device: %s\n", backing_swap); + error = -EINVAL; + goto out; + } + + mapping = swap_file->f_mapping; + inode = mapping->host; + + if (S_ISBLK(inode->i_mode)) { + bdev = I_BDEV(inode); + error = bd_claim(bdev, ramzswap_init); + if (error < 0) { + bdev = NULL; + goto bad_param; + } + rzs.old_block_size = block_size(bdev); + error = set_blocksize(bdev, PAGE_SIZE); + if (error < 0) + goto bad_param; + } else { + /* TODO: support for regular file as backing swap */ + pr_info(C "%s is not a block device.\n", backing_swap); + error = -EINVAL; + goto out; + } + + rzs.swap_file = swap_file; + rzs.backing_swap = bdev; + rzs.disksize = i_size_read(inode); + BUG_ON(!rzs.disksize); + + return 0; + +bad_param: + if (bdev) { + set_blocksize(bdev, rzs.old_block_size); + bd_release(bdev); + } + filp_close(swap_file, NULL); + +out: + rzs.backing_swap = NULL; + return error; +} + +/* + * Check if request is within bounds and page aligned. + */ +static inline int valid_swap_request(struct bio *bio) +{ + if (unlikely( + (bio->bi_sector >= (rzs.disksize >> SECTOR_SHIFT)) || + (bio->bi_sector & (SECTORS_PER_PAGE - 1)) || + (bio->bi_vcnt != 1) || + (bio->bi_size != PAGE_SIZE) || + (bio->bi_io_vec[0].bv_offset != 0))) { + + return 0; + } + + /* swap request is valid */ + return 1; +} + +static void ramzswap_free_page(size_t index) +{ + u32 clen; + void *obj; + + u32 pagenum = rzs.table[index].pagenum; + u32 offset = rzs.table[index].offset; + + if (unlikely(!pagenum)) { + if (test_flag(index, RZS_ZERO)) { + clear_flag(index, RZS_ZERO); + stat_dec(stats.pages_zero); + } + return; + } + + if (unlikely(test_flag(index, RZS_UNCOMPRESSED))) { + clen = PAGE_SIZE; + __free_page(pfn_to_page(pagenum)); + clear_flag(index, RZS_UNCOMPRESSED); + stat_dec(stats.pages_expand); + goto out; + } + + obj = get_ptr_atomic(pagenum, offset, KM_USER0); + clen = xv_get_object_size(obj) - sizeof(struct zobj_header); + put_ptr_atomic(obj, KM_USER0); + + xv_free(rzs.mem_pool, pagenum, offset); + stat_dec_if_less(stats.good_compress, clen, PAGE_SIZE / 2 + 1); + +out: + stats.compr_size -= clen; + stat_dec(stats.pages_stored); + + rzs.table[index].pagenum = 0; + rzs.table[index].offset = 0; +} + +#ifdef CONFIG_SWAP_FREE_NOTIFY +/* + * callback function called when swap_map[offset] == 0 + * i.e page at this swap offset is no longer used + */ +static void ramzswap_free_notify(unsigned long index) +{ + if (rzs.table[index].pagenum) { + ramzswap_free_page(index); + stat_inc(rzs.stats.notify_free); + } +} +#endif + +int handle_zero_page(struct bio *bio) +{ + void *user_mem; + struct page *page = bio->bi_io_vec[0].bv_page; + + user_mem = get_ptr_atomic(page_to_pfn(page), 0, KM_USER0); + memset(user_mem, 0, PAGE_SIZE); + put_ptr_atomic(user_mem, KM_USER0); + + ramzswap_flush_dcache_page(page); + + set_bit(BIO_UPTODATE, &bio->bi_flags); + BIO_ENDIO(bio, 0); + return 0; +} + +int handle_uncompressed_page(struct bio *bio) +{ + u32 index; + struct page *page; + unsigned char *user_mem, *cmem; + + page = bio->bi_io_vec[0].bv_page; + index = bio->bi_sector >>SECTORS_PER_PAGE_SHIFT; + + user_mem = get_ptr_atomic(page_to_pfn(page), 0, KM_USER0); + cmem = get_ptr_atomic(rzs.table[index].pagenum, + rzs.table[index].offset, KM_USER1); + + memcpy(user_mem, cmem, PAGE_SIZE); + put_ptr_atomic(user_mem, KM_USER0); + put_ptr_atomic(cmem, KM_USER1); + + ramzswap_flush_dcache_page(page); + + set_bit(BIO_UPTODATE, &bio->bi_flags); + BIO_ENDIO(bio, 0); + return 0; +} + + +/* + * Called when request page is not present in ramzswap. + * Its either in backing swap device (if present) or + * this is an attempt to read before any previous write + * to this location - this happens due to readahead when + * swap device is read from user-space (e.g. during swapon) + */ +int handle_ramzswap_fault(struct bio *bio) +{ + void *user_mem; + struct page *page = bio->bi_io_vec[0].bv_page; + + /* + * Always forward such requests to backing swap + * device (if present) + */ + if (rzs.backing_swap) { + stat_dec(stats.num_reads); + stat_inc(stats.bdev_num_reads); + bio->bi_bdev = rzs.backing_swap; + return 1; + } + + /* + * Its unlikely event in case backing dev is + * not present + */ + pr_debug(C "Read before write on swap device: " + "sector=%lu, size=%u, offset=%u\n", + (ulong)(bio->bi_sector), bio->bi_size, + bio->bi_io_vec[0].bv_offset); + user_mem = kmap(page); + memset(user_mem, 0, PAGE_SIZE); + kunmap(page); + + set_bit(BIO_UPTODATE, &bio->bi_flags); + BIO_ENDIO(bio, 0); + return 0; +} + +#ifdef CONFIG_SWAP_NOTIFIERS +static int ramzswap_slot_free_notify(struct notifier_block *self, + unsigned long index, void *swap_file) +{ + ramzswap_free_page(index); + stat_inc(stats.notify_free); + return 0; +} + +static struct notifier_block ramzswap_slot_free_nb = { + .notifier_call = ramzswap_slot_free_notify +}; + +static int ramzswap_swapon_notify(struct notifier_block *self, + unsigned long swap_id, void *swap_file) +{ + int ret; + struct block_device *bdev; + struct inode *inode; + + inode = ((struct file *)swap_file)->f_mapping->host; + bdev = I_BDEV(inode); + if ((struct ramzswap *)(bdev->bd_disk->private_data) != &rzs) + return 0; + + ret = register_swap_event_notifier(&ramzswap_slot_free_nb, + SWAP_EVENT_SLOT_FREE, swap_id); + if (ret) + pr_err("Error registering swap free notifier\n"); + return ret; +} + +static int ramzswap_swapoff_notify(struct notifier_block *self, + unsigned long swap_id, void *swap_file) +{ + struct block_device *bdev; + struct inode *inode; + + inode = ((struct file *)swap_file)->f_mapping->host; + bdev = I_BDEV(inode); + if ((struct ramzswap *)(bdev->bd_disk->private_data) != &rzs) + return 0; + + unregister_swap_event_notifier(&ramzswap_slot_free_nb, + SWAP_EVENT_SLOT_FREE, swap_id); + return 0; +} + +static struct notifier_block ramzswap_swapon_nb = { + .notifier_call = ramzswap_swapon_notify +}; + +static struct notifier_block ramzswap_swapoff_nb = { + .notifier_call = ramzswap_swapoff_notify +}; +#endif + +int ramzswap_read(struct bio *bio) +{ + int ret; + u32 index; + size_t clen; + struct page *page; + struct zobj_header *zheader; + unsigned char *user_mem, *cmem; + + stat_inc(stats.num_reads); + + page = bio->bi_io_vec[0].bv_page; + index = bio->bi_sector >> SECTORS_PER_PAGE_SHIFT; + + if (test_flag(index, RZS_ZERO)) + return handle_zero_page(bio); + + /* Requested page is not present in compressed area */ + if (!rzs.table[index].pagenum) + return handle_ramzswap_fault(bio); + + /* Page is stored uncompressed since its incompressible */ + if (unlikely(test_flag(index, RZS_UNCOMPRESSED))) + return handle_uncompressed_page(bio); + + user_mem = get_ptr_atomic(page_to_pfn(page), 0, KM_USER0); + clen = PAGE_SIZE; + + cmem = get_ptr_atomic(rzs.table[index].pagenum, + rzs.table[index].offset, KM_USER1); + + ret = lzo1x_decompress_safe( + cmem + sizeof(*zheader), + xv_get_object_size(cmem) - sizeof(*zheader), + user_mem, &clen); + + put_ptr_atomic(user_mem, KM_USER0); + put_ptr_atomic(cmem, KM_USER1); + + /* should NEVER happen */ + if (unlikely(ret != LZO_E_OK)) { + pr_err(C "Decompression failed! err=%d, page=%u\n", + ret, index); + stat_inc(stats.failed_reads); + goto out; + } + + ramzswap_flush_dcache_page(page); + + set_bit(BIO_UPTODATE, &bio->bi_flags); + BIO_ENDIO(bio, 0); + return 0; + +out: + BIO_IO_ERROR(bio); + return 0; +} + +int ramzswap_write(struct bio *bio) +{ + int ret, fwd_write_request = 0; + u32 offset; + size_t clen, index; + struct zobj_header *zheader; + struct page *page, *page_store; + unsigned char *user_mem, *cmem, *src; + + stat_inc(stats.num_writes); + + page = bio->bi_io_vec[0].bv_page; + index = bio->bi_sector >> SECTORS_PER_PAGE_SHIFT; + + src = rzs.compress_buffer; + + /* + * System swaps to same sector again when the stored page + * is no longer referenced by any process. So, its now safe + * to free the memory that was allocated for this page. + */ + if (rzs.table[index].pagenum) + ramzswap_free_page(index); + + /* + * No memory ia allocated for zero filled pages. + * Simply clear zero page flag. + */ + if (test_flag(index, RZS_ZERO)) { + stat_dec(stats.pages_zero); + clear_flag(index, RZS_ZERO); + } + + mutex_lock(&rzs.lock); + + user_mem = get_ptr_atomic(page_to_pfn(page), 0, KM_USER0); + if (page_zero_filled(user_mem)) { + put_ptr_atomic(user_mem, KM_USER0); + mutex_unlock(&rzs.lock); + stat_inc(stats.pages_zero); + set_flag(index, RZS_ZERO); + + set_bit(BIO_UPTODATE, &bio->bi_flags); + BIO_ENDIO(bio, 0); + return 0; + } + + if (rzs.backing_swap && + (stats.compr_size > rzs.memlimit - PAGE_SIZE)) { + put_ptr_atomic(user_mem, KM_USER0); + mutex_unlock(&rzs.lock); + fwd_write_request = 1; + goto out; + } + + ret = lzo1x_1_compress(user_mem, PAGE_SIZE, src, &clen, + rzs.compress_workmem); + + put_ptr_atomic(user_mem, KM_USER0); + + if (unlikely(ret != LZO_E_OK)) { + mutex_unlock(&rzs.lock); + pr_err(C "Compression failed! err=%d\n", ret); + stat_inc(stats.failed_writes); + goto out; + } + + /* + * Page is incompressible. Forward it to backing swap + * if present. Otherwise, store it as-is (uncompressed) + * since we do not want to return too many swap write + * errors which has side effect of hanging the system. + */ + if (unlikely(clen > MAX_CPAGE_SIZE)) { + if (rzs.backing_swap) { + mutex_unlock(&rzs.lock); + fwd_write_request = 1; + goto out; + } + + clen = PAGE_SIZE; + page_store = alloc_page(GFP_NOIO | __GFP_HIGHMEM); + if (unlikely(!page_store)) { + mutex_unlock(&rzs.lock); + stat_inc(stats.failed_writes); + goto out; + } + + offset = 0; + set_flag(index, RZS_UNCOMPRESSED); + stat_inc(stats.pages_expand); + rzs.table[index].pagenum = page_to_pfn(page_store); + src = get_ptr_atomic(page_to_pfn(page), 0, KM_USER0); + goto memstore; + } + + if (xv_malloc(rzs.mem_pool, clen + sizeof(*zheader), + &rzs.table[index].pagenum, &offset, + GFP_NOIO | __GFP_HIGHMEM)) { + mutex_unlock(&rzs.lock); + pr_info(C "Error allocating memory for compressed " + "page: %zu, size=%zu\n", index, clen); + stat_inc(stats.failed_writes); + if (rzs.backing_swap) + fwd_write_request = 1; + goto out; + } + +memstore: + rzs.table[index].offset = offset; + + cmem = get_ptr_atomic(rzs.table[index].pagenum, + rzs.table[index].offset, KM_USER1); + +#if 0 + /* Back-reference needed for memory defragmentation */ + if (!test_flag(index, RZS_UNCOMPRESSED)) { + zheader = (struct zobj_header *)cmem; + zheader->table_idx = index; + cmem += sizeof(*zheader); + } +#endif + + memcpy(cmem, src, clen); + + put_ptr_atomic(cmem, KM_USER1); + if (unlikely(test_flag(index, RZS_UNCOMPRESSED))) + put_ptr_atomic(src, KM_USER0); + + /* Update stats */ + stats.compr_size += clen; + stat_inc(stats.pages_stored); + stat_inc_if_less(stats.good_compress, clen, PAGE_SIZE / 2 + 1); + + mutex_unlock(&rzs.lock); + + set_bit(BIO_UPTODATE, &bio->bi_flags); + BIO_ENDIO(bio, 0); + return 0; + +out: + if (fwd_write_request) { + stat_inc(stats.bdev_num_writes); + bio->bi_bdev = rzs.backing_swap; + return 1; + } + + BIO_IO_ERROR(bio); + return 0; +} + +/* + * Handler function for all ramzswap I/O requests. + */ +static int ramzswap_make_request(struct request_queue *queue, struct bio *bio) +{ + int ret = 0; + + if (!valid_swap_request(bio)) { + stat_inc(stats.invalid_io); + BIO_IO_ERROR(bio); + return 0; + } + + switch (bio_data_dir(bio)) { + case READ: + ret = ramzswap_read(bio); + break; + + case WRITE: + ret = ramzswap_write(bio); + break; + } + + return ret; +} + +/* + * Swap header (1st page of swap device) contains information + * to indentify it as a swap partition. Prepare such a header + * for ramzswap device (ramzswap0) so that swapon can identify + * it as swap partition. In case backing swap device is provided, + * copy its swap header. + */ +static int setup_swap_header(union swap_header *s) +{ + int ret = 0; + struct page *page; + struct address_space *mapping; + union swap_header *backing_swap_header; + + /* + * There is no backing swap device. Create a swap header + * that is acceptable by swapon. + */ + if (rzs.backing_swap == NULL) { + s->info.version = 1; + s->info.last_page = (rzs.disksize >> PAGE_SHIFT) - 1; + s->info.nr_badpages = 0; + memcpy(s->magic.magic, "SWAPSPACE2", 10); + return 0; + } + + /* + * We have a backing swap device. Copy its swap header + * to ramzswap device header. If this header contains + * invalid information (backing device not a swap + * partition, etc.), swapon will fail for ramzswap + * which is correct behavior - we don't want to swap + * over filesystem partition! + */ + + /* Read the backing swap header (code from sys_swapon) */ + mapping = rzs.swap_file->f_mapping; + if (!mapping->a_ops->readpage) { + ret = -EINVAL; + goto out; + } + + page = read_mapping_page(mapping, 0, rzs.swap_file); + if (IS_ERR(page)) { + ret = PTR_ERR(page); + goto out; + } + + backing_swap_header = kmap(page); + memcpy(s, backing_swap_header, sizeof(*s)); + if (s->info.nr_badpages) { + pr_info("Cannot use backing swap with bad pages (%u)\n", + s->info.nr_badpages); + ret = -EINVAL; + } + /* + * ramzswap disksize equals number of usable pages in backing + * swap. Set last_page in swap header to match this disksize + * ('last_page' means 0-based index of last usable swap page). + */ + s->info.last_page = (rzs.disksize >> PAGE_SHIFT) - 1; + kunmap(page); + +out: + return ret; +} + +static void ramzswap_set_disksize(size_t totalram_bytes) +{ + rzs.disksize = disksize_kb << 10; + + if (!disksize_kb) { + pr_info(C + "disk size not provided. You can use disksize_kb module " + "param to specify size.\nUsing default: (%u%% of RAM).\n", + DEFAULT_DISKSIZE_PERC_RAM + ); + rzs.disksize = DEFAULT_DISKSIZE_PERC_RAM * + (totalram_bytes / 100); + } + + if (disksize_kb > 2 * (totalram_bytes >> 10)) { + pr_info(C + "There is little point creating a ramzswap of greater than " + "twice the size of memory since we expect a 2:1 compression " + "ratio. Note that ramzswap uses about 0.1%% of the size of " + "the swap device when not in use so a huge ramzswap is " + "wasteful.\n" + "\tMemory Size: %zu kB\n" + "\tSize you selected: %lu kB\n" + "Continuing anyway ...\n", + totalram_bytes >> 10, disksize_kb + ); + } + + rzs.disksize &= PAGE_MASK; + pr_info(C "disk size set to %zu kB\n", rzs.disksize >> 10); +} + +/* + * memlimit cannot be greater than backing disk size. + */ +static void ramzswap_set_memlimit(size_t totalram_bytes) +{ + int memlimit_valid = 1; + rzs.memlimit = memlimit_kb << 10; + + if (!rzs.memlimit) { + pr_info(C "memory limit not set. You can use " + "memlimit_kb module param to specify limit."); + memlimit_valid = 0; + } + + if (rzs.memlimit > rzs.disksize) { + pr_info(C "memory limit cannot be greater than " + "disksize: limit=%zu, disksize=%zu", + rzs.memlimit, rzs.disksize); + memlimit_valid = 0; + } + + if (!memlimit_valid) { + size_t mempart, disksize; + pr_info(C "\nUsing default: MIN[(%u%% of RAM), " + "(backing disk size)].\n", + DEFAULT_MEMLIMIT_PERC_RAM); + mempart = DEFAULT_MEMLIMIT_PERC_RAM * (totalram_bytes / 100); + disksize = rzs.disksize; + rzs.memlimit = mempart > disksize ? disksize : mempart; + } + + if (rzs.memlimit > totalram_bytes / 2) { + pr_info(C + "Its not advisable setting limit more than half of " + "size of memory since we expect a 2:1 compression ratio. " + "Limit represents amount of *compressed* data we can keep " + "in memory!\n" + "\tMemory Size: %zu kB\n" + "\tLimit you selected: %lu kB\n" + "Continuing anyway ...\n", + totalram_bytes >> 10, memlimit_kb + ); + } + + rzs.memlimit &= PAGE_MASK; + BUG_ON(!rzs.memlimit); + + pr_info(C "memory limit set to %zu kB\n", rzs.memlimit >> 10); +} + +static int __init ramzswap_init(void) +{ + int ret; + size_t num_pages, totalram_bytes; + struct page *page; + void *swap_header; + + mutex_init(&rzs.lock); + + ret = setup_backing_swap(); + if (ret) + goto fail; + + totalram_bytes = totalram_pages << PAGE_SHIFT; + + if (rzs.backing_swap) + ramzswap_set_memlimit(totalram_bytes); + else + ramzswap_set_disksize(totalram_bytes); + + rzs.compress_workmem = kmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL); + if (rzs.compress_workmem == NULL) { + pr_err(C "Error allocating compressor working memory\n"); + ret = -ENOMEM; + goto fail; + } + + rzs.compress_buffer = kmalloc(2 * PAGE_SIZE, GFP_KERNEL); + if (rzs.compress_buffer == NULL) { + pr_err(C "Error allocating compressor buffer space\n"); + ret = -ENOMEM; + goto fail; + } + + num_pages = rzs.disksize >> PAGE_SHIFT; + rzs.table = vmalloc(num_pages * sizeof(*rzs.table)); + if (rzs.table == NULL) { + pr_err(C "Error allocating ramzswap address table\n"); + ret = -ENOMEM; + goto fail; + } + memset(rzs.table, 0, num_pages * sizeof(*rzs.table)); + + page = alloc_page(__GFP_ZERO); + if (page == NULL) { + pr_err(C "Error allocating swap header page\n"); + ret = -ENOMEM; + goto fail; + } + rzs.table[0].pagenum = page_to_pfn(page); + set_flag(0, RZS_UNCOMPRESSED); + + swap_header = kmap(page); + ret = setup_swap_header((union swap_header *)(swap_header)); + kunmap(page); + if (ret) { + pr_err(C "Error setting swap header\n"); + goto fail; + } + + rzs.disk = alloc_disk(1); + if (rzs.disk == NULL) { + pr_err(C "Error allocating disk structure\n"); + ret = -ENOMEM; + goto fail; + } + + rzs.disk->first_minor = 0; + rzs.disk->fops = &ramzswap_devops; + /* + * It is named like this to prevent distro installers + * from offering ramzswap as installation target. They + * seem to ignore all devices beginning with 'ram' + */ + strcpy(rzs.disk->disk_name, "ramzswap0"); + + rzs.disk->major = register_blkdev(0, rzs.disk->disk_name); + if (rzs.disk->major < 0) { + pr_err(C "Cannot register block device\n"); + ret = -EFAULT; + goto fail; + } + + rzs.disk->queue = blk_alloc_queue(GFP_KERNEL); + if (rzs.disk->queue == NULL) { + pr_err(C "Cannot register disk queue\n"); + ret = -EFAULT; + goto fail; + } + + rzs.disk->private_data = &rzs; + set_capacity(rzs.disk, rzs.disksize >> SECTOR_SHIFT); + blk_queue_make_request(rzs.disk->queue, ramzswap_make_request); + + add_disk(rzs.disk); + + rzs.mem_pool = xv_create_pool(); + if (!rzs.mem_pool) { + pr_err(C "Error creating memory pool\n"); + ret = -ENOMEM; + goto fail; + } + +#if defined(STATS) + proc = create_proc_entry("ramzswap", S_IRUGO, NULL); + if (proc) + proc->read_proc = &proc_ramzswap_read; + else { + ret = -ENOMEM; + pr_warning(C "Error creating proc entry\n"); + goto fail; + } +#endif + + /* + * Pages that compress to size greater than this are forwarded + * to physical swap disk (if backing dev is provided) + */ + if (rzs.backing_swap) + MAX_CPAGE_SIZE = MAX_CPAGE_SIZE_BDEV; + else + MAX_CPAGE_SIZE = MAX_CPAGE_SIZE_NOBDEV; + + pr_debug(C "Max compressed page size: %u bytes\n", MAX_CPAGE_SIZE); + +#ifdef CONFIG_SWAP_NOTIFIERS + ret = register_swap_event_notifier(&ramzswap_swapon_nb, + SWAP_EVENT_SWAPON, 0); + if (ret) { + pr_err("Error registering swapon notifier\n"); + goto fail; + } + + ret = register_swap_event_notifier(&ramzswap_swapoff_nb, + SWAP_EVENT_SWAPOFF, 0); + if (ret) { + unregister_swap_event_notifier(&ramzswap_swapoff_nb, + SWAP_EVENT_SWAPON, 0); + pr_err("Error registering swapoff notifier\n"); + goto fail; + } +#endif + + pr_debug(C "Initialization done!\n"); + return 0; + +fail: + if (rzs.disk != NULL) { + if (rzs.disk->major > 0) + unregister_blkdev(rzs.disk->major, rzs.disk->disk_name); + del_gendisk(rzs.disk); + } + + if (rzs.table && rzs.table[0].pagenum) + __free_page(pfn_to_page(rzs.table[0].pagenum)); + kfree(rzs.compress_workmem); + kfree(rzs.compress_buffer); + vfree(rzs.table); + xv_destroy_pool(rzs.mem_pool); +#if defined(STATS) + if (proc) + remove_proc_entry("ramzswap", proc->parent); +#endif + pr_err(C "Initialization failed: err=%d\n", ret); + return ret; +} + +static void __exit ramzswap_exit(void) +{ + size_t index, num_pages; + num_pages = rzs.disksize >> PAGE_SHIFT; + +#ifdef CONFIG_SWAP_NOTIFIERS + unregister_swap_event_notifier(&ramzswap_swapon_nb, + SWAP_EVENT_SWAPON, 0); + unregister_swap_event_notifier(&ramzswap_swapoff_nb, + SWAP_EVENT_SWAPOFF, 0); +#endif + + unregister_blkdev(rzs.disk->major, rzs.disk->disk_name); + del_gendisk(rzs.disk); + + /* Close backing swap device (if present) */ + if (rzs.backing_swap) { + set_blocksize(rzs.backing_swap, rzs.old_block_size); + bd_release(rzs.backing_swap); + filp_close(rzs.swap_file, NULL); + } + + __free_page(pfn_to_page(rzs.table[0].pagenum)); + kfree(rzs.compress_workmem); + kfree(rzs.compress_buffer); + + /* Free all pages that are still in ramzswap */ + for (index = 1; index < num_pages; index++) { + u32 pagenum, offset; + + pagenum = rzs.table[index].pagenum; + offset = rzs.table[index].offset; + + if (!pagenum) + continue; + + if (unlikely(test_flag(index, RZS_UNCOMPRESSED))) + __free_page(pfn_to_page(pagenum)); + else + xv_free(rzs.mem_pool, pagenum, offset); + } + + vfree(rzs.table); + xv_destroy_pool(rzs.mem_pool); + +#if defined(STATS) + remove_proc_entry("ramzswap", proc->parent); +#endif + pr_debug(C "cleanup done!\n"); +} + +/* + * This param is applicable only when there is no backing swap device. + * We ignore this param in case backing dev is provided since then its + * always equal to size of the backing swap device. + * + * This size refers to amount of (uncompressed) data it can hold. + * For e.g. disksize_kb=1024 means it can hold 1024kb worth of + * uncompressed data even if this data compresses to just, say, 100kb. + * + * Default value is used if this param is missing or 0 (if its applicable). + * Default: [DEFAULT_DISKSIZE_PERC_RAM]% of RAM + */ +module_param(disksize_kb, ulong, 0); +MODULE_PARM_DESC(disksize_kb, "ramzswap device size (kB)"); + +/* + * This param is applicable only when backing swap device is provided. + * This refers to limit on amount of (compressed) data it can hold in + * memory. Note that total amount of memory used (MemUsedTotal) can + * exceed this memlimit since that includes memory wastage due to + * fragmentation and metadata overhead. + * + * Any additional data beyond this limit is forwarded to backing + * swap device. TODO: allow changing memlimit at runtime. + * + * Default value is used if this param is missing or 0 (if its applicable). + * Default: MIN([DEFAULT_MEMLIMIT_PERC_RAM]% of RAM, Backing Device Size) + */ +module_param(memlimit_kb, ulong, 0); +MODULE_PARM_DESC(memlimit_kb, "ramzswap memory limit (kB)"); + +/* + * This is block device to be used as backing store for ramzswap. + * When pages more than memlimit_kb as swapped to ramzswap, we store + * any additional pages in this device. We may also move some pages + * from ramzswap to this device in case system is really low on + * memory (TODO). + * + * This device is not directly visible to kernel as a swap device + * (/proc/swaps will only show /dev/ramzswap0 and not this device). + * Managing this backing device is the job of ramzswap module. + */ +module_param(backing_swap, charp, 0); +MODULE_PARM_DESC(backing_swap, "Backing swap partition"); + +module_init(ramzswap_init); +module_exit(ramzswap_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nitin Gupta "); +MODULE_DESCRIPTION("Compressed RAM Based Swap Device"); diff --git a/extra/ramzswap.h b/extra/ramzswap.h new file mode 100644 index 000000000..10b4b369a --- /dev/null +++ b/extra/ramzswap.h @@ -0,0 +1,160 @@ +/* + * Compressed RAM based swap device + * + * Copyright (C) 2008, 2009 Nitin Gupta + * + * This RAM based block device acts as swap disk. + * Pages swapped to this device are compressed and + * stored in memory. + * + * Released under the terms of GNU General Public License Version 2.0 + * + * Project home: http://compcache.googlecode.com + */ + +#ifndef _RAMZSWAP_H_ +#define _RAMZSWAP_H_ + +#include "xvmalloc.h" + +/* + * Stored at beginning of each compressed object. + * + * It stores back-reference to table entry which points to this + * object. This is required to support memory defragmentation or + * migrating compressed pages to backing swap disk. + */ +struct zobj_header { +#if 0 + u32 table_idx; +#endif +}; + +/*-- Configurable parameters */ + +/* Default ramzswap disk size: 25% of total RAM */ +#define DEFAULT_DISKSIZE_PERC_RAM 25 +#define DEFAULT_MEMLIMIT_PERC_RAM 15 + +/* + * Max compressed page size when backing device is provided. + * Pages that compress to size greater than this are sent to + * physical swap disk. + */ +#define MAX_CPAGE_SIZE_BDEV (PAGE_SIZE / 2) + +/* + * Max compressed page size when there is no backing dev. + * Pages that compress to size greater than this are stored + * uncompressed in memory. + */ +#define MAX_CPAGE_SIZE_NOBDEV (PAGE_SIZE / 4 * 3) + +/* + * NOTE: MAX_CPAGE_SIZE_{BDEV,NOBDEV} sizes must be + * less than or equal to: + * XV_MAX_ALLOC_SIZE - sizeof(struct zobj_header) + * since otherwise xvMalloc would always return failure. + */ + +/*-- End of configurable params */ + +#define SECTOR_SHIFT 9 +#define SECTOR_SIZE (1 << SECTOR_SHIFT) +#define SECTORS_PER_PAGE_SHIFT (PAGE_SHIFT - SECTOR_SHIFT) +#define SECTORS_PER_PAGE (1 << SECTORS_PER_PAGE_SHIFT) + +/* Message prefix */ +#define C "ramzswap: " + +/* Debugging and Stats */ +#define NOP do { } while (0) + +#if defined(CONFIG_BLK_DEV_RAMZSWAP_STATS) +#define STATS +#endif + +#if defined(STATS) +#define stat_inc(stat) ((stat)++) +#define stat_dec(stat) ((stat)--) +#define stat_inc_if_less(stat, val1, val2) \ + ((stat) += ((val1) < (val2) ? 1 : 0)) +#define stat_dec_if_less(stat, val1, val2) \ + ((stat) -= ((val1) < (val2) ? 1 : 0)) +#else /* STATS */ +#define stat_inc(x) NOP +#define stat_dec(x) NOP +#define stat_inc_if_less(x, v1, v2) NOP +#define stat_dec_if_less(x, v1, v2) NOP +#endif /* STATS */ + +/* Flags for ramzswap pages (table[page_no].flags) */ +enum rzs_pageflags { + /* Page is stored uncompressed */ + RZS_UNCOMPRESSED, + + /* Page consists entirely of zeros */ + RZS_ZERO, + + __NR_RZS_PAGEFLAGS, +}; + +/*-- Data structures */ + +/* Indexed by page no. */ +struct table { + u32 pagenum; + u16 offset; + u8 count; /* object ref count (not yet used) */ + u8 flags; +}; + +struct ramzswap { + struct xv_pool *mem_pool; + void *compress_workmem; + void *compress_buffer; + struct table *table; + struct mutex lock; + struct gendisk *disk; + /* + * This is limit on compressed data size (stats.compr_size) + * Its applicable only when backing swap device is present. + */ + size_t memlimit; /* bytes */ + /* + * This is limit on amount of *uncompressed* worth of data + * we can hold. When backing swap device is provided, it is + * set equal to device size. + */ + size_t disksize; /* bytes */ + + /* backing swap device info */ + struct block_device *backing_swap; + struct file *swap_file; + int old_block_size; + int init_notify_callback; +}; + +struct ramzswap_stats { + /* basic stats */ + size_t compr_size; /* compressed size of pages stored - + * needed to enforce memlimit */ + /* more stats */ +#if defined(STATS) + u64 num_reads; /* failed + successful */ + u64 num_writes; /* --do-- */ + u64 failed_reads; /* can happen when memory is too low */ + u64 failed_writes; /* should NEVER! happen */ + u64 invalid_io; /* non-swap I/O requests */ + u64 notify_free; /* no. of pages freed by swap free notifier */ + u32 pages_zero; /* no. of zero filled pages */ + u32 pages_stored; /* no. of pages currently stored */ + u32 good_compress; /* no. of pages with compression ratio<=50% */ + u32 pages_expand; /* no. of incompressible pages */ + u64 bdev_num_reads; /* no. of reads on backing dev */ + u64 bdev_num_writes; /* no. of writes on backing dev */ +#endif +}; +/*-- */ + +#endif diff --git a/extra/xvmalloc.c b/extra/xvmalloc.c new file mode 100644 index 000000000..f4195c4ab --- /dev/null +++ b/extra/xvmalloc.c @@ -0,0 +1,557 @@ +/* + * xvmalloc memory allocator + * + * Copyright (C) 2008, 2009 Nitin Gupta + * + * This code is released using a dual license strategy: BSD/GPL + * You can choose the licence that better fits your requirements. + * + * Released under the terms of 3-clause BSD License + * Released under the terms of GNU General Public License Version 2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include "xvmalloc.h" +#include "xvmalloc_int.h" + +static void stat_inc(u64 *value) +{ + *value = *value + 1; +} + +static void stat_dec(u64 *value) +{ + *value = *value - 1; +} + +static int test_flag(struct block_header *block, enum blockflags flag) +{ + return block->prev & BIT(flag); +} + +static void set_flag(struct block_header *block, enum blockflags flag) +{ + block->prev |= BIT(flag); +} + +static void clear_flag(struct block_header *block, enum blockflags flag) +{ + block->prev &= ~BIT(flag); +} + +/* + * Given pair, provide a derefrencable pointer. + * This is called from xv_malloc/xv_free path, so it needs to be fast. + */ +static void *get_ptr_atomic(u32 pagenum, u16 offset, enum km_type type) +{ + unsigned char *base; + + base = kmap_atomic(pfn_to_page(pagenum), type); + return base + offset; +} + +static void put_ptr_atomic(void *ptr, enum km_type type) +{ + kunmap_atomic(ptr, type); +} + +static u32 get_blockprev(struct block_header *block) +{ + return block->prev & PREV_MASK; +} + +static void set_blockprev(struct block_header *block, u16 new_offset) +{ + block->prev = new_offset | (block->prev & FLAGS_MASK); +} + +static struct block_header *BLOCK_NEXT(struct block_header *block) +{ + return (struct block_header *)((char *)block + block->size + XV_ALIGN); +} + +/* + * Get index of free list containing blocks of maximum size + * which is less than or equal to given size. + */ +static u32 get_index_for_insert(u32 size) +{ + if (unlikely(size > XV_MAX_ALLOC_SIZE)) + size = XV_MAX_ALLOC_SIZE; + size &= ~FL_DELTA_MASK; + return (size - XV_MIN_ALLOC_SIZE) >> FL_DELTA_SHIFT; +} + +/* + * Get index of free list having blocks of size greater than + * or equal to requested size. + */ +static u32 get_index(u32 size) +{ + if (unlikely(size < XV_MIN_ALLOC_SIZE)) + size = XV_MIN_ALLOC_SIZE; + size = ALIGN(size, FL_DELTA); + return (size - XV_MIN_ALLOC_SIZE) >> FL_DELTA_SHIFT; +} + +/* + * Allocate a memory page. Called when a pool needs to grow. + */ +static u32 xv_alloc_page(gfp_t flags) +{ + struct page *page; + + page = alloc_page(flags); + if (unlikely(!page)) + return 0; + + return page_to_pfn(page); +} + +/* + * Called when all objects in a page are freed. + */ +static void xv_free_page(u32 pagenum) +{ + __free_page(pfn_to_page(pagenum)); +} + +/** + * find_block - find block of at least given size + * @pool: memory pool to search from + * @size: size of block required + * @pagenum: page no. containing required block + * @offset: offset within the page where block is located. + * + * Searches two level bitmap to locate block of at least + * the given size. If such a block is found, it provides + * to identify this block and returns index + * in freelist where we found this block. + * Otherwise, returns 0 and params are not touched. + */ +static u32 find_block(struct xv_pool *pool, u32 size, + u32 *pagenum, u32 *offset) +{ + ulong flbitmap, slbitmap; + u32 flindex, slindex, slbitstart; + + /* There are no free blocks in this pool */ + if (!pool->flbitmap) + return 0; + + /* Get freelist index correspoding to this size */ + slindex = get_index(size); + slbitmap = pool->slbitmap[slindex / BITS_PER_LONG]; + slbitstart = slindex % BITS_PER_LONG; + + /* + * If freelist is not empty at this index, we found the + * block - head of this list. This is approximate best-fit match. + */ + if (test_bit(slbitstart, &slbitmap)) { + *pagenum = pool->freelist[slindex].pagenum; + *offset = pool->freelist[slindex].offset; + return slindex; + } + + /* + * No best-fit found. Search a bit further in bitmap for a free block. + * Second level bitmap consists of series of 32-bit chunks. Search + * further in the chunk where we expected a best-fit, starting from + * index location found above. + */ + slbitstart++; + slbitmap >>= slbitstart; + + /* Skip this search if we were already at end of this bitmap chunk */ + if ((slbitstart != BITS_PER_LONG) && slbitmap) { + slindex += __ffs(slbitmap) + 1; + *pagenum = pool->freelist[slindex].pagenum; + *offset = pool->freelist[slindex].offset; + return slindex; + } + + /* Now do a full two-level bitmap search to find next nearest fit */ + flindex = slindex / BITS_PER_LONG; + + flbitmap = (pool->flbitmap) >> (flindex + 1); + if (!flbitmap) + return 0; + + flindex += __ffs(flbitmap) + 1; + slbitmap = pool->slbitmap[flindex]; + slindex = (flindex * BITS_PER_LONG) + __ffs(slbitmap); + *pagenum = pool->freelist[slindex].pagenum; + *offset = pool->freelist[slindex].offset; + + return slindex; +} + +/* + * Insert block at in freelist of given pool. + * freelist used depends on block size. + */ +static void insert_block(struct xv_pool *pool, u32 pagenum, u32 offset, + struct block_header *block) +{ + u32 flindex, slindex; + struct block_header *nextblock; + + slindex = get_index_for_insert(block->size); + flindex = slindex / BITS_PER_LONG; + + block->link.prev_pagenum = 0; + block->link.prev_offset = 0; + block->link.next_pagenum = pool->freelist[slindex].pagenum; + block->link.next_offset = pool->freelist[slindex].offset; + pool->freelist[slindex].pagenum = pagenum; + pool->freelist[slindex].offset = offset; + + if (block->link.next_pagenum) { + nextblock = get_ptr_atomic(block->link.next_pagenum, + block->link.next_offset, KM_USER1); + nextblock->link.prev_pagenum = pagenum; + nextblock->link.prev_offset = offset; + put_ptr_atomic(nextblock, KM_USER1); + } + + __set_bit(slindex % BITS_PER_LONG, &pool->slbitmap[flindex]); + __set_bit(flindex, &pool->flbitmap); +} + +/* + * Remove block from head of freelist. Index 'slindex' identifies the freelist. + */ +static void remove_block_head(struct xv_pool *pool, + struct block_header *block, u32 slindex) +{ + struct block_header *tmpblock; + u32 flindex = slindex / BITS_PER_LONG; + + pool->freelist[slindex].pagenum = block->link.next_pagenum; + pool->freelist[slindex].offset = block->link.next_offset; + block->link.prev_pagenum = 0; + block->link.prev_offset = 0; + + if (!pool->freelist[slindex].pagenum) { + __clear_bit(slindex % BITS_PER_LONG, &pool->slbitmap[flindex]); + if (!pool->slbitmap[flindex]) + __clear_bit(flindex, &pool->flbitmap); + } else { + /* + * DEBUG ONLY: We need not reinitialize freelist head previous + * pointer to 0 - we never depend on its value. But just for + * sanity, lets do it. + */ + tmpblock = get_ptr_atomic(pool->freelist[slindex].pagenum, + pool->freelist[slindex].offset, KM_USER1); + tmpblock->link.prev_pagenum = 0; + tmpblock->link.prev_offset = 0; + put_ptr_atomic(tmpblock, KM_USER1); + } +} + +/* + * Remove block from freelist. Index 'slindex' identifies the freelist. + */ +static void remove_block(struct xv_pool *pool, u32 pagenum, u32 offset, + struct block_header *block, u32 slindex) +{ + u32 flindex; + struct block_header *tmpblock; + + if (pool->freelist[slindex].pagenum == pagenum + && pool->freelist[slindex].offset == offset) { + remove_block_head(pool, block, slindex); + return; + } + + flindex = slindex / BITS_PER_LONG; + + if (block->link.prev_pagenum) { + tmpblock = get_ptr_atomic(block->link.prev_pagenum, + block->link.prev_offset, KM_USER1); + tmpblock->link.next_pagenum = block->link.next_pagenum; + tmpblock->link.next_offset = block->link.next_offset; + put_ptr_atomic(tmpblock, KM_USER1); + } + + if (block->link.next_pagenum) { + tmpblock = get_ptr_atomic(block->link.next_pagenum, + block->link.next_offset, KM_USER1); + tmpblock->link.prev_pagenum = block->link.prev_pagenum; + tmpblock->link.prev_offset = block->link.prev_offset; + put_ptr_atomic(tmpblock, KM_USER1); + } + + return; +} + +/* + * Allocate a page and add it freelist of given pool. + */ +static int grow_pool(struct xv_pool *pool, gfp_t flags) +{ + u32 pagenum; + struct block_header *block; + + pagenum = xv_alloc_page(flags); + if (unlikely(!pagenum)) + return -ENOMEM; + + stat_inc(&pool->total_pages); + + spin_lock(&pool->lock); + block = get_ptr_atomic(pagenum, 0, KM_USER0); + + block->size = PAGE_SIZE - XV_ALIGN; + set_flag(block, BLOCK_FREE); + clear_flag(block, PREV_FREE); + set_blockprev(block, 0); + + insert_block(pool, pagenum, 0, block); + + put_ptr_atomic(block, KM_USER0); + spin_unlock(&pool->lock); + + return 0; +} + +/* + * Create a memory pool. Allocates freelist, bitmaps and other + * per-pool metadata. + */ +struct xv_pool *xv_create_pool(void) +{ + u32 ovhd_size; + struct xv_pool *pool; + + ovhd_size = roundup(sizeof(*pool), PAGE_SIZE); + pool = kzalloc(ovhd_size, GFP_KERNEL); + if (!pool) + return NULL; + + spin_lock_init(&pool->lock); + + return pool; +} +EXPORT_SYMBOL_GPL(xv_create_pool); + +void xv_destroy_pool(struct xv_pool *pool) +{ + kfree(pool); +} +EXPORT_SYMBOL_GPL(xv_destroy_pool); + +/** + * xv_malloc - Allocate block of given size from pool. + * @pool: pool to allocate from + * @size: size of block to allocate + * @pagenum: page no. that holds the object + * @offset: location of object within pagenum + * + * On success, identifies block allocated + * and 0 is returned. On failure, is set to + * 0 and -ENOMEM is returned. + * + * Allocation requests with size > XV_MAX_ALLOC_SIZE will fail. + */ +int xv_malloc(struct xv_pool *pool, u32 size, u32 *pagenum, u32 *offset, + gfp_t flags) +{ + int error; + u32 index, tmpsize, origsize, tmpoffset; + struct block_header *block, *tmpblock; + + *pagenum = 0; + *offset = 0; + origsize = size; + + if (unlikely(!size || size > XV_MAX_ALLOC_SIZE)) + return -ENOMEM; + + size = ALIGN(size, XV_ALIGN); + + spin_lock(&pool->lock); + + index = find_block(pool, size, pagenum, offset); + + if (!*pagenum) { + spin_unlock(&pool->lock); + if (flags & GFP_NOWAIT) + return -ENOMEM; + error = grow_pool(pool, flags); + if (unlikely(error)) + return -ENOMEM; + + spin_lock(&pool->lock); + index = find_block(pool, size, pagenum, offset); + } + + if (!*pagenum) { + spin_unlock(&pool->lock); + return -ENOMEM; + } + + block = get_ptr_atomic(*pagenum, *offset, KM_USER0); + + remove_block_head(pool, block, index); + + /* Split the block if required */ + tmpoffset = *offset + size + XV_ALIGN; + tmpsize = block->size - size; + tmpblock = (struct block_header *)((char *)block + size + XV_ALIGN); + if (tmpsize) { + tmpblock->size = tmpsize - XV_ALIGN; + set_flag(tmpblock, BLOCK_FREE); + clear_flag(tmpblock, PREV_FREE); + + set_blockprev(tmpblock, *offset); + if (tmpblock->size >= XV_MIN_ALLOC_SIZE) + insert_block(pool, *pagenum, tmpoffset, tmpblock); + + if (tmpoffset + XV_ALIGN + tmpblock->size != PAGE_SIZE) { + tmpblock = BLOCK_NEXT(tmpblock); + set_blockprev(tmpblock, tmpoffset); + } + } else { + /* This block is exact fit */ + if (tmpoffset != PAGE_SIZE) + clear_flag(tmpblock, PREV_FREE); + } + + block->size = origsize; + clear_flag(block, BLOCK_FREE); + + put_ptr_atomic(block, KM_USER0); + spin_unlock(&pool->lock); + + *offset += XV_ALIGN; + + return 0; +} +EXPORT_SYMBOL_GPL(xv_malloc); + +/* + * Free block identified with + */ +void xv_free(struct xv_pool *pool, u32 pagenum, u32 offset) +{ + void *page; + struct block_header *block, *tmpblock; + + offset -= XV_ALIGN; + + spin_lock(&pool->lock); + + page = get_ptr_atomic(pagenum, 0, KM_USER0); + block = (struct block_header *)((char *)page + offset); + + /* Catch double free bugs */ + BUG_ON(test_flag(block, BLOCK_FREE)); + + block->size = ALIGN(block->size, XV_ALIGN); + + tmpblock = BLOCK_NEXT(block); + if (offset + block->size + XV_ALIGN == PAGE_SIZE) + tmpblock = NULL; + + /* Merge next block if its free */ + if (tmpblock && test_flag(tmpblock, BLOCK_FREE)) { + /* + * Blocks smaller than XV_MIN_ALLOC_SIZE + * are not inserted in any free list. + */ + if (tmpblock->size >= XV_MIN_ALLOC_SIZE) { + remove_block(pool, pagenum, + offset + block->size + XV_ALIGN, tmpblock, + get_index_for_insert(tmpblock->size)); + } + block->size += tmpblock->size + XV_ALIGN; + } + + /* Merge previous block if its free */ + if (test_flag(block, PREV_FREE)) { + tmpblock = (struct block_header *)((char *)(page) + + get_blockprev(block)); + offset = offset - tmpblock->size - XV_ALIGN; + + if (tmpblock->size >= XV_MIN_ALLOC_SIZE) + remove_block(pool, pagenum, offset, tmpblock, + get_index_for_insert(tmpblock->size)); + + tmpblock->size += block->size + XV_ALIGN; + block = tmpblock; + } + + /* No used objects in this page. Free it. */ + if (block->size == PAGE_SIZE - XV_ALIGN) { + put_ptr_atomic(page, KM_USER0); + spin_unlock(&pool->lock); + + xv_free_page(pagenum); + stat_dec(&pool->total_pages); + return; + } + + set_flag(block, BLOCK_FREE); + if (block->size >= XV_MIN_ALLOC_SIZE) + insert_block(pool, pagenum, offset, block); + + if (offset + block->size + XV_ALIGN != PAGE_SIZE) { + tmpblock = BLOCK_NEXT(block); + set_flag(tmpblock, PREV_FREE); + set_blockprev(tmpblock, offset); + } + + put_ptr_atomic(page, KM_USER0); + spin_unlock(&pool->lock); + + return; +} +EXPORT_SYMBOL_GPL(xv_free); + +u32 xv_get_object_size(void *obj) +{ + struct block_header *blk; + + blk = (struct block_header *)((char *)(obj) - XV_ALIGN); + return blk->size; +} +EXPORT_SYMBOL_GPL(xv_get_object_size); + +/* + * Returns total memory used by allocator (userdata + metadata) + */ +u64 xv_get_total_size_bytes(struct xv_pool *pool) +{ + return pool->total_pages << PAGE_SHIFT; +} +EXPORT_SYMBOL_GPL(xv_get_total_size_bytes); + +static int __init xv_malloc_init(void) +{ + return 0; +} + +static void __exit xv_malloc_exit(void) +{ + return; +} + +module_init(xv_malloc_init); +module_exit(xv_malloc_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nitin Gupta "); +MODULE_DESCRIPTION("xvmalloc memory allocator"); diff --git a/extra/xvmalloc.h b/extra/xvmalloc.h new file mode 100644 index 000000000..699bb0447 --- /dev/null +++ b/extra/xvmalloc.h @@ -0,0 +1,30 @@ +/* + * xvmalloc memory allocator + * + * Copyright (C) 2008, 2009 Nitin Gupta + * + * This code is released using a dual license strategy: BSD/GPL + * You can choose the licence that better fits your requirements. + * + * Released under the terms of 3-clause BSD License + * Released under the terms of GNU General Public License Version 2.0 + */ + +#ifndef _XVMALLOC_H_ +#define _XVMALLOC_H_ + +#include + +struct xv_pool; + +struct xv_pool *xv_create_pool(void); +void xv_destroy_pool(struct xv_pool *pool); + +int xv_malloc(struct xv_pool *pool, u32 size, u32 *pagenum, u32 *offset, + gfp_t flags); +void xv_free(struct xv_pool *pool, u32 pagenum, u32 offset); + +u32 xv_get_object_size(void *obj); +u64 xv_get_total_size_bytes(struct xv_pool *pool); + +#endif diff --git a/extra/xvmalloc_int.h b/extra/xvmalloc_int.h new file mode 100644 index 000000000..4d96c485f --- /dev/null +++ b/extra/xvmalloc_int.h @@ -0,0 +1,86 @@ +/* + * xvmalloc memory allocator + * + * Copyright (C) 2008, 2009 Nitin Gupta + * + * This code is released using a dual license strategy: BSD/GPL + * You can choose the licence that better fits your requirements. + * + * Released under the terms of 3-clause BSD License + * Released under the terms of GNU General Public License Version 2.0 + */ + +#ifndef _XVMALLOC_INT_H_ +#define _XVMALLOC_INT_H_ + +#include +#include + +/* User configurable params */ + +/* This must be greater than sizeof(LinkFree) */ +#define XV_MIN_ALLOC_SIZE 32 +#define XV_MAX_ALLOC_SIZE (PAGE_SIZE - XV_ALIGN) + +/* Must be power of two */ +#define XV_ALIGN_SHIFT 2 +#define XV_ALIGN (1 << XV_ALIGN_SHIFT) +#define XV_ALIGN_MASK (XV_ALIGN - 1) + +/* Free lists are separated by FL_DELTA bytes */ +#define FL_DELTA_SHIFT 3 +#define FL_DELTA (1 << FL_DELTA_SHIFT) +#define FL_DELTA_MASK (FL_DELTA - 1) +#define NUM_FREE_LISTS ((XV_MAX_ALLOC_SIZE - XV_MIN_ALLOC_SIZE) \ + / FL_DELTA + 1) + +#define MAX_FLI DIV_ROUND_UP(NUM_FREE_LISTS, BITS_PER_LONG) + +/* End of user params */ + +enum blockflags { + BLOCK_FREE, + PREV_FREE, + __NR_BLOCKFLAGS, +}; + +#define FLAGS_MASK XV_ALIGN_MASK +#define PREV_MASK (~FLAGS_MASK) + +struct freelist_entry { + u32 pagenum; + u16 offset; + u16 pad; +}; + +struct link_free { + u32 prev_pagenum; + u32 next_pagenum; + u16 prev_offset; + u16 next_offset; +}; + +struct block_header { + union { + /* This common header must be ALIGN bytes */ + u8 common[XV_ALIGN]; + struct { + u16 size; + u16 prev; + }; + }; + struct link_free link; +}; + +struct xv_pool { + ulong flbitmap; + ulong slbitmap[MAX_FLI]; + spinlock_t lock; + + struct freelist_entry freelist[NUM_FREE_LISTS]; + + /* stats */ + u64 total_pages; +}; + +#endif diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index 30441dc86..439f53f23 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -355,6 +355,9 @@ extern struct cpufreq_governor cpufreq_gov_powersave; #elif defined(CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE) extern struct cpufreq_governor cpufreq_gov_userspace; #define CPUFREQ_DEFAULT_GOVERNOR (&cpufreq_gov_userspace) +#elif defined(CONFIG_CPU_FREQ_DEFAULT_GOV_SCREENSTATE) +extern struct cpufreq_governor cpufreq_gov_screenstate; +#define CPUFREQ_DEFAULT_GOVERNOR (&cpufreq_gov_screenstate) #elif defined(CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND) extern struct cpufreq_governor cpufreq_gov_ondemand; #define CPUFREQ_DEFAULT_GOVERNOR (&cpufreq_gov_ondemand) diff --git a/include/linux/kernel.h b/include/linux/kernel.h index 94bc99656..e452dbc7e 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -104,6 +104,7 @@ struct user; * be bitten later when the calling function happens to sleep when it is not * supposed to. */ +#define CONFIG_PREEMPT_VOLUNTARY #ifdef CONFIG_PREEMPT_VOLUNTARY extern int cond_resched(void); # define might_resched() cond_resched() diff --git a/include/linux/swap.h b/include/linux/swap.h index 4f3838adb..0f28f7b77 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -123,6 +123,12 @@ enum { SWP_SCANNING = (1 << 8), /* refcount in scan_swap_map */ }; +enum swap_event { + SWAP_EVENT_SWAPON, + SWAP_EVENT_SWAPOFF, + SWAP_EVENT_SLOT_FREE, +}; + #define SWAP_CLUSTER_MAX 32 #define SWAP_MAP_MAX 0x7fff @@ -138,6 +144,7 @@ struct swap_info_struct { struct block_device *bdev; struct list_head extent_list; struct swap_extent *curr_swap_extent; + struct atomic_notifier_head slot_free_notify_list; unsigned old_block_size; unsigned short * swap_map; unsigned int lowest_bit; @@ -250,6 +257,10 @@ extern sector_t swapdev_block(int, pgoff_t); extern struct swap_info_struct *get_swap_info_struct(unsigned); extern int can_share_swap_page(struct page *); extern int remove_exclusive_swap_page(struct page *); +extern int register_swap_event_notifier(struct notifier_block *nb, + enum swap_event event, unsigned long val); +extern int unregister_swap_event_notifier(struct notifier_block *nb, + enum swap_event event, unsigned long val); struct backing_dev_info; extern spinlock_t swap_lock; diff --git a/mm/swapfile.c b/mm/swapfile.c index d6376ebcb..abe44f6c0 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -47,6 +47,8 @@ struct swap_list_t swap_list = {-1, -1}; static struct swap_info_struct swap_info[MAX_SWAPFILES]; static DEFINE_MUTEX(swapon_mutex); +static BLOCKING_NOTIFIER_HEAD(swapon_notify_list); +static BLOCKING_NOTIFIER_HEAD(swapoff_notify_list); /* * We need this because the bdev->unplug_fn can sleep and we cannot @@ -284,6 +286,9 @@ static int swap_entry_free(struct swap_info_struct *p, unsigned long offset) swap_list.next = p - swap_info; nr_swap_pages++; p->inuse_pages--; + atomic_notifier_call_chain(&p->slot_free_notify_list, + offset, p->swap_file); + } } return count; @@ -1281,6 +1286,7 @@ asmlinkage long sys_swapoff(const char __user * specialfile) p->swap_map = NULL; p->flags = 0; spin_unlock(&swap_lock); + blocking_notifier_call_chain(&swapoff_notify_list, type, swap_file); mutex_unlock(&swapon_mutex); vfree(swap_map); inode = mapping->host; @@ -1661,7 +1667,9 @@ asmlinkage long sys_swapon(const char __user * specialfile, int swap_flags) } else { swap_info[prev].next = p - swap_info; } + ATOMIC_INIT_NOTIFIER_HEAD(&p->slot_free_notify_list); spin_unlock(&swap_lock); + blocking_notifier_call_chain(&swapon_notify_list, type, swap_file); mutex_unlock(&swapon_mutex); error = 0; goto out; @@ -1797,3 +1805,61 @@ int valid_swaphandles(swp_entry_t entry, unsigned long *offset) spin_unlock(&swap_lock); return ret; } + +int register_swap_event_notifier(struct notifier_block *nb, + enum swap_event event, unsigned long val) +{ + switch (event) { + case SWAP_EVENT_SWAPON: + return blocking_notifier_chain_register( + &swapon_notify_list, nb); + case SWAP_EVENT_SWAPOFF: + return blocking_notifier_chain_register( + &swapoff_notify_list, nb); + case SWAP_EVENT_SLOT_FREE: + { + struct swap_info_struct *sis; + + if (val > nr_swapfiles) + goto out; + sis = get_swap_info_struct(val); + return atomic_notifier_chain_register( + &sis->slot_free_notify_list, nb); + } + default: + pr_err("Invalid swap event: %d\n", event); + }; + +out: + return -EINVAL; +} +EXPORT_SYMBOL_GPL(register_swap_event_notifier); + +int unregister_swap_event_notifier(struct notifier_block *nb, + enum swap_event event, unsigned long val) +{ + switch (event) { + case SWAP_EVENT_SWAPON: + return blocking_notifier_chain_unregister( + &swapon_notify_list, nb); + case SWAP_EVENT_SWAPOFF: + return blocking_notifier_chain_unregister( + &swapoff_notify_list, nb); + case SWAP_EVENT_SLOT_FREE: + { + struct swap_info_struct *sis; + + if (val > nr_swapfiles) + goto out; + sis = get_swap_info_struct(val); + return atomic_notifier_chain_unregister( + &sis->slot_free_notify_list, nb); + } + default: + pr_err("Invalid swap event: %d\n", event); + }; + +out: + return -EINVAL; +} +EXPORT_SYMBOL_GPL(unregister_swap_event_notifier);