Commit e9b92d00 authored by Dominik Brodowski's avatar Dominik Brodowski Committed by Jeff Garzik

[PATCH] (1/5) CPUfreq core

CPUFreq core for 2.5.39
include/linux/cpufreq.h		CPUFreq header
kernel/Makefile			add cpufreq.c if necessary
kernel/cpufreq.c		CPUFreq core
parent 18e131d1
/*
* linux/include/linux/cpufreq.h
*
* Copyright (C) 2001 Russell King
* (C) 2002 Dominik Brodowski <linux@brodo.de>
*
*
* $Id: cpufreq.h,v 1.26 2002/09/21 09:05:29 db Exp $
*
* 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.
*/
#ifndef _LINUX_CPUFREQ_H
#define _LINUX_CPUFREQ_H
#include <linux/config.h>
#include <linux/notifier.h>
#include <linux/threads.h>
/*********************************************************************
* CPUFREQ NOTIFIER INTERFACE *
*********************************************************************/
int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list);
int cpufreq_unregister_notifier(struct notifier_block *nb, unsigned int list);
#define CPUFREQ_TRANSITION_NOTIFIER (0)
#define CPUFREQ_POLICY_NOTIFIER (1)
#define CPUFREQ_ALL_CPUS ((NR_CPUS))
/********************** cpufreq policy notifiers *********************/
#define CPUFREQ_POLICY_POWERSAVE (1)
#define CPUFREQ_POLICY_PERFORMANCE (2)
/* values here are CPU kHz so that hardware which doesn't run with some
* frequencies can complain without having to guess what per cent / per
* mille means. */
struct cpufreq_policy {
unsigned int cpu; /* cpu nr or CPUFREQ_ALL_CPUS */
unsigned int min; /* in kHz */
unsigned int max; /* in kHz */
unsigned int policy; /* see above */
unsigned int max_cpu_freq; /* for information */
};
#define CPUFREQ_ADJUST (0)
#define CPUFREQ_INCOMPATIBLE (1)
#define CPUFREQ_NOTIFY (2)
/******************** cpufreq transition notifiers *******************/
#define CPUFREQ_PRECHANGE (0)
#define CPUFREQ_POSTCHANGE (1)
struct cpufreq_freqs {
unsigned int cpu; /* cpu nr or CPUFREQ_ALL_CPUS */
unsigned int old;
unsigned int new;
};
/**
* cpufreq_scale - "old * mult / div" calculation for large values (32-bit-arch safe)
* @old: old value
* @div: divisor
* @mult: multiplier
*
* Needed for loops_per_jiffy and similar calculations. We do it
* this way to avoid math overflow on 32-bit machines. This will
* become architecture dependent once high-resolution-timer is
* merged (or any other thing that introduces sc_math.h).
*
* new = old * mult / div
*/
static inline unsigned long cpufreq_scale(unsigned long old, u_int div, u_int mult)
{
unsigned long val, carry;
mult /= 100;
div /= 100;
val = (old / div) * mult;
carry = old % div;
carry = carry * mult / div;
return carry + val;
};
/*********************************************************************
* DYNAMIC CPUFREQ INTERFACE *
*********************************************************************/
#ifdef CONFIG_CPU_FREQ_DYNAMIC
/* TBD */
#endif /* CONFIG_CPU_FREQ_DYNAMIC */
/*********************************************************************
* CPUFREQ DRIVER INTERFACE *
*********************************************************************/
typedef void (*cpufreq_policy_t) (struct cpufreq_policy *policy);
struct cpufreq_driver {
/* needed by all drivers */
cpufreq_policy_t verify;
cpufreq_policy_t setpolicy;
struct cpufreq_policy *policy;
#ifdef CONFIG_CPU_FREQ_DYNAMIC
/* TBD */
#endif
/* 2.4. compatible API */
#ifdef CONFIG_CPU_FREQ_24_API
unsigned int cpu_min_freq;
unsigned int cpu_cur_freq[NR_CPUS];
#endif
};
int cpufreq_register(struct cpufreq_driver *driver_data);
int cpufreq_unregister(void);
void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state);
static inline void cpufreq_verify_within_limits(struct cpufreq_policy *policy, unsigned int min, unsigned int max)
{
if (policy->min < min)
policy->min = min;
if (policy->max < min)
policy->max = min;
if (policy->min > max)
policy->min = max;
if (policy->max > max)
policy->max = max;
if (policy->min > policy->max)
policy->min = policy->max;
return;
}
/*********************************************************************
* CPUFREQ 2.6. INTERFACE *
*********************************************************************/
int cpufreq_set_policy(struct cpufreq_policy *policy);
int cpufreq_get_policy(struct cpufreq_policy *policy, unsigned int cpu);
#ifdef CONFIG_CPU_FREQ_26_API
#ifdef CONFIG_PM
int cpufreq_restore(void);
#endif
#endif
#endif /* _LINUX_CPUFREQ_H */
......@@ -3,7 +3,7 @@
#
export-objs = signal.o sys.o kmod.o context.o ksyms.o pm.o exec_domain.o \
printk.o platform.o suspend.o dma.o module.o
printk.o platform.o suspend.o dma.o module.o cpufreq.o
obj-y = sched.o fork.o exec_domain.o panic.o printk.o \
module.o exit.o itimer.o time.o softirq.o resource.o \
......@@ -16,6 +16,7 @@ obj-$(CONFIG_UID16) += uid16.o
obj-$(CONFIG_MODULES) += ksyms.o
obj-$(CONFIG_KALLSYMS) += kallsyms.o
obj-$(CONFIG_PM) += pm.o
obj-$(CONFIG_CPU_FREQ) += cpufreq.o
obj-$(CONFIG_BSD_PROCESS_ACCT) += acct.o
obj-$(CONFIG_SOFTWARE_SUSPEND) += suspend.o
......
/*
* linux/kernel/cpufreq.c
*
* Copyright (C) 2001 Russell King
* (C) 2002 Dominik Brodowski <linux@brodo.de>
*
* $Id: cpufreq.c,v 1.43 2002/09/21 09:05:29 db Exp $
*
* 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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/notifier.h>
#include <linux/cpufreq.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/ctype.h>
#include <asm/uaccess.h>
#ifdef CONFIG_CPU_FREQ_26_API
#include <linux/proc_fs.h>
#endif
/**
* The "cpufreq driver" - the arch- or hardware-dependend low
* level driver of CPUFreq support, and its locking mutex.
* cpu_max_freq is in kHz.
*/
static struct cpufreq_driver *cpufreq_driver;
static DECLARE_MUTEX (cpufreq_driver_sem);
/**
* Two notifier lists: the "policy" list is involved in the
* validation process for a new CPU frequency policy; the
* "transition" list for kernel code that needs to handle
* changes to devices when the CPU clock speed changes.
* The mutex locks both lists. If both cpufreq_driver_sem
* and cpufreq_notifier_sem need to be hold, get cpufreq_driver_sem
* first.
*/
static struct notifier_block *cpufreq_policy_notifier_list;
static struct notifier_block *cpufreq_transition_notifier_list;
static DECLARE_MUTEX (cpufreq_notifier_sem);
/**
* The cpufreq default policy. Can be set by a "cpufreq=..." command
* line option.
*/
static struct cpufreq_policy default_policy = {
.cpu = CPUFREQ_ALL_CPUS,
.min = 0,
.max = 0,
.policy = 0,
};
/*********************************************************************
* 2.6. API *
*********************************************************************/
/**
* cpufreq_parse_policy - parse a policy string
* @input_string: the string to parse.
* @policy: the policy written inside input_string
*
* This function parses a "policy string" - something the user echo'es into
* /proc/cpufreq or gives as boot parameter - into a struct cpufreq_policy.
* If there are invalid/missing entries, they are replaced with current
* cpufreq policy.
*/
static int cpufreq_parse_policy(char input_string[42], struct cpufreq_policy *policy)
{
unsigned int min = 0;
unsigned int max = 0;
unsigned int cpu = 0;
char policy_string[42] = {'\0'};
struct cpufreq_policy current_policy;
unsigned int result = -EFAULT;
unsigned int i = 0;
if (!policy)
return -EINVAL;
policy->min = 0;
policy->max = 0;
policy->policy = 0;
policy->cpu = CPUFREQ_ALL_CPUS;
if (sscanf(input_string, "%d:%d:%d:%s", &cpu, &min, &max, policy_string) == 4)
{
policy->min = min;
policy->max = max;
policy->cpu = cpu;
result = 0;
goto scan_policy;
}
if (sscanf(input_string, "%d%%%d%%%d%%%s", &cpu, &min, &max, policy_string) == 4)
{
if (!cpufreq_get_policy(&current_policy, cpu)) {
policy->min = (min * current_policy.max_cpu_freq) / 100;
policy->max = (max * current_policy.max_cpu_freq) / 100;
policy->cpu = cpu;
result = 0;
goto scan_policy;
}
}
if (sscanf(input_string, "%d:%d:%s", &min, &max, policy_string) == 3)
{
policy->min = min;
policy->max = max;
result = 0;
goto scan_policy;
}
if (sscanf(input_string, "%d%%%d%%%s", &min, &max, policy_string) == 3)
{
if (!cpufreq_get_policy(&current_policy, cpu)) {
policy->min = (min * current_policy.max_cpu_freq) / 100;
policy->max = (max * current_policy.max_cpu_freq) / 100;
result = 0;
goto scan_policy;
}
}
return -EINVAL;
scan_policy:
for (i=0;i<sizeof(policy_string);i++){
if (policy_string[i]=='\0')
break;
policy_string[i] = tolower(policy_string[i]);
}
if (!strncmp(policy_string, "powersave", 6) ||
!strncmp(policy_string, "eco", 3) ||
!strncmp(policy_string, "batter", 6) ||
!strncmp(policy_string, "low", 3))
{
result = 0;
policy->policy = CPUFREQ_POLICY_POWERSAVE;
}
else if (!strncmp(policy_string, "performance",6) ||
!strncmp(policy_string, "high",4) ||
!strncmp(policy_string, "full",4))
{
result = 0;
policy->policy = CPUFREQ_POLICY_PERFORMANCE;
}
else if (!cpufreq_get_policy(&current_policy, policy->cpu))
{
policy->policy = current_policy.policy;
}
else
{
policy->policy = 0;
}
return result;
}
/*
* cpufreq command line parameter. Must be hard values (kHz)
* cpufreq=1000000:2000000:PERFORMANCE
* to set the default CPUFreq policy.
*/
static int __init cpufreq_setup(char *str)
{
cpufreq_parse_policy(str, &default_policy);
default_policy.cpu = CPUFREQ_ALL_CPUS;
return 1;
}
__setup("cpufreq=", cpufreq_setup);
#ifdef CONFIG_CPU_FREQ_26_API
#ifdef CONFIG_PROC_FS
/**
* cpufreq_proc_read - read /proc/cpufreq
*
* This function prints out the current cpufreq policy.
*/
static int cpufreq_proc_read (
char *page,
char **start,
off_t off,
int count,
int *eof,
void *data)
{
char *p = page;
int len = 0;
struct cpufreq_policy policy;
unsigned int min_pctg = 0;
unsigned int max_pctg = 0;
unsigned int i = 0;
if (off != 0)
goto end;
p += sprintf(p, " minimum CPU frequency - maximum CPU frequency - policy\n");
for (i=0;i<NR_CPUS;i++) {
if (!cpu_online(i))
continue;
cpufreq_get_policy(&policy, i);
min_pctg = (policy.min * 100) / policy.max_cpu_freq;
max_pctg = (policy.max * 100) / policy.max_cpu_freq;
p += sprintf(p, "CPU%3d %9d kHz (%3d %%) - %9d kHz (%3d %%) - ",
i , policy.min, min_pctg, policy.max, max_pctg);
switch (policy.policy) {
case CPUFREQ_POLICY_POWERSAVE:
p += sprintf(p, "powersave\n");
break;
case CPUFREQ_POLICY_PERFORMANCE:
p += sprintf(p, "performance\n");
break;
default:
p += sprintf(p, "INVALID\n");
break;
}
}
end:
len = (p - page);
if (len <= off+count)
*eof = 1;
*start = page + off;
len -= off;
if (len>count)
len = count;
if (len<0)
len = 0;
return len;
}
/**
* cpufreq_proc_write - handles writing into /proc/cpufreq
*
* This function calls the parsing script and then sets the policy
* accordingly.
*/
static int cpufreq_proc_write (
struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
int result = 0;
char proc_string[42] = {'\0'};
struct cpufreq_policy policy;
if ((count > sizeof(proc_string) - 1))
return -EINVAL;
if (copy_from_user(proc_string, buffer, count))
return -EFAULT;
proc_string[count] = '\0';
result = cpufreq_parse_policy(proc_string, &policy);
if (result)
return -EFAULT;
cpufreq_set_policy(&policy);
return count;
}
/**
* cpufreq_proc_init - add "cpufreq" to the /proc root directory
*
* This function adds "cpufreq" to the /proc root directory.
*/
static unsigned int cpufreq_proc_init (void)
{
struct proc_dir_entry *entry = NULL;
/* are these acceptable values? */
entry = create_proc_entry("cpufreq", S_IFREG|S_IRUGO|S_IWUSR,
&proc_root);
if (!entry) {
printk(KERN_ERR "unable to create /proc/cpufreq entry\n");
return -EIO;
} else {
entry->read_proc = cpufreq_proc_read;
entry->write_proc = cpufreq_proc_write;
}
return 0;
}
/**
* cpufreq_proc_exit - removes "cpufreq" from the /proc root directory.
*
* This function removes "cpufreq" from the /proc root directory.
*/
static void cpufreq_proc_exit (void)
{
remove_proc_entry("cpufreq", &proc_root);
return;
}
#endif /* CONFIG_PROC_FS */
#endif /* CONFIG_CPU_FREQ_26_API */
/*********************************************************************
* NOTIFIER LISTS INTERFACE *
*********************************************************************/
/**
* cpufreq_register_notifier - register a driver with cpufreq
* @nb: notifier function to register
* @list: CPUFREQ_TRANSITION_NOTIFIER or CPUFREQ_POLICY_NOTIFIER
*
* Add a driver to one of two lists: either a list of drivers that
* are notified about clock rate changes (once before and once after
* the transition), or a list of drivers that are notified about
* changes in cpufreq policy.
*
* This function may sleep, and has the same return conditions as
* notifier_chain_register.
*/
int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list)
{
int ret;
down(&cpufreq_notifier_sem);
switch (list) {
case CPUFREQ_TRANSITION_NOTIFIER:
ret = notifier_chain_register(&cpufreq_transition_notifier_list, nb);
break;
case CPUFREQ_POLICY_NOTIFIER:
ret = notifier_chain_register(&cpufreq_policy_notifier_list, nb);
break;
default:
ret = -EINVAL;
}
up(&cpufreq_notifier_sem);
return ret;
}
EXPORT_SYMBOL(cpufreq_register_notifier);
/**
* cpufreq_unregister_notifier - unregister a driver with cpufreq
* @nb: notifier block to be unregistered
* @list: CPUFREQ_TRANSITION_NOTIFIER or CPUFREQ_POLICY_NOTIFIER
*
* Remove a driver from the CPU frequency notifier list.
*
* This function may sleep, and has the same return conditions as
* notifier_chain_unregister.
*/
int cpufreq_unregister_notifier(struct notifier_block *nb, unsigned int list)
{
int ret;
down(&cpufreq_notifier_sem);
switch (list) {
case CPUFREQ_TRANSITION_NOTIFIER:
ret = notifier_chain_unregister(&cpufreq_transition_notifier_list, nb);
break;
case CPUFREQ_POLICY_NOTIFIER:
ret = notifier_chain_unregister(&cpufreq_policy_notifier_list, nb);
break;
default:
ret = -EINVAL;
}
up(&cpufreq_notifier_sem);
return ret;
}
EXPORT_SYMBOL(cpufreq_unregister_notifier);
/*********************************************************************
* POLICY INTERFACE *
*********************************************************************/
/**
* cpufreq_get_policy - get the current cpufreq_policy
* @policy: struct cpufreq_policy into which the current cpufreq_policy is written
*
* Reads the current cpufreq policy.
*/
int cpufreq_get_policy(struct cpufreq_policy *policy, unsigned int cpu)
{
down(&cpufreq_driver_sem);
if (!cpufreq_driver || !policy ||
(cpu >= NR_CPUS) || (!cpu_online(cpu))) {
up(&cpufreq_driver_sem);
return -EINVAL;
}
policy->min = cpufreq_driver->policy[cpu].min;
policy->max = cpufreq_driver->policy[cpu].max;
policy->policy = cpufreq_driver->policy[cpu].policy;
policy->max_cpu_freq = cpufreq_driver->policy[0].max_cpu_freq;
policy->cpu = cpu;
up(&cpufreq_driver_sem);
return 0;
}
/**
* cpufreq_set_policy - set a new CPUFreq policy
* @policy: policy to be set.
*
* Sets a new CPU frequency and voltage scaling policy.
*/
int cpufreq_set_policy(struct cpufreq_policy *policy)
{
unsigned int i;
down(&cpufreq_driver_sem);
if (!cpufreq_driver || !cpufreq_driver->verify ||
!cpufreq_driver->setpolicy || !policy ||
(policy->cpu > NR_CPUS)) {
up(&cpufreq_driver_sem);
return -EINVAL;
}
down(&cpufreq_notifier_sem);
policy->max_cpu_freq = cpufreq_driver->policy[0].max_cpu_freq;
/* verify the cpu speed can be set within this limit */
cpufreq_driver->verify(policy);
/* adjust if neccessary - all reasons */
notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_ADJUST,
policy);
/* adjust if neccessary - hardware incompatibility*/
notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_INCOMPATIBLE,
policy);
/* verify the cpu speed can be set within this limit,
which might be different to the first one */
cpufreq_driver->verify(policy);
/* notification of the new policy */
notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_NOTIFY,
policy);
up(&cpufreq_notifier_sem);
if (policy->cpu == CPUFREQ_ALL_CPUS) {
for (i=0;i<NR_CPUS;i++) {
cpufreq_driver->policy[i].min = policy->min;
cpufreq_driver->policy[i].max = policy->max;
cpufreq_driver->policy[i].policy = policy->policy;
}
} else {
cpufreq_driver->policy[policy->cpu].min = policy->min;
cpufreq_driver->policy[policy->cpu].max = policy->max;
cpufreq_driver->policy[policy->cpu].policy = policy->policy;
}
cpufreq_driver->setpolicy(policy);
up(&cpufreq_driver_sem);
return 0;
}
EXPORT_SYMBOL(cpufreq_set_policy);
/*********************************************************************
* DYNAMIC CPUFREQ SWITCHING *
*********************************************************************/
#ifdef CONFIG_CPU_FREQ_DYNAMIC
/* TBD */
#endif /* CONFIG_CPU_FREQ_DYNAMIC */
/*********************************************************************
* EXTERNALLY AFFECTING FREQUENCY CHANGES *
*********************************************************************/
/**
* adjust_jiffies - adjust the system "loops_per_jiffy"
*
* This function alters the system "loops_per_jiffy" for the clock
* speed change. Note that loops_per_jiffy is only updated if all
* CPUs are affected - else there is a need for per-CPU loops_per_jiffy
* values which are provided by various architectures.
*/
static inline void adjust_jiffies(unsigned long val, struct cpufreq_freqs *ci)
{
if ((val == CPUFREQ_PRECHANGE && ci->old < ci->new) ||
(val == CPUFREQ_POSTCHANGE && ci->old > ci->new))
if (ci->cpu == CPUFREQ_ALL_CPUS)
loops_per_jiffy = cpufreq_scale(loops_per_jiffy, ci->old, ci->new);
}
/**
* cpufreq_notify_transition - call notifier chain and adjust_jiffies on frequency transition
*
* This function calls the transition notifiers and the "adjust_jiffies" function. It is called
* twice on all CPU frequency changes that have external effects.
*/
void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state)
{
down(&cpufreq_notifier_sem);
switch (state) {
case CPUFREQ_PRECHANGE:
notifier_call_chain(&cpufreq_transition_notifier_list, CPUFREQ_PRECHANGE, freqs);
adjust_jiffies(CPUFREQ_PRECHANGE, freqs);
break;
case CPUFREQ_POSTCHANGE:
adjust_jiffies(CPUFREQ_POSTCHANGE, freqs);
notifier_call_chain(&cpufreq_transition_notifier_list, CPUFREQ_POSTCHANGE, freqs);
break;
}
up(&cpufreq_notifier_sem);
}
EXPORT_SYMBOL_GPL(cpufreq_notify_transition);
/*********************************************************************
* REGISTER / UNREGISTER CPUFREQ DRIVER *
*********************************************************************/
/**
* cpufreq_register - register a CPU Frequency driver
* @driver_data: A struct cpufreq_driver containing the values submitted by the CPU Frequency driver.
*
* Registers a CPU Frequency driver to this core code. This code
* returns zero on success, -EBUSY when another driver got here first
* (and isn't unregistered in the meantime).
*
*/
int cpufreq_register(struct cpufreq_driver *driver_data)
{
unsigned int ret;
if (cpufreq_driver)
return -EBUSY;
if (!driver_data || !driver_data->verify ||
!driver_data->setpolicy)
return -EINVAL;
down(&cpufreq_driver_sem);
cpufreq_driver = driver_data;
if (!default_policy.policy)
default_policy.policy = driver_data->policy[0].policy;
if (!default_policy.min)
default_policy.min = driver_data->policy[0].min;
if (!default_policy.max)
default_policy.max = driver_data->policy[0].max;
default_policy.cpu = CPUFREQ_ALL_CPUS;
up(&cpufreq_driver_sem);
ret = cpufreq_set_policy(&default_policy);
#ifdef CONFIG_CPU_FREQ_26_API
cpufreq_proc_init();
#endif
if (ret) {
down(&cpufreq_driver_sem);
cpufreq_driver = NULL;
up(&cpufreq_driver_sem);
}
return ret;
}
EXPORT_SYMBOL_GPL(cpufreq_register);
/**
* cpufreq_unregister - unregister the current CPUFreq driver
*
* Unregister the current CPUFreq driver. Only call this if you have
* the right to do so, i.e. if you have succeeded in initialising before!
* Returns zero if successful, and -EINVAL if the cpufreq_driver is
* currently not initialised.
*/
int cpufreq_unregister(void)
{
down(&cpufreq_driver_sem);
if (!cpufreq_driver) {
up(&cpufreq_driver_sem);
return -EINVAL;
}
cpufreq_driver = NULL;
up(&cpufreq_driver_sem);
#ifdef CONFIG_CPU_FREQ_26_API
cpufreq_proc_exit();
#endif
return 0;
}
EXPORT_SYMBOL_GPL(cpufreq_unregister);
#ifdef CONFIG_PM
/**
* cpufreq_restore - restore the CPU clock frequency after resume
*
* Restore the CPU clock frequency so that our idea of the current
* frequency reflects the actual hardware.
*/
int cpufreq_restore(void)
{
struct cpufreq_policy policy;
unsigned int i;
if (in_interrupt())
panic("cpufreq_restore() called from interrupt context!");
for (i=0;i<NR_CPUS;i++) {
if (!cpu_online(i))
continue;
down(&cpufreq_driver_sem);
if (!cpufreq_driver) {
up(&cpufreq_driver_sem);
return 0;
}
policy.min = cpufreq_driver->policy[i].min;
policy.max = cpufreq_driver->policy[i].max;
policy.policy = cpufreq_driver->policy[i].policy;
policy.cpu = i;
up(&cpufreq_driver_sem);
#ifdef CONFIG_CPU_FREQ_26_API
cpufreq_set_policy(&policy);
#endif
}
return 0;
}
EXPORT_SYMBOL_GPL(cpufreq_restore);
#else
#define cpufreq_restore()
#endif /* CONFIG_PM */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment