-
Notifications
You must be signed in to change notification settings - Fork 844
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Driver for the I3C Controller IP Core #2480
base: main
Are you sure you want to change the base?
Conversation
a9e0da2
to
1458e52
Compare
Add nodes for ADI I3C Controller IP core. Signed-off-by: Jorge Marques <[email protected]>
ADI I3C Controller is an AXI-interfaced HDL IP that implements an I3C Controller. Using standard "master" name for coherence with the abstraction and other controllers already in the Linux kernel. Signed-off-by: Jorge Marques <[email protected]>
Add the optional IBI methods for the I3C Controller. Signed-off-by: Jorge Marques <[email protected]>
index = 0; | ||
for (status0 = readl(master->regs + REG_FIFO_STATUS); | ||
!(status0 & FIFO_STATUS_CMDR_EMPTY); | ||
status0 = readl(master->regs + REG_FIFO_STATUS)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any chance of this going into an infinite loop?
if yes, maybe add a timeout?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, for each command CMD sent, one command receipt CMDR is received.
pending = readl(master->regs + REG_IRQ_PENDING); | ||
|
||
spin_lock(&master->xferqueue.lock); | ||
adi_i3c_master_end_xfer_locked(master, pending); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this may be a lot of work for an IRQ handler? (not sure, just guessing here);
if it is too much work (squeezed into an IRQ handler) it may make sense to add a threaded_irq
handler too;
(still open for debate on this point)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expected number of AXI R/W per interrupt type:
- CMDR: 2 Reads (4-bytes from the CMDR plus 4-bytes of SDI data) + [2x#CMD Writes from next transfer, if any]
- IBI: 1 Read (4-bytes)
- DAA: 2 Reads + 1 Write (6-bytes R, 1-byte W)
Plus 1 Write to clear the interrupt.
2-bytes per CMD
is always less than SPI Engine's commands {[REG_CONFIG;REG_CLK_DIV,REG_WORD_LEN];CS;Transfer;Sleep;CS;SYNC}
|
||
ret = xfer->ret; | ||
cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]); | ||
kfree(xfer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm curios if this is correct;
AFAICT, adi_i3c_master_queue_xfer()
may have added this to a list, so, now it's free'd?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so, the idea that makes it hard (for me to visualize this, is): when wait_for_completion_timeout()
finishes, is there a chance that xfer
will still be part of a list?
the whole mechanism from adi_i3c_master_queue_xfer()
&& adi_i3c_master_unqueue_xfer()
(see below example from adi_i3c_master_unqueue_xfer()
), makes it hard to say if list_del_init()
has been called when we get here:
if (master->xferqueue.cur == xfer)
master->xferqueue.cur = NULL;
else
list_del_init(&xfer->node)
it may be an idea to move the kfree
handling in the same place where list_del_init()
(and other stuff) happens;
that may make it more difficult to get the xfer->ret
value and the other errors;
what about doing it the other way around?
pass xfer->cmd = cmd
(at the start of the transfer) and work on it inside adi_i3c_master_unqueue_xfer()
and other handlers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Design flaws TLDR:
- no FIFO flushing on unqueue_xfer (fixed by sw core reset).
- wait_for_completion_timeout is blocking (seem to be the normal).
current transfer: master->xferqueue.cur
queue: master->xferqueue
Pipeline:
When a xfer is queued:
- If current transfer is null, set it to xfer and start transfer.
- If not, append to xfer to queue.
When a xfer finishes, triggered by the first CMDR received and iterated through all CMDR (bus transfers are faster than AXI reads), get the next xfer from the queue and:
- if it is not null; remove xfer from queue and set it as current transfer and start transfer.
- if it is null, current transfer is set null
Finally, in normal operation wait_for_completion_timeout should never expire, but if it does:
- if xfer is current transfer, set it to null
- If not, remove the node from the queued list
A flaw of the flow is that the FIFOs are not flushed when unqueue_xfer is called; so if a CMDR from current transfer arrives (super) late it will "sit" on the FIFO and miss-belong to the next xfer.
Still, wait_for_completion_timeout should never happen since the bus never hangs (unlike I²C), unless there is a bug in the HDL.
So, overall the solution is to flush the FIFOs on timeout? As is, a core reset will flush all FIFOs
For the o.g. questions
AFAICT, adi_i3c_master_queue_xfer() may have added this to a list, so, now it's free'd?
Even if it is added to a list, the completion is only satisfied on complete(&xfer->comp)
at end_xfer_locked
when wait_for_completion_timeout() finishes, is there a chance that xfer will still be part of a list?
- In normal operation (no timeout) no, it exed as current transfer before
end_xfer_locked
- on timeout, no,
unqueue_xfer
removed it form the list.
At driver level, we call either i3c_device_do_priv_xfers
, passing a buffer at the each xfer of the xfers array;
or even use the regmap API, e.g, regmap_raw_read
; both eventually call the controller priv_xfers
.
for (i = 0; i < nxfers; i++) | ||
xfers[i].err = adi_i3c_cmd_get_err(&xfer->cmds[i]); | ||
|
||
kfree(xfer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment here;
AFAICT, adi_i3c_master_queue_xfer()
may have added this to a list, so, now it's free'd?`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See long comment.
adi_i3c_master_unqueue_xfer(master, xfer); | ||
|
||
ret = xfer->ret; | ||
kfree(xfer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment here
AFAICT, adi_i3c_master_queue_xfer()
may have added this to a list, so, now it's free'd?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See long comment.
drivers/i3c/master/adi-i3c-master.c
Outdated
if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000))) | ||
adi_i3c_master_unqueue_xfer(master, xfer); | ||
|
||
ret = xfer->ret; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this ret
is assigned bu never used from here-on
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was also an id assigned but never used that I removed.
And a commented //sleep
that I swear I had removed
drivers/i3c/master/adi-i3c-master.c
Outdated
writel(readl(master->regs + REG_IRQ_MASK) | IRQ_PENDING_IBI_PENDING, | ||
master->regs + REG_IRQ_MASK); | ||
|
||
ret = i3c_master_enec_locked(m, dev->info.dyn_addr, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return i3c_master_enec_locked()
directly?
or will there be more code added after this later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, done
Reset core on xfer timeout to ensure no orphan transfers. Remove dangling id variable. Remove forgotten commented sleep. Remove unused rets. Commit to be squashed. Signed-off-by: Jorge Marques <[email protected]>
Only disable the IBI feature at the IP Core level if no other device has IBI enabled. Signed-off-by: Jorge Marques <[email protected]>
Wrong mask for I2C detach. Signed-off-by: Jorge Marques <[email protected]>
PR Description
Creating this PR to get some preliminary feedback and because I'm sitting on it for a while without making changes.
It is divided in two commits:
Even though the IP Core has an Offload engine, I didn't try to implement it, because the SPI Engine Offload is being/has been reworked and I would rather wait the final implementation to settle before trying to do the same on the I3C abstraction.
Using the upstream
master/slave
nomenclature for coherence, our IP core uses the speccontroller/peripheral
.I think we should only rename it when upstream updates the already upstream drivers.
Something that might catch the attention of reviewers is the call-back/entry point for IBIs,
That is because it is allocated per driver device, along these lines:
PR Type
PR Checklist