Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6b2bbb08 authored by Hans Verkuil's avatar Hans Verkuil Committed by Mauro Carvalho Chehab
Browse files

media: cec: rework the cec event handling



Event handling was always fairly simplistic since there were only
two events. With the addition of pin events this needed to be redesigned.

The state_change and lost_msgs events are now core events with the
guarantee that the last state is always available. The new pin events
are a queue of events (up to 64 for each event) and the oldest event
will be dropped if the application cannot keep up. Lost events are
marked with a new event flag.

Signed-off-by: default avatarHans Verkuil <hans.verkuil@cisco.com>
Reviewed-by: default avatarMaxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@s-opensource.com>
parent 6303d978
Loading
Loading
Loading
Loading
+76 −52
Original line number Diff line number Diff line
@@ -78,42 +78,62 @@ static unsigned int cec_log_addr2dev(const struct cec_adapter *adap, u8 log_addr
 * Queue a new event for this filehandle. If ts == 0, then set it
 * to the current time.
 *
 * The two events that are currently defined do not need to keep track
 * of intermediate events, so no actual queue of events is needed,
 * instead just store the latest state and the total number of lost
 * messages.
 *
 * Should new events be added in the future that require intermediate
 * results to be queued as well, then a proper queue data structure is
 * required. But until then, just keep it simple.
 * We keep a queue of at most max_event events where max_event differs
 * per event. If the queue becomes full, then drop the oldest event and
 * keep track of how many events we've dropped.
 */
void cec_queue_event_fh(struct cec_fh *fh,
			const struct cec_event *new_ev, u64 ts)
{
	struct cec_event *ev = &fh->events[new_ev->event - 1];
	static const u8 max_events[CEC_NUM_EVENTS] = {
		1, 1, 64, 64,
	};
	struct cec_event_entry *entry;
	unsigned int ev_idx = new_ev->event - 1;

	if (WARN_ON(ev_idx >= ARRAY_SIZE(fh->events)))
		return;

	if (ts == 0)
		ts = ktime_get_ns();

	mutex_lock(&fh->lock);
	if (ev_idx < CEC_NUM_CORE_EVENTS)
		entry = &fh->core_events[ev_idx];
	else
		entry = kmalloc(sizeof(*entry), GFP_KERNEL);
	if (entry) {
		if (new_ev->event == CEC_EVENT_LOST_MSGS &&
	    fh->pending_events & (1 << new_ev->event)) {
		/*
		 * If there is already a lost_msgs event, then just
		 * update the lost_msgs count. This effectively
		 * merges the old and new events into one.
		 */
		ev->lost_msgs.lost_msgs += new_ev->lost_msgs.lost_msgs;
		    fh->queued_events[ev_idx]) {
			entry->ev.lost_msgs.lost_msgs +=
				new_ev->lost_msgs.lost_msgs;
			goto unlock;
		}
		entry->ev = *new_ev;
		entry->ev.ts = ts;

	/*
	 * Intermediate states are not interesting, so just
	 * overwrite any older event.
	 */
	*ev = *new_ev;
	ev->ts = ts;
	fh->pending_events |= 1 << new_ev->event;
		if (fh->queued_events[ev_idx] < max_events[ev_idx]) {
			/* Add new msg at the end of the queue */
			list_add_tail(&entry->list, &fh->events[ev_idx]);
			fh->queued_events[ev_idx]++;
			fh->total_queued_events++;
			goto unlock;
		}

		if (ev_idx >= CEC_NUM_CORE_EVENTS) {
			list_add_tail(&entry->list, &fh->events[ev_idx]);
			/* drop the oldest event */
			entry = list_first_entry(&fh->events[ev_idx],
						 struct cec_event_entry, list);
			list_del(&entry->list);
			kfree(entry);
		}
	}
	/* Mark that events were lost */
	entry = list_first_entry_or_null(&fh->events[ev_idx],
					 struct cec_event_entry, list);
	if (entry)
		entry->ev.flags |= CEC_EVENT_FL_DROPPED_EVENTS;

unlock:
	mutex_unlock(&fh->lock);
@@ -134,46 +154,50 @@ static void cec_queue_event(struct cec_adapter *adap,
}

/*
 * Queue a new message for this filehandle. If there is no more room
 * in the queue, then send the LOST_MSGS event instead.
 * Queue a new message for this filehandle.
 *
 * We keep a queue of at most CEC_MAX_MSG_RX_QUEUE_SZ messages. If the
 * queue becomes full, then drop the oldest message and keep track
 * of how many messages we've dropped.
 */
static void cec_queue_msg_fh(struct cec_fh *fh, const struct cec_msg *msg)
{
	static const struct cec_event ev_lost_msg = {
		.ts = 0,
	static const struct cec_event ev_lost_msgs = {
		.event = CEC_EVENT_LOST_MSGS,
		.flags = 0,
		{
		.lost_msgs.lost_msgs = 1,
		},
	};
	struct cec_msg_entry *entry;

	mutex_lock(&fh->lock);
	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
	if (!entry)
		goto lost_msgs;

	if (entry) {
		entry->msg = *msg;
		/* Add new msg at the end of the queue */
		list_add_tail(&entry->list, &fh->msgs);

	/*
	 * if the queue now has more than CEC_MAX_MSG_RX_QUEUE_SZ
	 * messages, drop the oldest one and send a lost message event.
	 */
	if (fh->queued_msgs == CEC_MAX_MSG_RX_QUEUE_SZ) {
		list_del(&entry->list);
		goto lost_msgs;
	}
		if (fh->queued_msgs < CEC_MAX_MSG_RX_QUEUE_SZ) {
			/* All is fine if there is enough room */
			fh->queued_msgs++;
			mutex_unlock(&fh->lock);
			wake_up_interruptible(&fh->wait);
			return;
		}

lost_msgs:
		/*
		 * if the message queue is full, then drop the oldest one and
		 * send a lost message event.
		 */
		entry = list_first_entry(&fh->msgs, struct cec_msg_entry, list);
		list_del(&entry->list);
		kfree(entry);
	}
	mutex_unlock(&fh->lock);
	cec_queue_event_fh(fh, &ev_lost_msg, 0);

	/*
	 * We lost a message, either because kmalloc failed or the queue
	 * was full.
	 */
	cec_queue_event_fh(fh, &ev_lost_msgs, ktime_get_ns());
}

/*
+35 −13
Original line number Diff line number Diff line
@@ -57,7 +57,7 @@ static unsigned int cec_poll(struct file *filp,
		res |= POLLOUT | POLLWRNORM;
	if (fh->queued_msgs)
		res |= POLLIN | POLLRDNORM;
	if (fh->pending_events)
	if (fh->total_queued_events)
		res |= POLLPRI;
	poll_wait(filp, &fh->wait, poll);
	mutex_unlock(&adap->lock);
@@ -289,15 +289,17 @@ static long cec_receive(struct cec_adapter *adap, struct cec_fh *fh,
static long cec_dqevent(struct cec_adapter *adap, struct cec_fh *fh,
			bool block, struct cec_event __user *parg)
{
	struct cec_event *ev = NULL;
	struct cec_event_entry *ev = NULL;
	u64 ts = ~0ULL;
	unsigned int i;
	unsigned int ev_idx;
	long err = 0;

	mutex_lock(&fh->lock);
	while (!fh->pending_events && block) {
	while (!fh->total_queued_events && block) {
		mutex_unlock(&fh->lock);
		err = wait_event_interruptible(fh->wait, fh->pending_events);
		err = wait_event_interruptible(fh->wait,
					       fh->total_queued_events);
		if (err)
			return err;
		mutex_lock(&fh->lock);
@@ -305,23 +307,29 @@ static long cec_dqevent(struct cec_adapter *adap, struct cec_fh *fh,

	/* Find the oldest event */
	for (i = 0; i < CEC_NUM_EVENTS; i++) {
		if (fh->pending_events & (1 << (i + 1)) &&
		    fh->events[i].ts <= ts) {
			ev = &fh->events[i];
			ts = ev->ts;
		struct cec_event_entry *entry =
			list_first_entry_or_null(&fh->events[i],
						 struct cec_event_entry, list);

		if (entry && entry->ev.ts <= ts) {
			ev = entry;
			ev_idx = i;
			ts = ev->ev.ts;
		}
	}

	if (!ev) {
		err = -EAGAIN;
		goto unlock;
	}
	list_del(&ev->list);

	if (copy_to_user(parg, ev, sizeof(*ev))) {
	if (copy_to_user(parg, &ev->ev, sizeof(ev->ev)))
		err = -EFAULT;
		goto unlock;
	}

	fh->pending_events &= ~(1 << ev->event);
	if (ev_idx >= CEC_NUM_CORE_EVENTS)
		kfree(ev);
	fh->queued_events[ev_idx]--;
	fh->total_queued_events--;

unlock:
	mutex_unlock(&fh->lock);
@@ -495,6 +503,7 @@ static int cec_open(struct inode *inode, struct file *filp)
		.event = CEC_EVENT_STATE_CHANGE,
		.flags = CEC_EVENT_FL_INITIAL_STATE,
	};
	unsigned int i;
	int err;

	if (!fh)
@@ -502,6 +511,8 @@ static int cec_open(struct inode *inode, struct file *filp)

	INIT_LIST_HEAD(&fh->msgs);
	INIT_LIST_HEAD(&fh->xfer_list);
	for (i = 0; i < CEC_NUM_EVENTS; i++)
		INIT_LIST_HEAD(&fh->events[i]);
	mutex_init(&fh->lock);
	init_waitqueue_head(&fh->wait);

@@ -544,6 +555,7 @@ static int cec_release(struct inode *inode, struct file *filp)
	struct cec_devnode *devnode = cec_devnode_data(filp);
	struct cec_adapter *adap = to_cec_adapter(devnode);
	struct cec_fh *fh = filp->private_data;
	unsigned int i;

	mutex_lock(&adap->lock);
	if (adap->cec_initiator == fh)
@@ -585,6 +597,16 @@ static int cec_release(struct inode *inode, struct file *filp)
		list_del(&entry->list);
		kfree(entry);
	}
	for (i = CEC_NUM_CORE_EVENTS; i < CEC_NUM_EVENTS; i++) {
		while (!list_empty(&fh->events[i])) {
			struct cec_event_entry *entry =
				list_first_entry(&fh->events[i],
						 struct cec_event_entry, list);

			list_del(&entry->list);
			kfree(entry);
		}
	}
	kfree(fh);

	cec_put_device(devnode);
+11 −3
Original line number Diff line number Diff line
@@ -81,7 +81,13 @@ struct cec_msg_entry {
	struct cec_msg		msg;
};

#define CEC_NUM_EVENTS		CEC_EVENT_LOST_MSGS
struct cec_event_entry {
	struct list_head	list;
	struct cec_event	ev;
};

#define CEC_NUM_CORE_EVENTS 2
#define CEC_NUM_EVENTS CEC_EVENT_PIN_HIGH

struct cec_fh {
	struct list_head	list;
@@ -92,9 +98,11 @@ struct cec_fh {

	/* Events */
	wait_queue_head_t	wait;
	unsigned int		pending_events;
	struct cec_event	events[CEC_NUM_EVENTS];
	struct mutex		lock;
	struct list_head	events[CEC_NUM_EVENTS]; /* queued events */
	u8			queued_events[CEC_NUM_EVENTS];
	unsigned int		total_queued_events;
	struct cec_event_entry	core_events[CEC_NUM_CORE_EVENTS];
	struct list_head	msgs; /* queued messages */
	unsigned int		queued_msgs;
};
+2 −1
Original line number Diff line number Diff line
@@ -412,6 +412,7 @@ struct cec_log_addrs {
#define CEC_EVENT_PIN_HIGH		4

#define CEC_EVENT_FL_INITIAL_STATE	(1 << 0)
#define CEC_EVENT_FL_DROPPED_EVENTS	(1 << 1)

/**
 * struct cec_event_state_change - used when the CEC adapter changes state.
@@ -424,7 +425,7 @@ struct cec_event_state_change {
};

/**
 * struct cec_event_lost_msgs - tells you how many messages were lost due.
 * struct cec_event_lost_msgs - tells you how many messages were lost.
 * @lost_msgs: how many messages were lost.
 */
struct cec_event_lost_msgs {