diff mbox series

[v6,5/5] drm/xe/hwmon: Expose power1_max_interval

Message ID 20230925081842.3566834-6-badal.nilawar@intel.com (mailing list archive)
State Handled Elsewhere
Headers show
Series Add HWMON support for DGFX | expand

Commit Message

Nilawar, Badal Sept. 25, 2023, 8:18 a.m. UTC
Expose power1_max_interval, that is the tau corresponding to PL1, as a
custom hwmon attribute. Some bit manipulation is needed because of the
format of PKG_PWR_LIM_1_TIME in
PACKAGE_RAPL_LIMIT register (1.x * power(2,y))

v2: Get rpm wake ref while accessing power1_max_interval
v3: %s/hwmon/xe_hwmon/

Acked-by: Rodrigo Vivi <rodrigo.vivi@intel.com>
Reviewed-by: Himal Prasad Ghimiray <himal.prasad.ghimiray@intel.com>
Signed-off-by: Badal Nilawar <badal.nilawar@intel.com>
---
 .../ABI/testing/sysfs-driver-intel-xe-hwmon   |  11 ++
 drivers/gpu/drm/xe/regs/xe_mchbar_regs.h      |   8 +
 drivers/gpu/drm/xe/xe_hwmon.c                 | 138 +++++++++++++++++-
 3 files changed, 156 insertions(+), 1 deletion(-)

Comments

Andi Shyti Sept. 25, 2023, 11:56 a.m. UTC | #1
Hi Badal,

[...]

> +static ssize_t
> +xe_hwmon_power1_max_interval_store(struct device *dev, struct device_attribute *attr,
> +				   const char *buf, size_t count)
> +{
> +	struct xe_hwmon *hwmon = dev_get_drvdata(dev);
> +	u32 x, y, rxy, x_w = 2; /* 2 bits */
> +	u64 tau4, r, max_win;
> +	unsigned long val;
> +	int ret;
> +
> +	ret = kstrtoul(buf, 0, &val);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Max HW supported tau in '1.x * power(2,y)' format, x = 0, y = 0x12
> +	 * The hwmon->scl_shift_time default of 0xa results in a max tau of 256 seconds
> +	 */
> +#define PKG_MAX_WIN_DEFAULT 0x12ull
> +
> +	/*
> +	 * val must be < max in hwmon interface units. The steps below are
> +	 * explained in xe_hwmon_power1_max_interval_show()
> +	 */
> +	r = FIELD_PREP(PKG_MAX_WIN, PKG_MAX_WIN_DEFAULT);
> +	x = REG_FIELD_GET(PKG_MAX_WIN_X, r);
> +	y = REG_FIELD_GET(PKG_MAX_WIN_Y, r);
> +	tau4 = ((1 << x_w) | x) << y;
> +	max_win = mul_u64_u32_shr(tau4, SF_TIME, hwmon->scl_shift_time + x_w);
> +
> +	if (val > max_win)
> +		return -EINVAL;
> +
> +	/* val in hw units */
> +	val = DIV_ROUND_CLOSEST_ULL((u64)val << hwmon->scl_shift_time, SF_TIME);
> +	/* Convert to 1.x * power(2,y) */
> +	if (!val) {
> +		/* Avoid ilog2(0) */
> +		y = 0;
> +		x = 0;
> +	} else {
> +		y = ilog2(val);
> +		/* x = (val - (1 << y)) >> (y - 2); */

this is some spurious development comment, can you please remove
it?

> +		x = (val - (1ul << y)) << x_w >> y;
> +	}
> +
> +	rxy = REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_X, x) | REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_Y, y);
> +
> +	xe_device_mem_access_get(gt_to_xe(hwmon->gt));
> +
> +	mutex_lock(&hwmon->hwmon_lock);
> +
> +	xe_hwmon_process_reg(hwmon, REG_PKG_RAPL_LIMIT, REG_RMW, (u32 *)&r,
> +			     PKG_PWR_LIM_1_TIME, rxy);
> +
> +	mutex_unlock(&hwmon->hwmon_lock);

why are we locking here?

Andi

> +
> +	xe_device_mem_access_put(gt_to_xe(hwmon->gt));
> +
> +	return count;
> +}
Andi Shyti Sept. 26, 2023, 8:01 a.m. UTC | #2
Hi Badal,

> > > +	/* val in hw units */
> > > +	val = DIV_ROUND_CLOSEST_ULL((u64)val << hwmon->scl_shift_time, SF_TIME);
> > > +	/* Convert to 1.x * power(2,y) */
> > > +	if (!val) {
> > > +		/* Avoid ilog2(0) */
> > > +		y = 0;
> > > +		x = 0;
> > > +	} else {
> > > +		y = ilog2(val);
> > > +		/* x = (val - (1 << y)) >> (y - 2); */
> > 
> > this is some spurious development comment, can you please remove
> > it?
> 
> This is kept intentionally to help to understand the calculations.

then this is confusing... Can you please expand the concept?
As it is it's not understandable and I would expect someone
sending a patch with title:

 [PATCH] drm/xe/hwmon: Remove spurious comment

Because it just looks forgotten from previous development.

> > > +		x = (val - (1ul << y)) << x_w >> y;
> > > +	}
> > > +
> > > +	rxy = REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_X, x) | REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_Y, y);
> > > +
> > > +	xe_device_mem_access_get(gt_to_xe(hwmon->gt));
> > > +
> > > +	mutex_lock(&hwmon->hwmon_lock);
> > > +
> > > +	xe_hwmon_process_reg(hwmon, REG_PKG_RAPL_LIMIT, REG_RMW, (u32 *)&r,
> > > +			     PKG_PWR_LIM_1_TIME, rxy);
> > > +
> > > +	mutex_unlock(&hwmon->hwmon_lock);
> > 
> > why are we locking here?
> 
> Since it is rmw operation we are using lock here.

