diff options
Diffstat (limited to 'kernel/pm_qos_params.c')
| -rw-r--r-- | kernel/pm_qos_params.c | 232 | 
1 files changed, 125 insertions, 107 deletions
| diff --git a/kernel/pm_qos_params.c b/kernel/pm_qos_params.c index f42d3f737a3..aeaa7f84682 100644 --- a/kernel/pm_qos_params.c +++ b/kernel/pm_qos_params.c @@ -48,59 +48,49 @@   * or pm_qos_object list and pm_qos_objects need to happen with pm_qos_lock   * held, taken with _irqsave.  One lock to rule them all   */ -struct pm_qos_request_list { -	struct list_head list; -	union { -		s32 value; -		s32 usec; -		s32 kbps; -	}; -	int pm_qos_class; +enum pm_qos_type { +	PM_QOS_MAX,		/* return the largest value */ +	PM_QOS_MIN		/* return the smallest value */  }; -static s32 max_compare(s32 v1, s32 v2); -static s32 min_compare(s32 v1, s32 v2); -  struct pm_qos_object { -	struct pm_qos_request_list requests; +	struct plist_head requests;  	struct blocking_notifier_head *notifiers;  	struct miscdevice pm_qos_power_miscdev;  	char *name;  	s32 default_value; -	atomic_t target_value; -	s32 (*comparitor)(s32, s32); +	enum pm_qos_type type;  }; +static DEFINE_SPINLOCK(pm_qos_lock); +  static struct pm_qos_object null_pm_qos;  static BLOCKING_NOTIFIER_HEAD(cpu_dma_lat_notifier);  static struct pm_qos_object cpu_dma_pm_qos = { -	.requests = {LIST_HEAD_INIT(cpu_dma_pm_qos.requests.list)}, +	.requests = PLIST_HEAD_INIT(cpu_dma_pm_qos.requests, pm_qos_lock),  	.notifiers = &cpu_dma_lat_notifier,  	.name = "cpu_dma_latency",  	.default_value = 2000 * USEC_PER_SEC, -	.target_value = ATOMIC_INIT(2000 * USEC_PER_SEC), -	.comparitor = min_compare +	.type = PM_QOS_MIN,  };  static BLOCKING_NOTIFIER_HEAD(network_lat_notifier);  static struct pm_qos_object network_lat_pm_qos = { -	.requests = {LIST_HEAD_INIT(network_lat_pm_qos.requests.list)}, +	.requests = PLIST_HEAD_INIT(network_lat_pm_qos.requests, pm_qos_lock),  	.notifiers = &network_lat_notifier,  	.name = "network_latency",  	.default_value = 2000 * USEC_PER_SEC, -	.target_value = ATOMIC_INIT(2000 * USEC_PER_SEC), -	.comparitor = min_compare +	.type = PM_QOS_MIN  };  static BLOCKING_NOTIFIER_HEAD(network_throughput_notifier);  static struct pm_qos_object network_throughput_pm_qos = { -	.requests = {LIST_HEAD_INIT(network_throughput_pm_qos.requests.list)}, +	.requests = PLIST_HEAD_INIT(network_throughput_pm_qos.requests, pm_qos_lock),  	.notifiers = &network_throughput_notifier,  	.name = "network_throughput",  	.default_value = 0, -	.target_value = ATOMIC_INIT(0), -	.comparitor = max_compare +	.type = PM_QOS_MAX,  }; @@ -111,8 +101,6 @@ static struct pm_qos_object *pm_qos_array[] = {  	&network_throughput_pm_qos  }; -static DEFINE_SPINLOCK(pm_qos_lock); -  static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf,  		size_t count, loff_t *f_pos);  static int pm_qos_power_open(struct inode *inode, struct file *filp); @@ -122,48 +110,58 @@ static const struct file_operations pm_qos_power_fops = {  	.write = pm_qos_power_write,  	.open = pm_qos_power_open,  	.release = pm_qos_power_release, +	.llseek = noop_llseek,  }; -/* static helper functions */ -static s32 max_compare(s32 v1, s32 v2) +/* unlocked internal variant */ +static inline int pm_qos_get_value(struct pm_qos_object *o)  { -	return max(v1, v2); -} +	if (plist_head_empty(&o->requests)) +		return o->default_value; -static s32 min_compare(s32 v1, s32 v2) -{ -	return min(v1, v2); -} +	switch (o->type) { +	case PM_QOS_MIN: +		return plist_first(&o->requests)->prio; + +	case PM_QOS_MAX: +		return plist_last(&o->requests)->prio; +	default: +		/* runtime check for not using enum */ +		BUG(); +	} +} -static void update_target(int pm_qos_class) +static void update_target(struct pm_qos_object *o, struct plist_node *node, +			  int del, int value)  { -	s32 extreme_value; -	struct pm_qos_request_list *node;  	unsigned long flags; -	int call_notifier = 0; +	int prev_value, curr_value;  	spin_lock_irqsave(&pm_qos_lock, flags); -	extreme_value = pm_qos_array[pm_qos_class]->default_value; -	list_for_each_entry(node, -			&pm_qos_array[pm_qos_class]->requests.list, list) { -		extreme_value = pm_qos_array[pm_qos_class]->comparitor( -				extreme_value, node->value); -	} -	if (atomic_read(&pm_qos_array[pm_qos_class]->target_value) != -			extreme_value) { -		call_notifier = 1; -		atomic_set(&pm_qos_array[pm_qos_class]->target_value, -				extreme_value); -		pr_debug(KERN_ERR "new target for qos %d is %d\n", pm_qos_class, -			atomic_read(&pm_qos_array[pm_qos_class]->target_value)); +	prev_value = pm_qos_get_value(o); +	/* PM_QOS_DEFAULT_VALUE is a signal that the value is unchanged */ +	if (value != PM_QOS_DEFAULT_VALUE) { +		/* +		 * to change the list, we atomically remove, reinit +		 * with new value and add, then see if the extremal +		 * changed +		 */ +		plist_del(node, &o->requests); +		plist_node_init(node, value); +		plist_add(node, &o->requests); +	} else if (del) { +		plist_del(node, &o->requests); +	} else { +		plist_add(node, &o->requests);  	} +	curr_value = pm_qos_get_value(o);  	spin_unlock_irqrestore(&pm_qos_lock, flags); -	if (call_notifier) -		blocking_notifier_call_chain( -				pm_qos_array[pm_qos_class]->notifiers, -					(unsigned long) extreme_value, NULL); +	if (prev_value != curr_value) +		blocking_notifier_call_chain(o->notifiers, +					     (unsigned long)curr_value, +					     NULL);  }  static int register_pm_qos_misc(struct pm_qos_object *qos) @@ -196,42 +194,53 @@ static int find_pm_qos_object_by_minor(int minor)   */  int pm_qos_request(int pm_qos_class)  { -	return atomic_read(&pm_qos_array[pm_qos_class]->target_value); +	unsigned long flags; +	int value; + +	spin_lock_irqsave(&pm_qos_lock, flags); +	value = pm_qos_get_value(pm_qos_array[pm_qos_class]); +	spin_unlock_irqrestore(&pm_qos_lock, flags); + +	return value;  }  EXPORT_SYMBOL_GPL(pm_qos_request); +int pm_qos_request_active(struct pm_qos_request_list *req) +{ +	return req->pm_qos_class != 0; +} +EXPORT_SYMBOL_GPL(pm_qos_request_active); +  /**   * pm_qos_add_request - inserts new qos request into the list - * @pm_qos_class: identifies which list of qos request to us + * @dep: pointer to a preallocated handle + * @pm_qos_class: identifies which list of qos request to use   * @value: defines the qos request   *   * This function inserts a new entry in the pm_qos_class list of requested qos   * performance characteristics.  It recomputes the aggregate QoS expectations - * for the pm_qos_class of parameters, and returns the pm_qos_request list - * element as a handle for use in updating and removal.  Call needs to save - * this handle for later use. + * for the pm_qos_class of parameters and initializes the pm_qos_request_list + * handle.  Caller needs to save this handle for later use in updates and + * removal.   */ -struct pm_qos_request_list *pm_qos_add_request(int pm_qos_class, s32 value) + +void pm_qos_add_request(struct pm_qos_request_list *dep, +			int pm_qos_class, s32 value)  { -	struct pm_qos_request_list *dep; -	unsigned long flags; +	struct pm_qos_object *o =  pm_qos_array[pm_qos_class]; +	int new_value; -	dep = kzalloc(sizeof(struct pm_qos_request_list), GFP_KERNEL); -	if (dep) { -		if (value == PM_QOS_DEFAULT_VALUE) -			dep->value = pm_qos_array[pm_qos_class]->default_value; -		else -			dep->value = value; -		dep->pm_qos_class = pm_qos_class; - -		spin_lock_irqsave(&pm_qos_lock, flags); -		list_add(&dep->list, -			&pm_qos_array[pm_qos_class]->requests.list); -		spin_unlock_irqrestore(&pm_qos_lock, flags); -		update_target(pm_qos_class); +	if (pm_qos_request_active(dep)) { +		WARN(1, KERN_ERR "pm_qos_add_request() called for already added request\n"); +		return;  	} - -	return dep; +	if (value == PM_QOS_DEFAULT_VALUE) +		new_value = o->default_value; +	else +		new_value = value; +	plist_node_init(&dep->list, new_value); +	dep->pm_qos_class = pm_qos_class; +	update_target(o, &dep->list, 0, PM_QOS_DEFAULT_VALUE);  }  EXPORT_SYMBOL_GPL(pm_qos_add_request); @@ -246,27 +255,28 @@ EXPORT_SYMBOL_GPL(pm_qos_add_request);   * Attempts are made to make this code callable on hot code paths.   */  void pm_qos_update_request(struct pm_qos_request_list *pm_qos_req, -		s32 new_value) +			   s32 new_value)  { -	unsigned long flags; -	int pending_update = 0;  	s32 temp; +	struct pm_qos_object *o; + +	if (!pm_qos_req) /*guard against callers passing in null */ +		return; -	if (pm_qos_req) { /*guard against callers passing in null */ -		spin_lock_irqsave(&pm_qos_lock, flags); -		if (new_value == PM_QOS_DEFAULT_VALUE) -			temp = pm_qos_array[pm_qos_req->pm_qos_class]->default_value; -		else -			temp = new_value; - -		if (temp != pm_qos_req->value) { -			pending_update = 1; -			pm_qos_req->value = temp; -		} -		spin_unlock_irqrestore(&pm_qos_lock, flags); -		if (pending_update) -			update_target(pm_qos_req->pm_qos_class); +	if (!pm_qos_request_active(pm_qos_req)) { +		WARN(1, KERN_ERR "pm_qos_update_request() called for unknown object\n"); +		return;  	} + +	o = pm_qos_array[pm_qos_req->pm_qos_class]; + +	if (new_value == PM_QOS_DEFAULT_VALUE) +		temp = o->default_value; +	else +		temp = new_value; + +	if (temp != pm_qos_req->list.prio) +		update_target(o, &pm_qos_req->list, 0, temp);  }  EXPORT_SYMBOL_GPL(pm_qos_update_request); @@ -280,19 +290,20 @@ EXPORT_SYMBOL_GPL(pm_qos_update_request);   */  void pm_qos_remove_request(struct pm_qos_request_list *pm_qos_req)  { -	unsigned long flags; -	int qos_class; +	struct pm_qos_object *o;  	if (pm_qos_req == NULL)  		return;  		/* silent return to keep pcm code cleaner */ -	qos_class = pm_qos_req->pm_qos_class; -	spin_lock_irqsave(&pm_qos_lock, flags); -	list_del(&pm_qos_req->list); -	kfree(pm_qos_req); -	spin_unlock_irqrestore(&pm_qos_lock, flags); -	update_target(qos_class); +	if (!pm_qos_request_active(pm_qos_req)) { +		WARN(1, KERN_ERR "pm_qos_remove_request() called for unknown object\n"); +		return; +	} + +	o = pm_qos_array[pm_qos_req->pm_qos_class]; +	update_target(o, &pm_qos_req->list, 1, PM_QOS_DEFAULT_VALUE); +	memset(pm_qos_req, 0, sizeof(*pm_qos_req));  }  EXPORT_SYMBOL_GPL(pm_qos_remove_request); @@ -340,8 +351,12 @@ static int pm_qos_power_open(struct inode *inode, struct file *filp)  	pm_qos_class = find_pm_qos_object_by_minor(iminor(inode));  	if (pm_qos_class >= 0) { -		filp->private_data = (void *) pm_qos_add_request(pm_qos_class, -				PM_QOS_DEFAULT_VALUE); +               struct pm_qos_request_list *req = kzalloc(sizeof(*req), GFP_KERNEL); +		if (!req) +			return -ENOMEM; + +		pm_qos_add_request(req, pm_qos_class, PM_QOS_DEFAULT_VALUE); +		filp->private_data = req;  		if (filp->private_data)  			return 0; @@ -353,8 +368,9 @@ static int pm_qos_power_release(struct inode *inode, struct file *filp)  {  	struct pm_qos_request_list *req; -	req = (struct pm_qos_request_list *)filp->private_data; +	req = filp->private_data;  	pm_qos_remove_request(req); +	kfree(req);  	return 0;  } @@ -374,14 +390,16 @@ static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf,  	} else if (count == 11) { /* len('0x12345678/0') */  		if (copy_from_user(ascii_value, buf, 11))  			return -EFAULT; +		if (strlen(ascii_value) != 10) +			return -EINVAL;  		x = sscanf(ascii_value, "%x", &value);  		if (x != 1)  			return -EINVAL; -		pr_debug(KERN_ERR "%s, %d, 0x%x\n", ascii_value, x, value); +		pr_debug("%s, %d, 0x%x\n", ascii_value, x, value);  	} else  		return -EINVAL; -	pm_qos_req = (struct pm_qos_request_list *)filp->private_data; +	pm_qos_req = filp->private_data;  	pm_qos_update_request(pm_qos_req, value);  	return count; | 
