[PATCH v4] power: introduce library for device-specific OPPs
Nishanth Menon
nm at ti.com
Mon Sep 27 10:29:05 EDT 2010
Paul E. McKenney had written, on 09/24/2010 04:40 PM, the following:
[...]
>>>> +/**
>>>> + * opp_find_freq_ceil() - Search for an rounded ceil freq
>>>> + * @dev: device for which we do this operation
>>>> + * @freq: Start frequency
>>>> + *
>>>> + * Search for the matching ceil *available* OPP from a starting freq
>>>> + * for a device.
>>>> + *
>>>> + * Returns matching *opp and refreshes *freq accordingly, else returns
>>>> + * ERR_PTR in case of error and should be handled using IS_ERR.
>>>> + *
>>>> + * Locking: RCU reader.
>>>> + */
>>>> +struct opp *opp_find_freq_ceil(struct device *dev, unsigned long *freq)
>>>> +{
>>>> + struct device_opp *dev_opp;
>>>> + struct opp *temp_opp, *opp = ERR_PTR(-ENODEV);
>>>> +
>>>> + if (!dev || !freq) {
>>>> + pr_err("%s: invalid param dev=%p freq=%p\n", __func__,
>>>> + dev, freq);
>>>> + return ERR_PTR(-EINVAL);
>>>> + }
>>>> +
>>>> + dev_opp = find_device_opp(dev);
>>>> + if (IS_ERR(dev_opp))
>>>> + return opp;
>>>> +
>>>> + rcu_read_lock();
>>>> + list_for_each_entry_rcu(temp_opp, &dev_opp->opp_list, node) {
>>>> + if (temp_opp->available && temp_opp->rate >= *freq) {
>>>> + opp = temp_opp;
>>>> + *freq = opp->rate;
>>>> + break;
>>>> + }
>>>> + }
>>>> + rcu_read_unlock();
>>> And this one also has the same problem that find_device_opp() does.
>> guessing opp ptr here.. am I right? if it is about device_opp, it is
>> not going to be freed as I mentioned above - at least we dont give
>> an function to update(hence free) it.
>
> It really does look to me that you are passing a pointer to the thing
> being freed out of an RCU read-side critical section. If you are really
> doing this, you do need to do something to fix it. I outlined some of
> the options earlier.
ack. will try to fix in v5.
>
>>>> + return opp;
>>>> +}
>>>> +
>>>> +/**
>>>> + * opp_find_freq_floor() - Search for a rounded floor freq
>>>> + * @dev: device for which we do this operation
>>>> + * @freq: Start frequency
>>>> + *
>>>> + * Search for the matching floor *available* OPP from a starting freq
>>>> + * for a device.
>>>> + *
>>>> + * Returns matching *opp and refreshes *freq accordingly, else returns
>>>> + * ERR_PTR in case of error and should be handled using IS_ERR.
>>>> + *
>>>> + * Locking: RCU reader.
>>>> + */
>>>> +struct opp *opp_find_freq_floor(struct device *dev, unsigned long *freq)
>>>> +{
>>>> + struct device_opp *dev_opp;
>>>> + struct opp *temp_opp, *opp = ERR_PTR(-ENODEV);
>>>> +
>>>> + if (!dev || !freq) {
>>>> + pr_err("%s: invalid param dev=%p freq=%p\n", __func__,
>>>> + dev, freq);
>>>> + return ERR_PTR(-EINVAL);
>>>> + }
>>>> +
>>>> + dev_opp = find_device_opp(dev);
>>>> + if (IS_ERR(dev_opp))
>>>> + return opp;
>>>> +
>>>> + rcu_read_lock();
>>>> + list_for_each_entry_rcu(temp_opp, &dev_opp->opp_list, node) {
>>>> + if (temp_opp->available) {
>>>> + /* go to the next node, before choosing prev */
>>>> + if (temp_opp->rate > *freq)
>>>> + break;
>>>> + else
>>>> + opp = temp_opp;
>>>> + }
>>>> + }
>>>> + if (!IS_ERR(opp))
>>>> + *freq = opp->rate;
>>>> + rcu_read_unlock();
>>> As does this one.
>> guessing opp ptr here.. am I right?
>
> Again, here it looks to me like you are passing a pointer out of an RCU
> read-side critical section that could be freed out from under you. If
> so, again, this must be fixed.
>
[...]
>>>> +static int opp_set_availability(struct opp *opp, bool availability_req)
>>>> +{
>>>> + struct opp *new_opp, *tmp_opp;
>>>> + bool is_available;
>>>> +
>>>> + if (unlikely(!opp || IS_ERR(opp))) {
>>>> + pr_err("%s: Invalid parameters being passed\n", __func__);
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + new_opp = kmalloc(sizeof(struct opp), GFP_KERNEL);
>>>> + if (!new_opp) {
>>>> + pr_warning("%s: unable to allocate opp\n", __func__);
>>>> + return -ENOMEM;
>>>> + }
>>>> +
>>>> + mutex_lock(&opp->dev_opp->lock);
>>>> +
>>>> + rcu_read_lock();
>>>> + tmp_opp = rcu_dereference(opp);
>>>> + is_available = tmp_opp->available;
>>>> + rcu_read_unlock();
>>>> +
>>>> + /* Is update really needed? */
>>>> + if (is_available == availability_req) {
>>>> + mutex_unlock(&opp->dev_opp->lock);
>>>> + kfree(tmp_opp);
>>>> + return 0;
>>>> + }
>>>> +
>>>> + *new_opp = *opp;
>>>> + new_opp->available = availability_req;
>>>> + list_replace_rcu(&opp->node, &new_opp->node);
>>>> +
>>>> + mutex_unlock(&opp->dev_opp->lock);
>>>> + synchronize_rcu();
>>> If you decide to rely on reference counts to fix the problem in
>>> find_device_opp(), you will need to check the reference counts here.
>>> Again, please see Documentation/RCU/rcuref.txt.
>> Does the original point about not needing to free dev_opp resolve this?
>
> For the dev_opp case, yes. However, I believe that my point is still
> valid for the opp case.
Ack. I missed that :(.. let me relook at the logic yet again. hopefully
fix in v5.
>
>>>> + kfree(opp);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +/**
>>>> + * opp_enable() - Enable a specific OPP
>>>> + * @opp: Pointer to opp
>>>> + *
>>>> + * Enables a provided opp. If the operation is valid, this returns 0, else the
>>>> + * corresponding error value. It is meant to be used for users an OPP available
>>>> + * after being temporarily made unavailable with opp_disable.
>>>> + *
>>>> + * Locking: RCU, mutex
>>> By "Locking: RCU", you presumably don't mean that the caller must do
>>> an rcu_read_lock() -- this would result in a synchronize_rcu() being
>>> invoked in an RCU read-side critical section, which is illegal.
>> aye..thx. I will make it more verbose. Does the following sound right?
>>
>> Locking used internally: RCU copy-update and read_lock used, mutex
>>
>> and for the readers:
>>
>> Locking used internally: RCU read_lock used
>>
>> or do we need to go all verbatim about the implementation here?
>>
>> I intended the user to know the context in which they can call it,
>> for example, since mutex is used, dont think of using this in
>> interrupt context. since read_locks are already used, dont need to
>> double lock it.. opp library takes care of it's own exclusivity.
>
> I would stick to the constraints on the caller, and describe the internals
> elsewhere, for example, near the data-structure definitions. But tastes
> do vary on this.
okay. let me see how to clean this up.
[..]
>>>> +void opp_init_cpufreq_table(struct device *dev,
>>>> + struct cpufreq_frequency_table **table)
>>>> +{
>>>> + struct device_opp *dev_opp;
>>>> + struct opp *opp;
>>>> + struct cpufreq_frequency_table *freq_table;
>>>> + int i = 0;
>>>> +
>>>> + dev_opp = find_device_opp(dev);
>>>> + if (IS_ERR(dev_opp)) {
>>>> + pr_warning("%s: unable to find device\n", __func__);
>>>> + return;
>>>> + }
>>>> +
>>>> + freq_table = kzalloc(sizeof(struct cpufreq_frequency_table) *
>>>> + (opp_get_opp_count(dev) + 1), GFP_ATOMIC);
>>>> + if (!freq_table) {
>>>> + pr_warning("%s: failed to allocate frequency table\n",
>>>> + __func__);
>>>> + return;
>>> How does the caller tell that the allocation failed? Should the caller
>>> set the pointer passed in through the "table" argument to NULL before
>>> calling this function? Or should this function set *table to NULL
>>> before returning in this case?
>> Good catch. Thanks. I would rather change the return to int and pass
>> proper errors to caller so that they can handle it appropriately.
>
> Works for me!
thx.
--
Regards,
Nishanth Menon
More information about the linux-arm-kernel
mailing list