OK... so what you are trying to protect here is the

  read -> update -> write

and it makes sense. The problem is that if this is a generic
rule, which means that everyone who will do a rmw operation has
to take the lock, why not take the lock directly in
xe_hwmon_process_reg()?

But also this can be a bit confusing, because a function is
either locked or unlocked and purists might complain.

A suggestion would be to do something like:

   static int xe_hwmon_process_reg(..., enum xe_hwmon_reg_operation operation)
   {
   	...
   }

   static int xe_hwmon_reg_read(...);
   {
   	return xe_hwmon_process_reg(..., REG_READ);
   }

   static int xe_hwmon_reg_write(...);
   {
   	return xe_hwmon_process_reg(..., REG_WRITE);
   }

   static int xe_hwmon_reg_rmw(...);
   {
	int ret;
   	
	/*
	 * Optional: you can check that the lock is not taken
	 * to shout loud if potential deadlocks arise.
	 */

	/*
	 * We want to protect the register update with the
	 * lock blah blah blah... explanatory comment.
	 */
	mutex_lock(&hwmon->hwmon_lock);
	ret = xe_hwmon_process_reg(..., REG_RMW);
	mutex_unlock(&hwmon->hwmon_lock);

	return ret;
   }

What do you think? It looks much clearer to me.

Andi
Nilawar, Badal Sept. 26, 2023, 9 a.m. UTC | #3
Hi Andi,

On 26-09-2023 13:31, Andi Shyti wrote:
> Hi Badal,
> 
>>>> +	/* val in hw units */
>>>> +	val = DIV_ROUND_CLOSEST_ULL((u64)val << hwmon->scl_shift_time, SF_TIME);
>>>> +	/* Convert to 1.x * power(2,y) */
>>>> +	if (!val) {
>>>> +		/* Avoid ilog2(0) */
>>>> +		y = 0;
>>>> +		x = 0;
>>>> +	} else {
>>>> +		y = ilog2(val);
>>>> +		/* x = (val - (1 << y)) >> (y - 2); */
>>>
>>> this is some spurious development comment, can you please remove
>>> it?
>>
>> This is kept intentionally to help to understand the calculations.
> 
> then this is confusing... Can you please expand the concept?
> As it is it's not understandable and I would expect someone
> sending a patch with title:
> 
>   [PATCH] drm/xe/hwmon: Remove spurious comment
> 
> Because it just looks forgotten from previous development.
I will add this comment inside the comment at the top of if. So it will 
look like.
/*
  * Convert to 1.x * power(2,y)
  * y = ilog(val);
  * x = (val - (1 << y)) >> (y-2);
  */
> 
>>>> +		x = (val - (1ul << y)) << x_w >> y;
>>>> +	}
>>>> +
>>>> +	rxy = REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_X, x) | REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_Y, y);
>>>> +
>>>> +	xe_device_mem_access_get(gt_to_xe(hwmon->gt));
>>>> +
>>>> +	mutex_lock(&hwmon->hwmon_lock);
>>>> +
>>>> +	xe_hwmon_process_reg(hwmon, REG_PKG_RAPL_LIMIT, REG_RMW, (u32 *)&r,
>>>> +			     PKG_PWR_LIM_1_TIME, rxy);
>>>> +
>>>> +	mutex_unlock(&hwmon->hwmon_lock);
>>>
>>> why are we locking here?
>>
>> Since it is rmw operation we are using lock here.
> 
> OK... so what you are trying to protect here is the
> 
>    read -> update -> write
> 
> and it makes sense. The problem is that if this is a generic
> rule, which means that everyone who will do a rmw operation has
> to take the lock, why not take the lock directly in
> xe_hwmon_process_reg()?
> 
> But also this can be a bit confusing, because a function is
> either locked or unlocked and purists might complain.
> 
> A suggestion would be to do something like:
> 
>     static int xe_hwmon_process_reg(..., enum xe_hwmon_reg_operation operation)
>     {
>     	...
>     }
> 
>     static int xe_hwmon_reg_read(...);
>     {
>     	return xe_hwmon_process_reg(..., REG_READ);
>     }
> 
>     static int xe_hwmon_reg_write(...);
>     {
>     	return xe_hwmon_process_reg(..., REG_WRITE);
>     }
> 
>     static int xe_hwmon_reg_rmw(...);
>     {
> 	int ret;
>     	
> 	/*
> 	 * Optional: you can check that the lock is not taken
> 	 * to shout loud if potential deadlocks arise.
> 	 */
> 
> 	/*
> 	 * We want to protect the register update with the
> 	 * lock blah blah blah... explanatory comment.
> 	 */
> 	mutex_lock(&hwmon->hwmon_lock);
> 	ret = xe_hwmon_process_reg(..., REG_RMW);
> 	mutex_unlock(&hwmon->hwmon_lock);
> 
> 	return ret;
>     }
> 
> What do you think? It looks much clearer to me.
REG_PKG_RAPL_LIMIT register is being written in xe_hwmon_power_max_write 
also, that's why lock is taken. But some how while cleaning up I forgot 
to take it in xe_hwmon_power_max_write(), thanks for catching it up. I 
will update xe_hwmon_power_max_write() and resend series.

Thanks,
Badal
> 
> Andi
Andi Shyti Sept. 26, 2023, 9:01 p.m. UTC | #4
Hi Badal,

