mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-04 11:24:04 +08:00
media: cec: add new tx/rx status bits to detect aborts/timeouts
If the HDMI cable is disconnected or the CEC adapter is manually unconfigured, then all pending transmits and wait-for-replies are aborted. Signal this with new status bits (CEC_RX/TX_STATUS_ABORTED). If due to (usually) a driver bug a transmit never ends (i.e. the transmit_done was never called by the driver), then when this times out the message is marked with CEC_TX_STATUS_TIMEOUT. This should not happen and is an indication of a driver bug. Without a separate status bit for this it was impossible to detect this from userspace. The 'transmit timed out' kernel message is now a warning, so this should be more prominent in the kernel log as well. Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com> Cc: <stable@vger.kernel.org> # for v4.18 and up Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
This commit is contained in:
parent
81e33279d1
commit
7ec2b3b941
@ -16,10 +16,10 @@ CEC_RECEIVE, CEC_TRANSMIT - Receive or transmit a CEC message
|
|||||||
Synopsis
|
Synopsis
|
||||||
========
|
========
|
||||||
|
|
||||||
.. c:function:: int ioctl( int fd, CEC_RECEIVE, struct cec_msg *argp )
|
.. c:function:: int ioctl( int fd, CEC_RECEIVE, struct cec_msg \*argp )
|
||||||
:name: CEC_RECEIVE
|
:name: CEC_RECEIVE
|
||||||
|
|
||||||
.. c:function:: int ioctl( int fd, CEC_TRANSMIT, struct cec_msg *argp )
|
.. c:function:: int ioctl( int fd, CEC_TRANSMIT, struct cec_msg \*argp )
|
||||||
:name: CEC_TRANSMIT
|
:name: CEC_TRANSMIT
|
||||||
|
|
||||||
Arguments
|
Arguments
|
||||||
@ -272,6 +272,19 @@ View On' messages from initiator 0xf ('Unregistered') to destination 0 ('TV').
|
|||||||
- The transmit failed after one or more retries. This status bit is
|
- The transmit failed after one or more retries. This status bit is
|
||||||
mutually exclusive with :ref:`CEC_TX_STATUS_OK <CEC-TX-STATUS-OK>`.
|
mutually exclusive with :ref:`CEC_TX_STATUS_OK <CEC-TX-STATUS-OK>`.
|
||||||
Other bits can still be set to explain which failures were seen.
|
Other bits can still be set to explain which failures were seen.
|
||||||
|
* .. _`CEC-TX-STATUS-ABORTED`:
|
||||||
|
|
||||||
|
- ``CEC_TX_STATUS_ABORTED``
|
||||||
|
- 0x40
|
||||||
|
- The transmit was aborted due to an HDMI disconnect, or the adapter
|
||||||
|
was unconfigured, or a transmit was interrupted, or the driver
|
||||||
|
returned an error when attempting to start a transmit.
|
||||||
|
* .. _`CEC-TX-STATUS-TIMEOUT`:
|
||||||
|
|
||||||
|
- ``CEC_TX_STATUS_TIMEOUT``
|
||||||
|
- 0x80
|
||||||
|
- The transmit timed out. This should not normally happen and this
|
||||||
|
indicates a driver problem.
|
||||||
|
|
||||||
|
|
||||||
.. tabularcolumns:: |p{5.6cm}|p{0.9cm}|p{11.0cm}|
|
.. tabularcolumns:: |p{5.6cm}|p{0.9cm}|p{11.0cm}|
|
||||||
@ -300,6 +313,14 @@ View On' messages from initiator 0xf ('Unregistered') to destination 0 ('TV').
|
|||||||
- The message was received successfully but the reply was
|
- The message was received successfully but the reply was
|
||||||
``CEC_MSG_FEATURE_ABORT``. This status is only set if this message
|
``CEC_MSG_FEATURE_ABORT``. This status is only set if this message
|
||||||
was the reply to an earlier transmitted message.
|
was the reply to an earlier transmitted message.
|
||||||
|
* .. _`CEC-RX-STATUS-ABORTED`:
|
||||||
|
|
||||||
|
- ``CEC_RX_STATUS_ABORTED``
|
||||||
|
- 0x08
|
||||||
|
- The wait for a reply to an earlier transmitted message was aborted
|
||||||
|
because the HDMI cable was disconnected, the adapter was unconfigured
|
||||||
|
or the :ref:`CEC_TRANSMIT <CEC_RECEIVE>` that waited for a
|
||||||
|
reply was interrupted.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -354,7 +354,7 @@ static void cec_data_completed(struct cec_data *data)
|
|||||||
*
|
*
|
||||||
* This function is called with adap->lock held.
|
* This function is called with adap->lock held.
|
||||||
*/
|
*/
|
||||||
static void cec_data_cancel(struct cec_data *data)
|
static void cec_data_cancel(struct cec_data *data, u8 tx_status)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* It's either the current transmit, or it is a pending
|
* It's either the current transmit, or it is a pending
|
||||||
@ -369,13 +369,11 @@ static void cec_data_cancel(struct cec_data *data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data->msg.tx_status & CEC_TX_STATUS_OK) {
|
if (data->msg.tx_status & CEC_TX_STATUS_OK) {
|
||||||
/* Mark the canceled RX as a timeout */
|
|
||||||
data->msg.rx_ts = ktime_get_ns();
|
data->msg.rx_ts = ktime_get_ns();
|
||||||
data->msg.rx_status = CEC_RX_STATUS_TIMEOUT;
|
data->msg.rx_status = CEC_RX_STATUS_ABORTED;
|
||||||
} else {
|
} else {
|
||||||
/* Mark the canceled TX as an error */
|
|
||||||
data->msg.tx_ts = ktime_get_ns();
|
data->msg.tx_ts = ktime_get_ns();
|
||||||
data->msg.tx_status |= CEC_TX_STATUS_ERROR |
|
data->msg.tx_status |= tx_status |
|
||||||
CEC_TX_STATUS_MAX_RETRIES;
|
CEC_TX_STATUS_MAX_RETRIES;
|
||||||
data->msg.tx_error_cnt++;
|
data->msg.tx_error_cnt++;
|
||||||
data->attempts = 0;
|
data->attempts = 0;
|
||||||
@ -403,15 +401,15 @@ static void cec_flush(struct cec_adapter *adap)
|
|||||||
while (!list_empty(&adap->transmit_queue)) {
|
while (!list_empty(&adap->transmit_queue)) {
|
||||||
data = list_first_entry(&adap->transmit_queue,
|
data = list_first_entry(&adap->transmit_queue,
|
||||||
struct cec_data, list);
|
struct cec_data, list);
|
||||||
cec_data_cancel(data);
|
cec_data_cancel(data, CEC_TX_STATUS_ABORTED);
|
||||||
}
|
}
|
||||||
if (adap->transmitting)
|
if (adap->transmitting)
|
||||||
cec_data_cancel(adap->transmitting);
|
cec_data_cancel(adap->transmitting, CEC_TX_STATUS_ABORTED);
|
||||||
|
|
||||||
/* Cancel the pending timeout work. */
|
/* Cancel the pending timeout work. */
|
||||||
list_for_each_entry_safe(data, n, &adap->wait_queue, list) {
|
list_for_each_entry_safe(data, n, &adap->wait_queue, list) {
|
||||||
if (cancel_delayed_work(&data->work))
|
if (cancel_delayed_work(&data->work))
|
||||||
cec_data_cancel(data);
|
cec_data_cancel(data, CEC_TX_STATUS_OK);
|
||||||
/*
|
/*
|
||||||
* If cancel_delayed_work returned false, then
|
* If cancel_delayed_work returned false, then
|
||||||
* the cec_wait_timeout function is running,
|
* the cec_wait_timeout function is running,
|
||||||
@ -487,12 +485,13 @@ int cec_thread_func(void *_adap)
|
|||||||
* so much traffic on the bus that the adapter was
|
* so much traffic on the bus that the adapter was
|
||||||
* unable to transmit for CEC_XFER_TIMEOUT_MS (2.1s).
|
* unable to transmit for CEC_XFER_TIMEOUT_MS (2.1s).
|
||||||
*/
|
*/
|
||||||
dprintk(1, "%s: message %*ph timed out\n", __func__,
|
pr_warn("cec-%s: message %*ph timed out\n", adap->name,
|
||||||
adap->transmitting->msg.len,
|
adap->transmitting->msg.len,
|
||||||
adap->transmitting->msg.msg);
|
adap->transmitting->msg.msg);
|
||||||
adap->tx_timeouts++;
|
adap->tx_timeouts++;
|
||||||
/* Just give up on this. */
|
/* Just give up on this. */
|
||||||
cec_data_cancel(adap->transmitting);
|
cec_data_cancel(adap->transmitting,
|
||||||
|
CEC_TX_STATUS_TIMEOUT);
|
||||||
goto unlock;
|
goto unlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,7 +542,7 @@ int cec_thread_func(void *_adap)
|
|||||||
/* Tell the adapter to transmit, cancel on error */
|
/* Tell the adapter to transmit, cancel on error */
|
||||||
if (adap->ops->adap_transmit(adap, data->attempts,
|
if (adap->ops->adap_transmit(adap, data->attempts,
|
||||||
signal_free_time, &data->msg))
|
signal_free_time, &data->msg))
|
||||||
cec_data_cancel(data);
|
cec_data_cancel(data, CEC_TX_STATUS_ABORTED);
|
||||||
|
|
||||||
unlock:
|
unlock:
|
||||||
mutex_unlock(&adap->lock);
|
mutex_unlock(&adap->lock);
|
||||||
@ -715,8 +714,6 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
|
|||||||
{
|
{
|
||||||
struct cec_data *data;
|
struct cec_data *data;
|
||||||
u8 last_initiator = 0xff;
|
u8 last_initiator = 0xff;
|
||||||
unsigned int timeout;
|
|
||||||
int res = 0;
|
|
||||||
|
|
||||||
msg->rx_ts = 0;
|
msg->rx_ts = 0;
|
||||||
msg->tx_ts = 0;
|
msg->tx_ts = 0;
|
||||||
@ -858,48 +855,21 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg,
|
|||||||
if (!block)
|
if (!block)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/*
|
|
||||||
* If we don't get a completion before this time something is really
|
|
||||||
* wrong and we time out.
|
|
||||||
*/
|
|
||||||
timeout = CEC_XFER_TIMEOUT_MS;
|
|
||||||
/* Add the requested timeout if we have to wait for a reply as well */
|
|
||||||
if (msg->timeout)
|
|
||||||
timeout += msg->timeout;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Release the lock and wait, retake the lock afterwards.
|
* Release the lock and wait, retake the lock afterwards.
|
||||||
*/
|
*/
|
||||||
mutex_unlock(&adap->lock);
|
mutex_unlock(&adap->lock);
|
||||||
res = wait_for_completion_killable_timeout(&data->c,
|
wait_for_completion_killable(&data->c);
|
||||||
msecs_to_jiffies(timeout));
|
|
||||||
mutex_lock(&adap->lock);
|
mutex_lock(&adap->lock);
|
||||||
|
|
||||||
if (data->completed) {
|
/* Cancel the transmit if it was interrupted */
|
||||||
/* The transmit completed (possibly with an error) */
|
if (!data->completed)
|
||||||
*msg = data->msg;
|
cec_data_cancel(data, CEC_TX_STATUS_ABORTED);
|
||||||
kfree(data);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* The wait for completion timed out or was interrupted, so mark this
|
|
||||||
* as non-blocking and disconnect from the filehandle since it is
|
|
||||||
* still 'in flight'. When it finally completes it will just drop the
|
|
||||||
* result silently.
|
|
||||||
*/
|
|
||||||
data->blocking = false;
|
|
||||||
if (data->fh)
|
|
||||||
list_del(&data->xfer_list);
|
|
||||||
data->fh = NULL;
|
|
||||||
|
|
||||||
if (res == 0) { /* timed out */
|
/* The transmit completed (possibly with an error) */
|
||||||
/* Check if the reply or the transmit failed */
|
*msg = data->msg;
|
||||||
if (msg->timeout && (msg->tx_status & CEC_TX_STATUS_OK))
|
kfree(data);
|
||||||
msg->rx_status = CEC_RX_STATUS_TIMEOUT;
|
return 0;
|
||||||
else
|
|
||||||
msg->tx_status = CEC_TX_STATUS_MAX_RETRIES;
|
|
||||||
}
|
|
||||||
return res > 0 ? 0 : res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper function to be used by drivers and this framework. */
|
/* Helper function to be used by drivers and this framework. */
|
||||||
|
@ -152,10 +152,13 @@ static inline void cec_msg_set_reply_to(struct cec_msg *msg,
|
|||||||
#define CEC_TX_STATUS_LOW_DRIVE (1 << 3)
|
#define CEC_TX_STATUS_LOW_DRIVE (1 << 3)
|
||||||
#define CEC_TX_STATUS_ERROR (1 << 4)
|
#define CEC_TX_STATUS_ERROR (1 << 4)
|
||||||
#define CEC_TX_STATUS_MAX_RETRIES (1 << 5)
|
#define CEC_TX_STATUS_MAX_RETRIES (1 << 5)
|
||||||
|
#define CEC_TX_STATUS_ABORTED (1 << 6)
|
||||||
|
#define CEC_TX_STATUS_TIMEOUT (1 << 7)
|
||||||
|
|
||||||
#define CEC_RX_STATUS_OK (1 << 0)
|
#define CEC_RX_STATUS_OK (1 << 0)
|
||||||
#define CEC_RX_STATUS_TIMEOUT (1 << 1)
|
#define CEC_RX_STATUS_TIMEOUT (1 << 1)
|
||||||
#define CEC_RX_STATUS_FEATURE_ABORT (1 << 2)
|
#define CEC_RX_STATUS_FEATURE_ABORT (1 << 2)
|
||||||
|
#define CEC_RX_STATUS_ABORTED (1 << 3)
|
||||||
|
|
||||||
static inline int cec_msg_status_is_ok(const struct cec_msg *msg)
|
static inline int cec_msg_status_is_ok(const struct cec_msg *msg)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user