[Skiboot] [PATCH v7 2/2] SBE: Add timer support
Vasant Hegde
hegdevasant at linux.vnet.ibm.com
Tue Apr 17 17:01:39 AEST 2018
On 04/17/2018 09:48 AM, Benjamin Herrenschmidt wrote:
> On Mon, 2018-04-16 at 17:18 +0530, Vasant Hegde wrote:
>> SBE on P9 provides one shot programmable timer facility. We can use this
>> to implement OPAL timers and hence limit the reliance on the Linux
>> heartbeat (similar to HW timer facility provided by SLW on P8).
>>
.../...
>>
>> +/*
>> + * Check if the timer is working. If at least 10ms elapsed since
>> + * last scheduled timer expiry.
>> + */
>> +static void p9_sbe_timer_poll(struct p9_sbe *sbe)
>> +{
>> + if (!sbe_has_timer || !sbe_timer_in_progress)
>> + return;
>> +
>> + lock(&sbe->lock);
>> + if (tb_compare(mftb(), sbe_last_gen_stamp + msecs_to_tb(10))
>> + != TB_AAFTERB)
>> + goto out;
>
> Do you need the lock for the above ?
Not required.
>
>> + /*
>> + * In some cases there will be a delay in calling OPAL interrupt
>> + * handler routine (opal_handle_interrupt). In such cases its
>> + * possible that SBE has responded, but OPAL didn't act on that.
>> + * Hence check for SBE response before disabling timer.
>> + */
>> + __p9_sbe_interrupt(sbe);
>> + if (got_timer_interrupt)
>> + goto call_timer;
>
> Again, why do you call that here ?
>
> You just called p9_sbe_timeout_poll_one() for that same SBE, which
> already took the lock *and* called __p9_sbe_interrupt.
>
> I would instead of a call-out from p9_sbe_timeout_poll_one() to check
> timers.
Yeah. We can move p9_sbe_timeout_poll inside p9_sbe_timeout_poll_one(). That way
we can avoid lock-unlock. But we still need to call __p9_sbe_interrupt() twice..
as timer interrupt will come after removing msg from sbe->msg_list.
>
>> + if (!sbe_timer_in_progress)
>> + goto out;
>> +
>> + if (tb_compare(mftb(), sbe_last_gen_stamp + msecs_to_tb(10))
>> + != TB_AAFTERB)
>> + goto out;
>> +
>> + prlog(PR_ERR, "Timer stuck, falling back to OPAL pollers.\n");
>> + prlog(PR_ERR, "You will likely have slower I2C and may have "
>> + "experienced increased jitter.\n");
>> + p9_sbe_reg_dump(sbe_default_chip_id);
>> + sbe_has_timer = false;
>> + sbe_timer_in_progress = false;
>> +
>> +out:
>> + unlock(&sbe->lock);
>> + return;
>> +
>> +call_timer:
>> + got_timer_interrupt = false;
>> + /* Drop lock and call timers */
>> + unlock(&sbe->lock);
>> + check_timers(true);
>> +}
>> +
>> static void p9_sbe_timeout_poll(void *user_data __unused)
>> {
>> struct p9_sbe *sbe;
>> @@ -644,9 +736,119 @@ static void p9_sbe_timeout_poll(void *user_data __unused)
>> continue;
>> sbe = chip->sbe;
>> p9_sbe_timeout_poll_one(sbe);
>> + if (sbe->chip_id == sbe_default_chip_id)
>> + p9_sbe_timer_poll(sbe);
>> }
>> }
>>
>> +static void p9_sbe_timer_resp(struct p9_sbe_msg *msg)
>> +{
>> + if (msg->state != sbe_msg_done) {
>> + prlog(PR_DEBUG, "Failed to schedule timer [chip id %x]\n",
>> + sbe_default_chip_id);
>> +
>> + if (!has_new_target)
>> + return;
>> + } else {
>> + /* Update last scheduled timer value */
>> + sbe_last_gen_stamp = mftb() +
>> + usecs_to_tb(timer_ctrl_msg->reg[1]);
>> + sbe_timer_in_progress = true;
>> + }
>
> The common path should be that we don't have a new target,
> I would suggest movign the !has_new_target down here, before
> the lock. To be race-free, we need to order it with the msg state, so
> you do need a sync.
Oh yes. I should have moved has_new_target to down.
>
> What about the lockless updates to sbe_last_gen_stamp and
> sbe_timer_in_progress ? Is that safe?
I think its safe.
>
>> + lock(&sbe_timer_lock);
>> + if (has_new_target) {
>> + has_new_target = false;
>> + p9_sbe_timer_schedule();
>> + }
>> + unlock(&sbe_timer_lock);
>> +}
>> +
>> +static void p9_sbe_timer_schedule(void)
>> +{
>> + int rc;
>> + u32 tick_us = SBE_TIMER_DEFAULT_US;
>> + u64 tb_cnt, now = mftb();
>> +
>> + if (sbe_timer_in_progress) {
>> + /* Remaining time of inflight timer <= sbe_timer_def_tb */
>> + if ((sbe_last_gen_stamp - now) <= sbe_timer_def_tb)
>> + return;
>> + }
>> +
>> + if (now < sbe_timer_target) {
>> + /* Calculate how many microseconds from now, rounded up */
>> + if ((sbe_timer_target - now) > sbe_timer_def_tb) {
>> + tb_cnt = sbe_timer_target - now + usecs_to_tb(1) - 1;
>> + tick_us = tb_to_usecs(tb_cnt);
>> + }
>> + }
>> +
>> + /* Clear sequence number. p9_sbe_queue_msg will add new sequene ID */
>> + timer_ctrl_msg->reg[0] &= ~(PPC_BITMASK(32, 47));
>> + /* Update timeout value */
>> + timer_ctrl_msg->reg[1] = tick_us;
>> + rc = p9_sbe_queue_msg(sbe_default_chip_id, timer_ctrl_msg,
>> + p9_sbe_timer_resp);
>> + if (rc != OPAL_SUCCESS) {
>> + prlog(PR_ERR, "Failed to start timer [chip id = %x]\n",
>> + sbe_default_chip_id);
>> + return;
>> + }
>> +}
>> +
>> +/*
>> + * This is called with the timer lock held, so there is no
>> + * issue with re-entrancy or concurrence
>> + */
>> +void p9_sbe_update_timer_expiry(uint64_t new_target)
>> +{
>> + u64 now = mftb();
>> +
>> + if (!sbe_has_timer || new_target == sbe_timer_target)
>> + return;
>> +
>> + lock(&sbe_timer_lock);
>> + /* Timer message is in flight. Record new timer and schedule later */
>> + if (p9_sbe_msg_busy(timer_ctrl_msg) || has_new_target) {
>> + if (new_target < now)
>> + goto out;
>
> Will the caller handle the case where we just passed the target ? Will
> it re-check timers after calling us ?
Sorry. I'm not sure I understood your question.
So we enter this block only when new_target is < inflight timer. So we are good
in source.
Now on callback path if SBE triggers early we have problem (..as check_timer
doesn't re-arm).
May be fix check_timer to re-arm timer? -OR- do you want me to take care in
driver itself?
We can take care here by doing something like below in p9_sbe_interrupt () function.
if (got_timer_interrupt) {
got_timer_interrupt = false;
/* Drop lock and call timers */
unlock(&sbe->lock);
check_timers(true);
/* Early interrupt , re-arm timer */
if (sbe_last_gen_timestamp > mftb())
p9_sbe_update_timer_expiry(sbe_last_gen_timestamp);
}
-Vasant
More information about the Skiboot
mailing list