> > > > > +	/* val in hw units */
> > > > > +	val = DIV_ROUND_CLOSEST_ULL((u64)val << hwmon->scl_shift_time, SF_TIME);
> > > > > +	/* Convert to 1.x * power(2,y) */
> > > > > +	if (!val) {
> > > > > +		/* Avoid ilog2(0) */
> > > > > +		y = 0;
> > > > > +		x = 0;
> > > > > +	} else {
> > > > > +		y = ilog2(val);
> > > > > +		/* x = (val - (1 << y)) >> (y - 2); */
> > > > 
> > > > this is some spurious development comment, can you please remove
> > > > it?
> > > 
> > > This is kept intentionally to help to understand the calculations.
> > 
> > then this is confusing... Can you please expand the concept?
> > As it is it's not understandable and I would expect someone
> > sending a patch with title:
> > 
> >   [PATCH] drm/xe/hwmon: Remove spurious comment
> > 
> > Because it just looks forgotten from previous development.
> I will add this comment inside the comment at the top of if. So it will look
> like.
> /*
>  * Convert to 1.x * power(2,y)
>  * y = ilog(val);
>  * x = (val - (1 << y)) >> (y-2);
>  */

All right.

> > > > > +		x = (val - (1ul << y)) << x_w >> y;
> > > > > +	}
> > > > > +
> > > > > +	rxy = REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_X, x) | REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_Y, y);
> > > > > +
> > > > > +	xe_device_mem_access_get(gt_to_xe(hwmon->gt));
> > > > > +
> > > > > +	mutex_lock(&hwmon->hwmon_lock);
> > > > > +
> > > > > +	xe_hwmon_process_reg(hwmon, REG_PKG_RAPL_LIMIT, REG_RMW, (u32 *)&r,
> > > > > +			     PKG_PWR_LIM_1_TIME, rxy);
> > > > > +
> > > > > +	mutex_unlock(&hwmon->hwmon_lock);
> > > > 
> > > > why are we locking here?
> > > 
> > > Since it is rmw operation we are using lock here.
> > 
> > OK... so what you are trying to protect here is the
> > 
> >    read -> update -> write
> > 
> > and it makes sense. The problem is that if this is a generic
> > rule, which means that everyone who will do a rmw operation has
> > to take the lock, why not take the lock directly in
> > xe_hwmon_process_reg()?
> > 
> > But also this can be a bit confusing, because a function is
> > either locked or unlocked and purists might complain.
> > 
> > A suggestion would be to do something like:
> > 
> >     static int xe_hwmon_process_reg(..., enum xe_hwmon_reg_operation operation)
> >     {
> >     	...
> >     }
> > 
> >     static int xe_hwmon_reg_read(...);
> >     {
> >     	return xe_hwmon_process_reg(..., REG_READ);
> >     }
> > 
> >     static int xe_hwmon_reg_write(...);
> >     {
> >     	return xe_hwmon_process_reg(..., REG_WRITE);
> >     }
> > 
> >     static int xe_hwmon_reg_rmw(...);
> >     {
> > 	int ret;
> >     	
> > 	/*
> > 	 * Optional: you can check that the lock is not taken
> > 	 * to shout loud if potential deadlocks arise.
> > 	 */
> > 
> > 	/*
> > 	 * We want to protect the register update with the
> > 	 * lock blah blah blah... explanatory comment.
> > 	 */
> > 	mutex_lock(&hwmon->hwmon_lock);
> > 	ret = xe_hwmon_process_reg(..., REG_RMW);
> > 	mutex_unlock(&hwmon->hwmon_lock);
> > 
> > 	return ret;
> >     }
> > 
> > What do you think? It looks much clearer to me.
> 
> REG_PKG_RAPL_LIMIT register is being written in xe_hwmon_power_max_write
> also, that's why lock is taken. But some how while cleaning up I forgot to
> take it in xe_hwmon_power_max_write(), thanks for catching it up. I will
> update xe_hwmon_power_max_write() and resend series.

yes... OK... then, please add the lock also in the write case.

But still... thinking of hwmon running in more threads don't you
think we might need a more generic locking? This might mean to
lock all around xe_hwmon_process_reg()... maybe it's an overkill.

For the time being I'm OK with your current solution.

If you don't like my suggestion above, feel free to ignore it.

Andi
Dixit, Ashutosh Sept. 27, 2023, 3:32 a.m. UTC | #5
On Tue, 26 Sep 2023 14:01:06 -0700, Andi Shyti wrote:
>

Hi Badal/Andi,

>
> > > > > > +	/* val in hw units */
> > > > > > +	val = DIV_ROUND_CLOSEST_ULL((u64)val << hwmon->scl_shift_time, SF_TIME);
> > > > > > +	/* Convert to 1.x * power(2,y) */
> > > > > > +	if (!val) {
> > > > > > +		/* Avoid ilog2(0) */
> > > > > > +		y = 0;
> > > > > > +		x = 0;
> > > > > > +	} else {
> > > > > > +		y = ilog2(val);
> > > > > > +		/* x = (val - (1 << y)) >> (y - 2); */
> > > > >
> > > > > this is some spurious development comment, can you please remove
> > > > > it?
> > > >
> > > > This is kept intentionally to help to understand the calculations.
> > >
> > > then this is confusing... Can you please expand the concept?
> > > As it is it's not understandable and I would expect someone
> > > sending a patch with title:
> > >
> > >   [PATCH] drm/xe/hwmon: Remove spurious comment
> > >
> > > Because it just looks forgotten from previous development.
> > I will add this comment inside the comment at the top of if. So it will look
> > like.
> > /*
> >  * Convert to 1.x * power(2,y)
> >  * y = ilog(val);
> >  * x = (val - (1 << y)) >> (y-2);
> >  */
>
> All right.

That comment is explaining that one line of code. I think we should just
leave it where it is, it doesn't make sense to move it above the if. How
else can we write it so that the comment doesn't look like a leftover?

If the code is clear we can remove the comment, but I think the code is
hard to understand. So try to understand the code and then you will need
the comment.

>
> > > > > > +		x = (val - (1ul << y)) << x_w >> y;
> > > > > > +	}
> > > > > > +
> > > > > > +	rxy = REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_X, x) | REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_Y, y);
> > > > > > +
> > > > > > +	xe_device_mem_access_get(gt_to_xe(hwmon->gt));
> > > > > > +
> > > > > > +	mutex_lock(&hwmon->hwmon_lock);
> > > > > > +
> > > > > > +	xe_hwmon_process_reg(hwmon, REG_PKG_RAPL_LIMIT, REG_RMW, (u32 *)&r,
> > > > > > +			     PKG_PWR_LIM_1_TIME, rxy);
> > > > > > +
> > > > > > +	mutex_unlock(&hwmon->hwmon_lock);
> > > > >
> > > > > why are we locking here?
> > > >
> > > > Since it is rmw operation we are using lock here.
> > >
> > > OK... so what you are trying to protect here is the
> > >
> > >    read -> update -> write
> > >
> > > and it makes sense. The problem is that if this is a generic
> > > rule, which means that everyone who will do a rmw operation has
> > > to take the lock, why not take the lock directly in
> > > xe_hwmon_process_reg()?
> > >
> > > But also this can be a bit confusing, because a function is
> > > either locked or unlocked and purists might complain.
> > >
> > > A suggestion would be to do something like:
> > >
> > >     static int xe_hwmon_process_reg(..., enum xe_hwmon_reg_operation operation)
> > >     {
> > >		...
> > >     }
> > >
> > >     static int xe_hwmon_reg_read(...);
> > >     {
> > >		return xe_hwmon_process_reg(..., REG_READ);
> > >     }
> > >
> > >     static int xe_hwmon_reg_write(...);
> > >     {
> > >		return xe_hwmon_process_reg(..., REG_WRITE);
> > >     }
> > >
> > >     static int xe_hwmon_reg_rmw(...);
> > >     {
> > >	int ret;
> > >
> > >	/*
> > >	 * Optional: you can check that the lock is not taken
> > >	 * to shout loud if potential deadlocks arise.
> > >	 */
> > >
> > >	/*
> > >	 * We want to protect the register update with the
> > >	 * lock blah blah blah... explanatory comment.
> > >	 */
> > >	mutex_lock(&hwmon->hwmon_lock);
> > >	ret = xe_hwmon_process_reg(..., REG_RMW);
> > >	mutex_unlock(&hwmon->hwmon_lock);
> > >
> > >	return ret;
> > >     }
> > >
> > > What do you think? It looks much clearer to me.
> >
> > REG_PKG_RAPL_LIMIT register is being written in xe_hwmon_power_max_write
> > also, that's why lock is taken. But some how while cleaning up I forgot to
> > take it in xe_hwmon_power_max_write(), thanks for catching it up. I will
> > update xe_hwmon_power_max_write() and resend series.
>
> yes... OK... then, please add the lock also in the write case.
>
> But still... thinking of hwmon running in more threads don't you
> think we might need a more generic locking? This might mean to
> lock all around xe_hwmon_process_reg()... maybe it's an overkill.
>
> For the time being I'm OK with your current solution.
>
> If you don't like my suggestion above, feel free to ignore it.

Yeah agree, might as well take the lock around the switch statement in
xe_hwmon_process_reg().

>
> Andi

Thanks.
--
Ashutosh
Nilawar, Badal Sept. 27, 2023, 9:04 a.m. UTC | #6
Hi Ashutosh,

On 27-09-2023 09:02, Dixit, Ashutosh wrote:
> On Tue, 26 Sep 2023 14:01:06 -0700, Andi Shyti wrote:
>>
> 
> Hi Badal/Andi,
> 
>>
>>>>>>> +	/* val in hw units */
>>>>>>> +	val = DIV_ROUND_CLOSEST_ULL((u64)val << hwmon->scl_shift_time, SF_TIME);
>>>>>>> +	/* Convert to 1.x * power(2,y) */
>>>>>>> +	if (!val) {
>>>>>>> +		/* Avoid ilog2(0) */
>>>>>>> +		y = 0;
>>>>>>> +		x = 0;
>>>>>>> +	} else {
>>>>>>> +		y = ilog2(val);
>>>>>>> +		/* x = (val - (1 << y)) >> (y - 2); */
>>>>>>
>>>>>> this is some spurious development comment, can you please remove
>>>>>> it?
>>>>>
>>>>> This is kept intentionally to help to understand the calculations.
>>>>
>>>> then this is confusing... Can you please expand the concept?
>>>> As it is it's not understandable and I would expect someone
>>>> sending a patch with title:
>>>>
>>>>    [PATCH] drm/xe/hwmon: Remove spurious comment
>>>>
>>>> Because it just looks forgotten from previous development.
>>> I will add this comment inside the comment at the top of if. So it will look
>>> like.
>>> /*
>>>   * Convert to 1.x * power(2,y)
>>>   * y = ilog(val);
>>>   * x = (val - (1 << y)) >> (y-2);
>>>   */
>>
>> All right.
> 
> That comment is explaining that one line of code. I think we should just
> leave it where it is, it doesn't make sense to move it above the if. How
> else can we write it so that the comment doesn't look like a leftover?
> 
> If the code is clear we can remove the comment, but I think the code is
> hard to understand. So try to understand the code and then you will need
> the comment.
Agreed, I will keep this comment as it is.
> 
>>
>>>>>>> +		x = (val - (1ul << y)) << x_w >> y;
>>>>>>> +	}
>>>>>>> +
>>>>>>> +	rxy = REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_X, x) | REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_Y, y);
>>>>>>> +
>>>>>>> +	xe_device_mem_access_get(gt_to_xe(hwmon->gt));
>>>>>>> +
>>>>>>> +	mutex_lock(&hwmon->hwmon_lock);
>>>>>>> +
>>>>>>> +	xe_hwmon_process_reg(hwmon, REG_PKG_RAPL_LIMIT, REG_RMW, (u32 *)&r,
>>>>>>> +			     PKG_PWR_LIM_1_TIME, rxy);
>>>>>>> +
>>>>>>> +	mutex_unlock(&hwmon->hwmon_lock);
>>>>>>
>>>>>> why are we locking here?
>>>>>
>>>>> Since it is rmw operation we are using lock here.
>>>>
>>>> OK... so what you are trying to protect here is the
>>>>
>>>>     read -> update -> write
>>>>
>>>> and it makes sense. The problem is that if this is a generic
>>>> rule, which means that everyone who will do a rmw operation has
>>>> to take the lock, why not take the lock directly in
>>>> xe_hwmon_process_reg()?
>>>>
>>>> But also this can be a bit confusing, because a function is
>>>> either locked or unlocked and purists might complain.
>>>>
>>>> A suggestion would be to do something like:
>>>>
>>>>      static int xe_hwmon_process_reg(..., enum xe_hwmon_reg_operation operation)
>>>>      {
>>>> 		...
>>>>      }
>>>>
>>>>      static int xe_hwmon_reg_read(...);
>>>>      {
>>>> 		return xe_hwmon_process_reg(..., REG_READ);
>>>>      }
>>>>
>>>>      static int xe_hwmon_reg_write(...);
>>>>      {
>>>> 		return xe_hwmon_process_reg(..., REG_WRITE);
>>>>      }
>>>>
>>>>      static int xe_hwmon_reg_rmw(...);
>>>>      {
>>>> 	int ret;
>>>>
>>>> 	/*
>>>> 	 * Optional: you can check that the lock is not taken
>>>> 	 * to shout loud if potential deadlocks arise.
>>>> 	 */
>>>>
>>>> 	/*
>>>> 	 * We want to protect the register update with the
>>>> 	 * lock blah blah blah... explanatory comment.
>>>> 	 */
>>>> 	mutex_lock(&hwmon->hwmon_lock);
>>>> 	ret = xe_hwmon_process_reg(..., REG_RMW);
>>>> 	mutex_unlock(&hwmon->hwmon_lock);
>>>>
>>>> 	return ret;
>>>>      }
>>>>
>>>> What do you think? It looks much clearer to me.
>>>
>>> REG_PKG_RAPL_LIMIT register is being written in xe_hwmon_power_max_write
>>> also, that's why lock is taken. But some how while cleaning up I forgot to
>>> take it in xe_hwmon_power_max_write(), thanks for catching it up. I will
>>> update xe_hwmon_power_max_write() and resend series.
>>
>> yes... OK... then, please add the lock also in the write case.
>>
>> But still... thinking of hwmon running in more threads don't you
>> think we might need a more generic locking? This might mean to
>> lock all around xe_hwmon_process_reg()... maybe it's an overkill.
>>
>> For the time being I'm OK with your current solution.
>>
>> If you don't like my suggestion above, feel free to ignore it.
> 
> Yeah agree, might as well take the lock around the switch statement in
> xe_hwmon_process_reg().
Will there be a possibility where two different threads will access 
power attributes power1_max and power1_max_interval simultaneously and 
frequently. I am not able to think such scenario. If not then we can 
remove lock from here.

Regards.
Badal
> 
>>
>> Andi
> 
> Thanks.
> --
> Ashutosh
Gupta, Anshuman Sept. 27, 2023, 9:31 a.m. UTC | #7
> -----Original Message-----
> From: Nilawar, Badal <badal.nilawar@intel.com>
> Sent: Wednesday, September 27, 2023 2:35 PM
> To: Dixit, Ashutosh <ashutosh.dixit@intel.com>; Andi Shyti
> <andi.shyti@linux.intel.com>
> Cc: intel-xe@lists.freedesktop.org; linux-hwmon@vger.kernel.org; Gupta,
> Anshuman <anshuman.gupta@intel.com>; linux@roeck-us.net; Tauro, Riana
> <riana.tauro@intel.com>; Brost, Matthew <matthew.brost@intel.com>; Vivi,
> Rodrigo <rodrigo.vivi@intel.com>
> Subject: Re: [PATCH v6 5/5] drm/xe/hwmon: Expose power1_max_interval
> 
> Hi Ashutosh,
> 
> On 27-09-2023 09:02, Dixit, Ashutosh wrote:
> > On Tue, 26 Sep 2023 14:01:06 -0700, Andi Shyti wrote:
> >>
> >
> > Hi Badal/Andi,
> >
> >>
> >>>>>>> +	/* val in hw units */
> >>>>>>> +	val = DIV_ROUND_CLOSEST_ULL((u64)val << hwmon-
> >scl_shift_time, SF_TIME);
> >>>>>>> +	/* Convert to 1.x * power(2,y) */
> >>>>>>> +	if (!val) {
> >>>>>>> +		/* Avoid ilog2(0) */
> >>>>>>> +		y = 0;
> >>>>>>> +		x = 0;
> >>>>>>> +	} else {
> >>>>>>> +		y = ilog2(val);
> >>>>>>> +		/* x = (val - (1 << y)) >> (y - 2); */
> >>>>>>
> >>>>>> this is some spurious development comment, can you please remove
> >>>>>> it?
> >>>>>
> >>>>> This is kept intentionally to help to understand the calculations.
> >>>>
> >>>> then this is confusing... Can you please expand the concept?
> >>>> As it is it's not understandable and I would expect someone sending
> >>>> a patch with title:
> >>>>
> >>>>    [PATCH] drm/xe/hwmon: Remove spurious comment
> >>>>
> >>>> Because it just looks forgotten from previous development.
> >>> I will add this comment inside the comment at the top of if. So it
> >>> will look like.
> >>> /*
> >>>   * Convert to 1.x * power(2,y)
> >>>   * y = ilog(val);
> >>>   * x = (val - (1 << y)) >> (y-2);
> >>>   */
> >>
> >> All right.
> >
> > That comment is explaining that one line of code. I think we should
> > just leave it where it is, it doesn't make sense to move it above the
> > if. How else can we write it so that the comment doesn't look like a leftover?
> >
> > If the code is clear we can remove the comment, but I think the code
> > is hard to understand. So try to understand the code and then you will
> > need the comment.
> Agreed, I will keep this comment as it is.
> >
> >>
> >>>>>>> +		x = (val - (1ul << y)) << x_w >> y;
> >>>>>>> +	}
> >>>>>>> +
> >>>>>>> +	rxy = REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_X, x) |
> >>>>>>> +REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_Y, y);
> >>>>>>> +
> >>>>>>> +	xe_device_mem_access_get(gt_to_xe(hwmon->gt));
> >>>>>>> +
> >>>>>>> +	mutex_lock(&hwmon->hwmon_lock);
> >>>>>>> +
> >>>>>>> +	xe_hwmon_process_reg(hwmon, REG_PKG_RAPL_LIMIT,
> REG_RMW, (u32 *)&r,
> >>>>>>> +			     PKG_PWR_LIM_1_TIME, rxy);
> >>>>>>> +
> >>>>>>> +	mutex_unlock(&hwmon->hwmon_lock);
> >>>>>>
> >>>>>> why are we locking here?
> >>>>>
> >>>>> Since it is rmw operation we are using lock here.
> >>>>
> >>>> OK... so what you are trying to protect here is the
> >>>>
> >>>>     read -> update -> write
> >>>>
> >>>> and it makes sense. The problem is that if this is a generic rule,
> >>>> which means that everyone who will do a rmw operation has to take
> >>>> the lock, why not take the lock directly in xe_hwmon_process_reg()?
> >>>>
> >>>> But also this can be a bit confusing, because a function is either
> >>>> locked or unlocked and purists might complain.
> >>>>
> >>>> A suggestion would be to do something like:
> >>>>
> >>>>      static int xe_hwmon_process_reg(..., enum xe_hwmon_reg_operation
> operation)
> >>>>      {
> >>>> 		...
> >>>>      }
> >>>>
> >>>>      static int xe_hwmon_reg_read(...);
> >>>>      {
> >>>> 		return xe_hwmon_process_reg(..., REG_READ);
> >>>>      }
> >>>>
> >>>>      static int xe_hwmon_reg_write(...);
> >>>>      {
> >>>> 		return xe_hwmon_process_reg(..., REG_WRITE);
> >>>>      }
> >>>>
> >>>>      static int xe_hwmon_reg_rmw(...);
> >>>>      {
> >>>> 	int ret;
> >>>>
> >>>> 	/*
> >>>> 	 * Optional: you can check that the lock is not taken
> >>>> 	 * to shout loud if potential deadlocks arise.
> >>>> 	 */
> >>>>
> >>>> 	/*
> >>>> 	 * We want to protect the register update with the
> >>>> 	 * lock blah blah blah... explanatory comment.
> >>>> 	 */
> >>>> 	mutex_lock(&hwmon->hwmon_lock);
> >>>> 	ret = xe_hwmon_process_reg(..., REG_RMW);
> >>>> 	mutex_unlock(&hwmon->hwmon_lock);
> >>>>
> >>>> 	return ret;
> >>>>      }
> >>>>
> >>>> What do you think? It looks much clearer to me.
> >>>
> >>> REG_PKG_RAPL_LIMIT register is being written in
> >>> xe_hwmon_power_max_write also, that's why lock is taken. But some
> >>> how while cleaning up I forgot to take it in
> >>> xe_hwmon_power_max_write(), thanks for catching it up. I will update
> xe_hwmon_power_max_write() and resend series.
> >>
> >> yes... OK... then, please add the lock also in the write case.
> >>
> >> But still... thinking of hwmon running in more threads don't you
> >> think we might need a more generic locking? This might mean to lock
> >> all around xe_hwmon_process_reg()... maybe it's an overkill.
> >>
> >> For the time being I'm OK with your current solution.
> >>
> >> If you don't like my suggestion above, feel free to ignore it.
> >
> > Yeah agree, might as well take the lock around the switch statement in
> > xe_hwmon_process_reg().
> Will there be a possibility where two different threads will access power
> attributes power1_max and power1_max_interval simultaneously and
> frequently. I am not able to think such scenario. If not then we can remove
> lock from here.
There are read and write cases, as far as I can see the seq_read_iter always takes seq_file->lock
So read cases like hwm_energy won't need any lock in my opinion, we are protected by above sysfs layer.
https://elixir.bootlin.com/linux/latest/source/fs/seq_file.c#L171
But seq_write on another hand does not use any lock, so I also fees for any ATTR does any read/write operation
on REG_PKG_RAPL_LIMIT register need a lock here.
Thanks,
Anshuman Gupta.

> 
> Regards.
> Badal
> >
> >>
> >> Andi
> >
> > Thanks.
> > --
> > Ashutosh
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
index 1a7a6c23e141..9ceb9c04b52b 100644
--- a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
+++ b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
@@ -59,3 +59,14 @@  Contact:	intel-xe@lists.freedesktop.org
 Description:	RO. Energy input of device in microjoules.
 
 		Only supported for particular Intel xe graphics platforms.
+
+What:		/sys/devices/.../hwmon/hwmon<i>/power1_max_interval
+Date:		September 2023
+KernelVersion:	6.5
+Contact:	intel-xe@lists.freedesktop.org
+Description:	RW. Sustained power limit interval (Tau in PL1/Tau) in
+		milliseconds over which sustained power is averaged.
+
+		Only supported for particular Intel xe graphics platforms.
+
+
diff --git a/drivers/gpu/drm/xe/regs/xe_mchbar_regs.h b/drivers/gpu/drm/xe/regs/xe_mchbar_regs.h
index d8ecbe1858d1..519dd1067a19 100644
--- a/drivers/gpu/drm/xe/regs/xe_mchbar_regs.h
+++ b/drivers/gpu/drm/xe/regs/xe_mchbar_regs.h
@@ -22,15 +22,23 @@ 
 #define   PKG_TDP				GENMASK_ULL(14, 0)
 #define   PKG_MIN_PWR				GENMASK_ULL(30, 16)
 #define   PKG_MAX_PWR				GENMASK_ULL(46, 32)
+#define   PKG_MAX_WIN				GENMASK_ULL(54, 48)
+#define     PKG_MAX_WIN_X			GENMASK_ULL(54, 53)
+#define     PKG_MAX_WIN_Y			GENMASK_ULL(52, 48)
+
 
 #define PCU_CR_PACKAGE_POWER_SKU_UNIT		XE_REG(MCHBAR_MIRROR_BASE_SNB + 0x5938)
 #define   PKG_PWR_UNIT				REG_GENMASK(3, 0)
 #define   PKG_ENERGY_UNIT			REG_GENMASK(12, 8)
+#define   PKG_TIME_UNIT				REG_GENMASK(19, 16)
 
 #define PCU_CR_PACKAGE_ENERGY_STATUS		XE_REG(MCHBAR_MIRROR_BASE_SNB + 0x593c)
 
 #define PCU_CR_PACKAGE_RAPL_LIMIT		XE_REG(MCHBAR_MIRROR_BASE_SNB + 0x59a0)
 #define   PKG_PWR_LIM_1				REG_GENMASK(14, 0)
 #define   PKG_PWR_LIM_1_EN			REG_BIT(15)
+#define   PKG_PWR_LIM_1_TIME			REG_GENMASK(23, 17)
+#define   PKG_PWR_LIM_1_TIME_X			REG_GENMASK(23, 22)
+#define   PKG_PWR_LIM_1_TIME_Y			REG_GENMASK(21, 17)
 
 #endif /* _XE_MCHBAR_REGS_H_ */
diff --git a/drivers/gpu/drm/xe/xe_hwmon.c b/drivers/gpu/drm/xe/xe_hwmon.c
index 1deb5007e1e2..5bd5e5e9958b 100644
--- a/drivers/gpu/drm/xe/xe_hwmon.c
+++ b/drivers/gpu/drm/xe/xe_hwmon.c
@@ -38,6 +38,7 @@  enum xe_hwmon_reg_operation {
 #define SF_CURR		1000		/* milliamperes */
 #define SF_VOLTAGE	1000		/* millivolts */
 #define SF_ENERGY	1000000		/* microjoules */
+#define SF_TIME		1000		/* milliseconds */
 
 struct xe_hwmon_energy_info {
 	u32 reg_val_prev;
@@ -50,6 +51,7 @@  struct xe_hwmon {
 	struct mutex hwmon_lock; /* rmw operations*/
 	int scl_shift_power;
 	int scl_shift_energy;
+	int scl_shift_time;
 	struct xe_hwmon_energy_info ei;	/*  Energy info for energy1_input */
 };
 
@@ -256,6 +258,138 @@  xe_hwmon_energy_get(struct xe_hwmon *hwmon, long *energy)
 	xe_device_mem_access_put(gt_to_xe(hwmon->gt));
 }
 
+static ssize_t
+xe_hwmon_power1_max_interval_show(struct device *dev, struct device_attribute *attr,
+				  char *buf)
+{
+	struct xe_hwmon *hwmon = dev_get_drvdata(dev);
+	u32 r, x, y, x_w = 2; /* 2 bits */
+	u64 tau4, out;
+
+	xe_device_mem_access_get(gt_to_xe(hwmon->gt));
+
+	xe_hwmon_process_reg(hwmon, REG_PKG_RAPL_LIMIT,
+			     REG_READ, &r, 0, 0);
+
+	xe_device_mem_access_put(gt_to_xe(hwmon->gt));
+
+	x = REG_FIELD_GET(PKG_PWR_LIM_1_TIME_X, r);
+	y = REG_FIELD_GET(PKG_PWR_LIM_1_TIME_Y, r);
+	/*
+	 * tau = 1.x * power(2,y), x = bits(23:22), y = bits(21:17)
+	 *     = (4 | x) << (y - 2)
+	 * where (y - 2) ensures a 1.x fixed point representation of 1.x
+	 * However because y can be < 2, we compute
+	 *     tau4 = (4 | x) << y
+	 * but add 2 when doing the final right shift to account for units
+	 */
+	tau4 = ((1 << x_w) | x) << y;
+	/* val in hwmon interface units (millisec) */
+	out = mul_u64_u32_shr(tau4, SF_TIME, hwmon->scl_shift_time + x_w);
+
+	return sysfs_emit(buf, "%llu\n", out);
+}
+
+static ssize_t
+xe_hwmon_power1_max_interval_store(struct device *dev, struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct xe_hwmon *hwmon = dev_get_drvdata(dev);
+	u32 x, y, rxy, x_w = 2; /* 2 bits */
+	u64 tau4, r, max_win;
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	/*
+	 * Max HW supported tau in '1.x * power(2,y)' format, x = 0, y = 0x12
+	 * The hwmon->scl_shift_time default of 0xa results in a max tau of 256 seconds
+	 */
+#define PKG_MAX_WIN_DEFAULT 0x12ull
+
+	/*
+	 * val must be < max in hwmon interface units. The steps below are
+	 * explained in xe_hwmon_power1_max_interval_show()
+	 */
+	r = FIELD_PREP(PKG_MAX_WIN, PKG_MAX_WIN_DEFAULT);
+	x = REG_FIELD_GET(PKG_MAX_WIN_X, r);
+	y = REG_FIELD_GET(PKG_MAX_WIN_Y, r);
+	tau4 = ((1 << x_w) | x) << y;
+	max_win = mul_u64_u32_shr(tau4, SF_TIME, hwmon->scl_shift_time + x_w);
+
+	if (val > max_win)
+		return -EINVAL;
+
+	/* val in hw units */
+	val = DIV_ROUND_CLOSEST_ULL((u64)val << hwmon->scl_shift_time, SF_TIME);
+	/* Convert to 1.x * power(2,y) */
+	if (!val) {
+		/* Avoid ilog2(0) */
+		y = 0;
+		x = 0;
+	} else {
+		y = ilog2(val);
+		/* x = (val - (1 << y)) >> (y - 2); */
+		x = (val - (1ul << y)) << x_w >> y;
+	}
+
+	rxy = REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_X, x) | REG_FIELD_PREP(PKG_PWR_LIM_1_TIME_Y, y);
+
+	xe_device_mem_access_get(gt_to_xe(hwmon->gt));
+
+	mutex_lock(&hwmon->hwmon_lock);
+
+	xe_hwmon_process_reg(hwmon, REG_PKG_RAPL_LIMIT, REG_RMW, (u32 *)&r,
+			     PKG_PWR_LIM_1_TIME, rxy);
+
+	mutex_unlock(&hwmon->hwmon_lock);
+
+	xe_device_mem_access_put(gt_to_xe(hwmon->gt));
+
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR(power1_max_interval, 0664,
+			  xe_hwmon_power1_max_interval_show,
+			  xe_hwmon_power1_max_interval_store, 0);
+
+static struct attribute *hwmon_attributes[] = {
+	&sensor_dev_attr_power1_max_interval.dev_attr.attr,
+	NULL
+};
+
+static umode_t xe_hwmon_attributes_visible(struct kobject *kobj,
+					   struct attribute *attr, int index)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct xe_hwmon *hwmon = dev_get_drvdata(dev);
+	u32 reg_val;
+	int ret = 0;
+
+	xe_device_mem_access_get(gt_to_xe(hwmon->gt));
+
+	if (attr == &sensor_dev_attr_power1_max_interval.dev_attr.attr)
+		ret =  xe_hwmon_process_reg(hwmon, REG_PKG_RAPL_LIMIT,
+					    REG_READ, &reg_val, 0, 0) ? 0 : attr->mode;
+
+	xe_device_mem_access_put(gt_to_xe(hwmon->gt));
+
+	return ret;
+}
+
+static const struct attribute_group hwmon_attrgroup = {
+	.attrs = hwmon_attributes,
+	.is_visible = xe_hwmon_attributes_visible,
+};
+
+static const struct attribute_group *hwmon_groups[] = {
+	&hwmon_attrgroup,
+	NULL
+};
+
 static const struct hwmon_channel_info *hwmon_info[] = {
 	HWMON_CHANNEL_INFO(power, HWMON_P_MAX | HWMON_P_RATED_MAX | HWMON_P_CRIT),
 	HWMON_CHANNEL_INFO(curr, HWMON_C_CRIT),
@@ -574,6 +708,7 @@  xe_hwmon_get_preregistration_info(struct xe_device *xe)
 	if (!ret) {
 		hwmon->scl_shift_power = REG_FIELD_GET(PKG_PWR_UNIT, val_sku_unit);
 		hwmon->scl_shift_energy = REG_FIELD_GET(PKG_ENERGY_UNIT, val_sku_unit);
+		hwmon->scl_shift_time = REG_FIELD_GET(PKG_TIME_UNIT, val_sku_unit);
 	}
 
 	/*
@@ -611,7 +746,8 @@  void xe_hwmon_register(struct xe_device *xe)
 	/*  hwmon_dev points to device hwmon<i> */
 	hwmon->hwmon_dev = devm_hwmon_device_register_with_info(dev, "xe", hwmon,
 								&hwmon_chip_info,
-								NULL);
+								hwmon_groups);
+
 	if (IS_ERR(hwmon->hwmon_dev)) {
 		drm_warn(&xe->drm, "Failed to register xe hwmon (%pe)\n", hwmon->hwmon_dev);
 		xe->hwmon = NULL;