From 6a0cb1bc106fc07ce0443303bcdb7f7da5131e5c Mon Sep 17 00:00:00 2001 From: Hannes Reinecke Date: Tue, 18 Oct 2016 15:40:33 +0900 Subject: block: Implement support for zoned block devices Implement zoned block device zone information reporting and reset. Zone information are reported as struct blk_zone. This implementation does not differentiate between host-aware and host-managed device models and is valid for both. Two functions are provided: blkdev_report_zones for discovering the zone configuration of a zoned block device, and blkdev_reset_zones for resetting the write pointer of sequential zones. The helper function blk_queue_zone_size and bdev_zone_size are also provided for, as the name suggest, obtaining the zone size (in 512B sectors) of the zones of the device. Signed-off-by: Hannes Reinecke [Damien: * Removed the zone cache * Implement report zones operation based on earlier proposal by Shaun Tancheff ] Signed-off-by: Damien Le Moal Reviewed-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Reviewed-by: Shaun Tancheff Tested-by: Shaun Tancheff Signed-off-by: Jens Axboe --- include/uapi/linux/Kbuild | 1 + 1 file changed, 1 insertion(+) (limited to 'include/uapi/linux/Kbuild') diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index 6965d0909554..b2166f283da9 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -70,6 +70,7 @@ header-y += bfs_fs.h header-y += binfmts.h header-y += blkpg.h header-y += blktrace_api.h +header-y += blkzoned.h header-y += bpf_common.h header-y += bpf_perf_event.h header-y += bpf.h -- cgit v1.3-7-g2ca7 From 0dbacebede1e4e44bf500f94d692fad05eb2c293 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 2 Nov 2016 08:25:28 -0200 Subject: [media] cec: move the CEC framework out of staging and to media The last open issues have been addressed, so it is time to move this out of staging and into the mainline and to move the public cec headers to include/uapi/linux. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/media/Makefile | 2 +- drivers/media/Kconfig | 16 + drivers/media/Makefile | 4 + drivers/media/cec/Makefile | 5 + drivers/media/cec/cec-adap.c | 1856 ++++++++++++++++++++++++++++ drivers/media/cec/cec-api.c | 588 +++++++++ drivers/media/cec/cec-core.c | 411 +++++++ drivers/media/cec/cec-priv.h | 56 + drivers/media/i2c/Kconfig | 6 +- drivers/media/platform/vivid/Kconfig | 2 +- drivers/staging/media/Kconfig | 2 - drivers/staging/media/Makefile | 1 - drivers/staging/media/cec/Kconfig | 12 - drivers/staging/media/cec/Makefile | 5 - drivers/staging/media/cec/TODO | 9 - drivers/staging/media/cec/cec-adap.c | 1856 ---------------------------- drivers/staging/media/cec/cec-api.c | 588 --------- drivers/staging/media/cec/cec-core.c | 411 ------- drivers/staging/media/cec/cec-priv.h | 56 - drivers/staging/media/pulse8-cec/Kconfig | 2 +- drivers/staging/media/s5p-cec/Kconfig | 2 +- drivers/staging/media/st-cec/Kconfig | 2 +- include/linux/cec-funcs.h | 1971 ------------------------------ include/linux/cec.h | 1071 ---------------- include/media/cec.h | 2 +- include/uapi/linux/Kbuild | 2 + include/uapi/linux/cec-funcs.h | 1965 +++++++++++++++++++++++++++++ include/uapi/linux/cec.h | 1065 ++++++++++++++++ 28 files changed, 5977 insertions(+), 5991 deletions(-) create mode 100644 drivers/media/cec/Makefile create mode 100644 drivers/media/cec/cec-adap.c create mode 100644 drivers/media/cec/cec-api.c create mode 100644 drivers/media/cec/cec-core.c create mode 100644 drivers/media/cec/cec-priv.h delete mode 100644 drivers/staging/media/cec/Kconfig delete mode 100644 drivers/staging/media/cec/Makefile delete mode 100644 drivers/staging/media/cec/TODO delete mode 100644 drivers/staging/media/cec/cec-adap.c delete mode 100644 drivers/staging/media/cec/cec-api.c delete mode 100644 drivers/staging/media/cec/cec-core.c delete mode 100644 drivers/staging/media/cec/cec-priv.h delete mode 100644 include/linux/cec-funcs.h delete mode 100644 include/linux/cec.h create mode 100644 include/uapi/linux/cec-funcs.h create mode 100644 include/uapi/linux/cec.h (limited to 'include/uapi/linux/Kbuild') diff --git a/Documentation/media/Makefile b/Documentation/media/Makefile index a7fb35291f6c..61afa052c501 100644 --- a/Documentation/media/Makefile +++ b/Documentation/media/Makefile @@ -51,7 +51,7 @@ $(BUILDDIR)/videodev2.h.rst: ${UAPI}/videodev2.h ${PARSER} $(SRC_DIR)/videodev2. $(BUILDDIR)/media.h.rst: ${UAPI}/media.h ${PARSER} $(SRC_DIR)/media.h.rst.exceptions @$($(quiet)gen_rst) -$(BUILDDIR)/cec.h.rst: ${KAPI}/cec.h ${PARSER} $(SRC_DIR)/cec.h.rst.exceptions +$(BUILDDIR)/cec.h.rst: ${UAPI}/cec.h ${PARSER} $(SRC_DIR)/cec.h.rst.exceptions @$($(quiet)gen_rst) $(BUILDDIR)/lirc.h.rst: ${UAPI}/lirc.h ${PARSER} $(SRC_DIR)/lirc.h.rst.exceptions diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index 7b8540291217..bc643cbf813e 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -80,6 +80,22 @@ config MEDIA_RC_SUPPORT Say Y when you have a TV or an IR device. +config MEDIA_CEC_SUPPORT + bool "HDMI CEC support" + select MEDIA_CEC_EDID + ---help--- + Enable support for HDMI CEC (Consumer Electronics Control), + which is an optional HDMI feature. + + Say Y when you have an HDMI receiver, transmitter or a USB CEC + adapter that supports HDMI CEC. + +config MEDIA_CEC_DEBUG + bool "HDMI CEC debugfs interface" + depends on MEDIA_CEC_SUPPORT && DEBUG_FS + ---help--- + Turns on the DebugFS interface for CEC devices. + config MEDIA_CEC_EDID bool diff --git a/drivers/media/Makefile b/drivers/media/Makefile index 0deaa93efdee..d87ccb8eeabe 100644 --- a/drivers/media/Makefile +++ b/drivers/media/Makefile @@ -6,6 +6,10 @@ ifeq ($(CONFIG_MEDIA_CEC_EDID),y) obj-$(CONFIG_MEDIA_SUPPORT) += cec-edid.o endif +ifeq ($(CONFIG_MEDIA_CEC_SUPPORT),y) + obj-$(CONFIG_MEDIA_SUPPORT) += cec/ +endif + media-objs := media-device.o media-devnode.o media-entity.o # diff --git a/drivers/media/cec/Makefile b/drivers/media/cec/Makefile new file mode 100644 index 000000000000..d6686337275f --- /dev/null +++ b/drivers/media/cec/Makefile @@ -0,0 +1,5 @@ +cec-objs := cec-core.o cec-adap.o cec-api.o + +ifeq ($(CONFIG_MEDIA_CEC_SUPPORT),y) + obj-$(CONFIG_MEDIA_SUPPORT) += cec.o +endif diff --git a/drivers/media/cec/cec-adap.c b/drivers/media/cec/cec-adap.c new file mode 100644 index 000000000000..054cd06e2247 --- /dev/null +++ b/drivers/media/cec/cec-adap.c @@ -0,0 +1,1856 @@ +/* + * cec-adap.c - HDMI Consumer Electronics Control framework - CEC adapter + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cec-priv.h" + +static int cec_report_features(struct cec_adapter *adap, unsigned int la_idx); +static int cec_report_phys_addr(struct cec_adapter *adap, unsigned int la_idx); + +/* + * 400 ms is the time it takes for one 16 byte message to be + * transferred and 5 is the maximum number of retries. Add + * another 100 ms as a margin. So if the transmit doesn't + * finish before that time something is really wrong and we + * have to time out. + * + * This is a sign that something it really wrong and a warning + * will be issued. + */ +#define CEC_XFER_TIMEOUT_MS (5 * 400 + 100) + +#define call_op(adap, op, arg...) \ + (adap->ops->op ? adap->ops->op(adap, ## arg) : 0) + +#define call_void_op(adap, op, arg...) \ + do { \ + if (adap->ops->op) \ + adap->ops->op(adap, ## arg); \ + } while (0) + +static int cec_log_addr2idx(const struct cec_adapter *adap, u8 log_addr) +{ + int i; + + for (i = 0; i < adap->log_addrs.num_log_addrs; i++) + if (adap->log_addrs.log_addr[i] == log_addr) + return i; + return -1; +} + +static unsigned int cec_log_addr2dev(const struct cec_adapter *adap, u8 log_addr) +{ + int i = cec_log_addr2idx(adap, log_addr); + + return adap->log_addrs.primary_device_type[i < 0 ? 0 : i]; +} + +/* + * 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. + */ +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]; + + if (ts == 0) + ts = ktime_get_ns(); + + mutex_lock(&fh->lock); + 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; + goto unlock; + } + + /* + * Intermediate states are not interesting, so just + * overwrite any older event. + */ + *ev = *new_ev; + ev->ts = ts; + fh->pending_events |= 1 << new_ev->event; + +unlock: + mutex_unlock(&fh->lock); + wake_up_interruptible(&fh->wait); +} + +/* Queue a new event for all open filehandles. */ +static void cec_queue_event(struct cec_adapter *adap, + const struct cec_event *ev) +{ + u64 ts = ktime_get_ns(); + struct cec_fh *fh; + + mutex_lock(&adap->devnode.lock); + list_for_each_entry(fh, &adap->devnode.fhs, list) + cec_queue_event_fh(fh, ev, ts); + mutex_unlock(&adap->devnode.lock); +} + +/* + * Queue a new message for this filehandle. If there is no more room + * in the queue, then send the LOST_MSGS event instead. + */ +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, + .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; + + 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; + } + fh->queued_msgs++; + mutex_unlock(&fh->lock); + wake_up_interruptible(&fh->wait); + return; + +lost_msgs: + mutex_unlock(&fh->lock); + cec_queue_event_fh(fh, &ev_lost_msg, 0); +} + +/* + * Queue the message for those filehandles that are in monitor mode. + * If valid_la is true (this message is for us or was sent by us), + * then pass it on to any monitoring filehandle. If this message + * isn't for us or from us, then only give it to filehandles that + * are in MONITOR_ALL mode. + * + * This can only happen if the CEC_CAP_MONITOR_ALL capability is + * set and the CEC adapter was placed in 'monitor all' mode. + */ +static void cec_queue_msg_monitor(struct cec_adapter *adap, + const struct cec_msg *msg, + bool valid_la) +{ + struct cec_fh *fh; + u32 monitor_mode = valid_la ? CEC_MODE_MONITOR : + CEC_MODE_MONITOR_ALL; + + mutex_lock(&adap->devnode.lock); + list_for_each_entry(fh, &adap->devnode.fhs, list) { + if (fh->mode_follower >= monitor_mode) + cec_queue_msg_fh(fh, msg); + } + mutex_unlock(&adap->devnode.lock); +} + +/* + * Queue the message for follower filehandles. + */ +static void cec_queue_msg_followers(struct cec_adapter *adap, + const struct cec_msg *msg) +{ + struct cec_fh *fh; + + mutex_lock(&adap->devnode.lock); + list_for_each_entry(fh, &adap->devnode.fhs, list) { + if (fh->mode_follower == CEC_MODE_FOLLOWER) + cec_queue_msg_fh(fh, msg); + } + mutex_unlock(&adap->devnode.lock); +} + +/* Notify userspace of an adapter state change. */ +static void cec_post_state_event(struct cec_adapter *adap) +{ + struct cec_event ev = { + .event = CEC_EVENT_STATE_CHANGE, + }; + + ev.state_change.phys_addr = adap->phys_addr; + ev.state_change.log_addr_mask = adap->log_addrs.log_addr_mask; + cec_queue_event(adap, &ev); +} + +/* + * A CEC transmit (and a possible wait for reply) completed. + * If this was in blocking mode, then complete it, otherwise + * queue the message for userspace to dequeue later. + * + * This function is called with adap->lock held. + */ +static void cec_data_completed(struct cec_data *data) +{ + /* + * Delete this transmit from the filehandle's xfer_list since + * we're done with it. + * + * Note that if the filehandle is closed before this transmit + * finished, then the release() function will set data->fh to NULL. + * Without that we would be referring to a closed filehandle. + */ + if (data->fh) + list_del(&data->xfer_list); + + if (data->blocking) { + /* + * Someone is blocking so mark the message as completed + * and call complete. + */ + data->completed = true; + complete(&data->c); + } else { + /* + * No blocking, so just queue the message if needed and + * free the memory. + */ + if (data->fh) + cec_queue_msg_fh(data->fh, &data->msg); + kfree(data); + } +} + +/* + * A pending CEC transmit needs to be cancelled, either because the CEC + * adapter is disabled or the transmit takes an impossibly long time to + * finish. + * + * This function is called with adap->lock held. + */ +static void cec_data_cancel(struct cec_data *data) +{ + /* + * It's either the current transmit, or it is a pending + * transmit. Take the appropriate action to clear it. + */ + if (data->adap->transmitting == data) { + data->adap->transmitting = NULL; + } else { + list_del_init(&data->list); + if (!(data->msg.tx_status & CEC_TX_STATUS_OK)) + data->adap->transmit_queue_sz--; + } + + /* Mark it as an error */ + data->msg.tx_ts = ktime_get_ns(); + data->msg.tx_status = CEC_TX_STATUS_ERROR | + CEC_TX_STATUS_MAX_RETRIES; + data->attempts = 0; + data->msg.tx_error_cnt = 1; + /* Queue transmitted message for monitoring purposes */ + cec_queue_msg_monitor(data->adap, &data->msg, 1); + + cec_data_completed(data); +} + +/* + * Main CEC state machine + * + * Wait until the thread should be stopped, or we are not transmitting and + * a new transmit message is queued up, in which case we start transmitting + * that message. When the adapter finished transmitting the message it will + * call cec_transmit_done(). + * + * If the adapter is disabled, then remove all queued messages instead. + * + * If the current transmit times out, then cancel that transmit. + */ +int cec_thread_func(void *_adap) +{ + struct cec_adapter *adap = _adap; + + for (;;) { + unsigned int signal_free_time; + struct cec_data *data; + bool timeout = false; + u8 attempts; + + if (adap->transmitting) { + int err; + + /* + * We are transmitting a message, so add a timeout + * to prevent the state machine to get stuck waiting + * for this message to finalize and add a check to + * see if the adapter is disabled in which case the + * transmit should be canceled. + */ + err = wait_event_interruptible_timeout(adap->kthread_waitq, + kthread_should_stop() || + (!adap->is_configured && !adap->is_configuring) || + (!adap->transmitting && + !list_empty(&adap->transmit_queue)), + msecs_to_jiffies(CEC_XFER_TIMEOUT_MS)); + timeout = err == 0; + } else { + /* Otherwise we just wait for something to happen. */ + wait_event_interruptible(adap->kthread_waitq, + kthread_should_stop() || + (!adap->transmitting && + !list_empty(&adap->transmit_queue))); + } + + mutex_lock(&adap->lock); + + if ((!adap->is_configured && !adap->is_configuring) || + kthread_should_stop()) { + /* + * If the adapter is disabled, or we're asked to stop, + * then cancel any pending transmits. + */ + while (!list_empty(&adap->transmit_queue)) { + data = list_first_entry(&adap->transmit_queue, + struct cec_data, list); + cec_data_cancel(data); + } + if (adap->transmitting) + cec_data_cancel(adap->transmitting); + + /* + * Cancel the pending timeout work. We have to unlock + * the mutex when flushing the work since + * cec_wait_timeout() will take it. This is OK since + * no new entries can be added to wait_queue as long + * as adap->transmitting is NULL, which it is due to + * the cec_data_cancel() above. + */ + while (!list_empty(&adap->wait_queue)) { + data = list_first_entry(&adap->wait_queue, + struct cec_data, list); + + if (!cancel_delayed_work(&data->work)) { + mutex_unlock(&adap->lock); + flush_scheduled_work(); + mutex_lock(&adap->lock); + } + cec_data_cancel(data); + } + goto unlock; + } + + if (adap->transmitting && timeout) { + /* + * If we timeout, then log that. This really shouldn't + * happen and is an indication of a faulty CEC adapter + * driver, or the CEC bus is in some weird state. + */ + dprintk(0, "message %*ph timed out!\n", + adap->transmitting->msg.len, + adap->transmitting->msg.msg); + /* Just give up on this. */ + cec_data_cancel(adap->transmitting); + goto unlock; + } + + /* + * If we are still transmitting, or there is nothing new to + * transmit, then just continue waiting. + */ + if (adap->transmitting || list_empty(&adap->transmit_queue)) + goto unlock; + + /* Get a new message to transmit */ + data = list_first_entry(&adap->transmit_queue, + struct cec_data, list); + list_del_init(&data->list); + adap->transmit_queue_sz--; + /* Make this the current transmitting message */ + adap->transmitting = data; + + /* + * Suggested number of attempts as per the CEC 2.0 spec: + * 4 attempts is the default, except for 'secondary poll + * messages', i.e. poll messages not sent during the adapter + * configuration phase when it allocates logical addresses. + */ + if (data->msg.len == 1 && adap->is_configured) + attempts = 2; + else + attempts = 4; + + /* Set the suggested signal free time */ + if (data->attempts) { + /* should be >= 3 data bit periods for a retry */ + signal_free_time = CEC_SIGNAL_FREE_TIME_RETRY; + } else if (data->new_initiator) { + /* should be >= 5 data bit periods for new initiator */ + signal_free_time = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR; + } else { + /* + * should be >= 7 data bit periods for sending another + * frame immediately after another. + */ + signal_free_time = CEC_SIGNAL_FREE_TIME_NEXT_XFER; + } + if (data->attempts == 0) + data->attempts = attempts; + + /* Tell the adapter to transmit, cancel on error */ + if (adap->ops->adap_transmit(adap, data->attempts, + signal_free_time, &data->msg)) + cec_data_cancel(data); + +unlock: + mutex_unlock(&adap->lock); + + if (kthread_should_stop()) + break; + } + return 0; +} + +/* + * Called by the CEC adapter if a transmit finished. + */ +void cec_transmit_done(struct cec_adapter *adap, u8 status, u8 arb_lost_cnt, + u8 nack_cnt, u8 low_drive_cnt, u8 error_cnt) +{ + struct cec_data *data; + struct cec_msg *msg; + u64 ts = ktime_get_ns(); + + dprintk(2, "cec_transmit_done %02x\n", status); + mutex_lock(&adap->lock); + data = adap->transmitting; + if (!data) { + /* + * This can happen if a transmit was issued and the cable is + * unplugged while the transmit is ongoing. Ignore this + * transmit in that case. + */ + dprintk(1, "cec_transmit_done without an ongoing transmit!\n"); + goto unlock; + } + + msg = &data->msg; + + /* Drivers must fill in the status! */ + WARN_ON(status == 0); + msg->tx_ts = ts; + msg->tx_status |= status; + msg->tx_arb_lost_cnt += arb_lost_cnt; + msg->tx_nack_cnt += nack_cnt; + msg->tx_low_drive_cnt += low_drive_cnt; + msg->tx_error_cnt += error_cnt; + + /* Mark that we're done with this transmit */ + adap->transmitting = NULL; + + /* + * If there are still retry attempts left and there was an error and + * the hardware didn't signal that it retried itself (by setting + * CEC_TX_STATUS_MAX_RETRIES), then we will retry ourselves. + */ + if (data->attempts > 1 && + !(status & (CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_OK))) { + /* Retry this message */ + data->attempts--; + /* Add the message in front of the transmit queue */ + list_add(&data->list, &adap->transmit_queue); + adap->transmit_queue_sz++; + goto wake_thread; + } + + data->attempts = 0; + + /* Always set CEC_TX_STATUS_MAX_RETRIES on error */ + if (!(status & CEC_TX_STATUS_OK)) + msg->tx_status |= CEC_TX_STATUS_MAX_RETRIES; + + /* Queue transmitted message for monitoring purposes */ + cec_queue_msg_monitor(adap, msg, 1); + + if ((status & CEC_TX_STATUS_OK) && adap->is_configured && + msg->timeout) { + /* + * Queue the message into the wait queue if we want to wait + * for a reply. + */ + list_add_tail(&data->list, &adap->wait_queue); + schedule_delayed_work(&data->work, + msecs_to_jiffies(msg->timeout)); + } else { + /* Otherwise we're done */ + cec_data_completed(data); + } + +wake_thread: + /* + * Wake up the main thread to see if another message is ready + * for transmitting or to retry the current message. + */ + wake_up_interruptible(&adap->kthread_waitq); +unlock: + mutex_unlock(&adap->lock); +} +EXPORT_SYMBOL_GPL(cec_transmit_done); + +/* + * Called when waiting for a reply times out. + */ +static void cec_wait_timeout(struct work_struct *work) +{ + struct cec_data *data = container_of(work, struct cec_data, work.work); + struct cec_adapter *adap = data->adap; + + mutex_lock(&adap->lock); + /* + * Sanity check in case the timeout and the arrival of the message + * happened at the same time. + */ + if (list_empty(&data->list)) + goto unlock; + + /* Mark the message as timed out */ + list_del_init(&data->list); + data->msg.rx_ts = ktime_get_ns(); + data->msg.rx_status = CEC_RX_STATUS_TIMEOUT; + cec_data_completed(data); +unlock: + mutex_unlock(&adap->lock); +} + +/* + * Transmit a message. The fh argument may be NULL if the transmit is not + * associated with a specific filehandle. + * + * This function is called with adap->lock held. + */ +int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, + struct cec_fh *fh, bool block) +{ + struct cec_data *data; + u8 last_initiator = 0xff; + unsigned int timeout; + int res = 0; + + msg->rx_ts = 0; + msg->tx_ts = 0; + msg->rx_status = 0; + msg->tx_status = 0; + msg->tx_arb_lost_cnt = 0; + msg->tx_nack_cnt = 0; + msg->tx_low_drive_cnt = 0; + msg->tx_error_cnt = 0; + msg->sequence = ++adap->sequence; + if (!msg->sequence) + msg->sequence = ++adap->sequence; + + if (msg->reply && msg->timeout == 0) { + /* Make sure the timeout isn't 0. */ + msg->timeout = 1000; + } + + /* Sanity checks */ + if (msg->len == 0 || msg->len > CEC_MAX_MSG_SIZE) { + dprintk(1, "cec_transmit_msg: invalid length %d\n", msg->len); + return -EINVAL; + } + if (msg->timeout && msg->len == 1) { + dprintk(1, "cec_transmit_msg: can't reply for poll msg\n"); + return -EINVAL; + } + memset(msg->msg + msg->len, 0, sizeof(msg->msg) - msg->len); + if (msg->len == 1) { + if (cec_msg_initiator(msg) != 0xf || + cec_msg_destination(msg) == 0xf) { + dprintk(1, "cec_transmit_msg: invalid poll message\n"); + return -EINVAL; + } + if (cec_has_log_addr(adap, cec_msg_destination(msg))) { + /* + * If the destination is a logical address our adapter + * has already claimed, then just NACK this. + * It depends on the hardware what it will do with a + * POLL to itself (some OK this), so it is just as + * easy to handle it here so the behavior will be + * consistent. + */ + msg->tx_ts = ktime_get_ns(); + msg->tx_status = CEC_TX_STATUS_NACK | + CEC_TX_STATUS_MAX_RETRIES; + msg->tx_nack_cnt = 1; + return 0; + } + } + if (msg->len > 1 && !cec_msg_is_broadcast(msg) && + cec_has_log_addr(adap, cec_msg_destination(msg))) { + dprintk(1, "cec_transmit_msg: destination is the adapter itself\n"); + return -EINVAL; + } + if (cec_msg_initiator(msg) != 0xf && + !cec_has_log_addr(adap, cec_msg_initiator(msg))) { + dprintk(1, "cec_transmit_msg: initiator has unknown logical address %d\n", + cec_msg_initiator(msg)); + return -EINVAL; + } + if (!adap->is_configured && !adap->is_configuring) + return -ENONET; + + if (adap->transmit_queue_sz >= CEC_MAX_MSG_TX_QUEUE_SZ) + return -EBUSY; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (msg->len > 1 && msg->msg[1] == CEC_MSG_CDC_MESSAGE) { + msg->msg[2] = adap->phys_addr >> 8; + msg->msg[3] = adap->phys_addr & 0xff; + } + + if (msg->timeout) + dprintk(2, "cec_transmit_msg: %*ph (wait for 0x%02x%s)\n", + msg->len, msg->msg, msg->reply, !block ? ", nb" : ""); + else + dprintk(2, "cec_transmit_msg: %*ph%s\n", + msg->len, msg->msg, !block ? " (nb)" : ""); + + data->msg = *msg; + data->fh = fh; + data->adap = adap; + data->blocking = block; + + /* + * Determine if this message follows a message from the same + * initiator. Needed to determine the free signal time later on. + */ + if (msg->len > 1) { + if (!(list_empty(&adap->transmit_queue))) { + const struct cec_data *last; + + last = list_last_entry(&adap->transmit_queue, + const struct cec_data, list); + last_initiator = cec_msg_initiator(&last->msg); + } else if (adap->transmitting) { + last_initiator = + cec_msg_initiator(&adap->transmitting->msg); + } + } + data->new_initiator = last_initiator != cec_msg_initiator(msg); + init_completion(&data->c); + INIT_DELAYED_WORK(&data->work, cec_wait_timeout); + + if (fh) + list_add_tail(&data->xfer_list, &fh->xfer_list); + list_add_tail(&data->list, &adap->transmit_queue); + adap->transmit_queue_sz++; + if (!adap->transmitting) + wake_up_interruptible(&adap->kthread_waitq); + + /* All done if we don't need to block waiting for completion */ + if (!block) + 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. + */ + mutex_unlock(&adap->lock); + res = wait_for_completion_killable_timeout(&data->c, + msecs_to_jiffies(timeout)); + mutex_lock(&adap->lock); + + if (data->completed) { + /* The transmit completed (possibly with an error) */ + *msg = data->msg; + 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 */ + /* Check if the reply or the transmit failed */ + if (msg->timeout && (msg->tx_status & CEC_TX_STATUS_OK)) + msg->rx_status = CEC_RX_STATUS_TIMEOUT; + else + msg->tx_status = CEC_TX_STATUS_MAX_RETRIES; + } + return res > 0 ? 0 : res; +} + +/* Helper function to be used by drivers and this framework. */ +int cec_transmit_msg(struct cec_adapter *adap, struct cec_msg *msg, + bool block) +{ + int ret; + + mutex_lock(&adap->lock); + ret = cec_transmit_msg_fh(adap, msg, NULL, block); + mutex_unlock(&adap->lock); + return ret; +} +EXPORT_SYMBOL_GPL(cec_transmit_msg); + +/* + * I don't like forward references but without this the low-level + * cec_received_msg() function would come after a bunch of high-level + * CEC protocol handling functions. That was very confusing. + */ +static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, + bool is_reply); + +#define DIRECTED 0x80 +#define BCAST1_4 0x40 +#define BCAST2_0 0x20 /* broadcast only allowed for >= 2.0 */ +#define BCAST (BCAST1_4 | BCAST2_0) +#define BOTH (BCAST | DIRECTED) + +/* + * Specify minimum length and whether the message is directed, broadcast + * or both. Messages that do not match the criteria are ignored as per + * the CEC specification. + */ +static const u8 cec_msg_size[256] = { + [CEC_MSG_ACTIVE_SOURCE] = 4 | BCAST, + [CEC_MSG_IMAGE_VIEW_ON] = 2 | DIRECTED, + [CEC_MSG_TEXT_VIEW_ON] = 2 | DIRECTED, + [CEC_MSG_INACTIVE_SOURCE] = 4 | DIRECTED, + [CEC_MSG_REQUEST_ACTIVE_SOURCE] = 2 | BCAST, + [CEC_MSG_ROUTING_CHANGE] = 6 | BCAST, + [CEC_MSG_ROUTING_INFORMATION] = 4 | BCAST, + [CEC_MSG_SET_STREAM_PATH] = 4 | BCAST, + [CEC_MSG_STANDBY] = 2 | BOTH, + [CEC_MSG_RECORD_OFF] = 2 | DIRECTED, + [CEC_MSG_RECORD_ON] = 3 | DIRECTED, + [CEC_MSG_RECORD_STATUS] = 3 | DIRECTED, + [CEC_MSG_RECORD_TV_SCREEN] = 2 | DIRECTED, + [CEC_MSG_CLEAR_ANALOGUE_TIMER] = 13 | DIRECTED, + [CEC_MSG_CLEAR_DIGITAL_TIMER] = 16 | DIRECTED, + [CEC_MSG_CLEAR_EXT_TIMER] = 13 | DIRECTED, + [CEC_MSG_SET_ANALOGUE_TIMER] = 13 | DIRECTED, + [CEC_MSG_SET_DIGITAL_TIMER] = 16 | DIRECTED, + [CEC_MSG_SET_EXT_TIMER] = 13 | DIRECTED, + [CEC_MSG_SET_TIMER_PROGRAM_TITLE] = 2 | DIRECTED, + [CEC_MSG_TIMER_CLEARED_STATUS] = 3 | DIRECTED, + [CEC_MSG_TIMER_STATUS] = 3 | DIRECTED, + [CEC_MSG_CEC_VERSION] = 3 | DIRECTED, + [CEC_MSG_GET_CEC_VERSION] = 2 | DIRECTED, + [CEC_MSG_GIVE_PHYSICAL_ADDR] = 2 | DIRECTED, + [CEC_MSG_GET_MENU_LANGUAGE] = 2 | DIRECTED, + [CEC_MSG_REPORT_PHYSICAL_ADDR] = 5 | BCAST, + [CEC_MSG_SET_MENU_LANGUAGE] = 5 | BCAST, + [CEC_MSG_REPORT_FEATURES] = 6 | BCAST, + [CEC_MSG_GIVE_FEATURES] = 2 | DIRECTED, + [CEC_MSG_DECK_CONTROL] = 3 | DIRECTED, + [CEC_MSG_DECK_STATUS] = 3 | DIRECTED, + [CEC_MSG_GIVE_DECK_STATUS] = 3 | DIRECTED, + [CEC_MSG_PLAY] = 3 | DIRECTED, + [CEC_MSG_GIVE_TUNER_DEVICE_STATUS] = 3 | DIRECTED, + [CEC_MSG_SELECT_ANALOGUE_SERVICE] = 6 | DIRECTED, + [CEC_MSG_SELECT_DIGITAL_SERVICE] = 9 | DIRECTED, + [CEC_MSG_TUNER_DEVICE_STATUS] = 7 | DIRECTED, + [CEC_MSG_TUNER_STEP_DECREMENT] = 2 | DIRECTED, + [CEC_MSG_TUNER_STEP_INCREMENT] = 2 | DIRECTED, + [CEC_MSG_DEVICE_VENDOR_ID] = 5 | BCAST, + [CEC_MSG_GIVE_DEVICE_VENDOR_ID] = 2 | DIRECTED, + [CEC_MSG_VENDOR_COMMAND] = 2 | DIRECTED, + [CEC_MSG_VENDOR_COMMAND_WITH_ID] = 5 | BOTH, + [CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN] = 2 | BOTH, + [CEC_MSG_VENDOR_REMOTE_BUTTON_UP] = 2 | BOTH, + [CEC_MSG_SET_OSD_STRING] = 3 | DIRECTED, + [CEC_MSG_GIVE_OSD_NAME] = 2 | DIRECTED, + [CEC_MSG_SET_OSD_NAME] = 2 | DIRECTED, + [CEC_MSG_MENU_REQUEST] = 3 | DIRECTED, + [CEC_MSG_MENU_STATUS] = 3 | DIRECTED, + [CEC_MSG_USER_CONTROL_PRESSED] = 3 | DIRECTED, + [CEC_MSG_USER_CONTROL_RELEASED] = 2 | DIRECTED, + [CEC_MSG_GIVE_DEVICE_POWER_STATUS] = 2 | DIRECTED, + [CEC_MSG_REPORT_POWER_STATUS] = 3 | DIRECTED | BCAST2_0, + [CEC_MSG_FEATURE_ABORT] = 4 | DIRECTED, + [CEC_MSG_ABORT] = 2 | DIRECTED, + [CEC_MSG_GIVE_AUDIO_STATUS] = 2 | DIRECTED, + [CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS] = 2 | DIRECTED, + [CEC_MSG_REPORT_AUDIO_STATUS] = 3 | DIRECTED, + [CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR] = 2 | DIRECTED, + [CEC_MSG_REQUEST_SHORT_AUDIO_DESCRIPTOR] = 2 | DIRECTED, + [CEC_MSG_SET_SYSTEM_AUDIO_MODE] = 3 | BOTH, + [CEC_MSG_SYSTEM_AUDIO_MODE_REQUEST] = 2 | DIRECTED, + [CEC_MSG_SYSTEM_AUDIO_MODE_STATUS] = 3 | DIRECTED, + [CEC_MSG_SET_AUDIO_RATE] = 3 | DIRECTED, + [CEC_MSG_INITIATE_ARC] = 2 | DIRECTED, + [CEC_MSG_REPORT_ARC_INITIATED] = 2 | DIRECTED, + [CEC_MSG_REPORT_ARC_TERMINATED] = 2 | DIRECTED, + [CEC_MSG_REQUEST_ARC_INITIATION] = 2 | DIRECTED, + [CEC_MSG_REQUEST_ARC_TERMINATION] = 2 | DIRECTED, + [CEC_MSG_TERMINATE_ARC] = 2 | DIRECTED, + [CEC_MSG_REQUEST_CURRENT_LATENCY] = 4 | BCAST, + [CEC_MSG_REPORT_CURRENT_LATENCY] = 7 | BCAST, + [CEC_MSG_CDC_MESSAGE] = 2 | BCAST, +}; + +/* Called by the CEC adapter if a message is received */ +void cec_received_msg(struct cec_adapter *adap, struct cec_msg *msg) +{ + struct cec_data *data; + u8 msg_init = cec_msg_initiator(msg); + u8 msg_dest = cec_msg_destination(msg); + u8 cmd = msg->msg[1]; + bool is_reply = false; + bool valid_la = true; + u8 min_len = 0; + + if (WARN_ON(!msg->len || msg->len > CEC_MAX_MSG_SIZE)) + return; + + msg->rx_ts = ktime_get_ns(); + msg->rx_status = CEC_RX_STATUS_OK; + msg->sequence = msg->reply = msg->timeout = 0; + msg->tx_status = 0; + msg->tx_ts = 0; + msg->flags = 0; + memset(msg->msg + msg->len, 0, sizeof(msg->msg) - msg->len); + + mutex_lock(&adap->lock); + dprintk(2, "cec_received_msg: %*ph\n", msg->len, msg->msg); + + /* Check if this message was for us (directed or broadcast). */ + if (!cec_msg_is_broadcast(msg)) + valid_la = cec_has_log_addr(adap, msg_dest); + + /* + * Check if the length is not too short or if the message is a + * broadcast message where a directed message was expected or + * vice versa. If so, then the message has to be ignored (according + * to section CEC 7.3 and CEC 12.2). + */ + if (valid_la && msg->len > 1 && cec_msg_size[cmd]) { + u8 dir_fl = cec_msg_size[cmd] & BOTH; + + min_len = cec_msg_size[cmd] & 0x1f; + if (msg->len < min_len) + valid_la = false; + else if (!cec_msg_is_broadcast(msg) && !(dir_fl & DIRECTED)) + valid_la = false; + else if (cec_msg_is_broadcast(msg) && !(dir_fl & BCAST1_4)) + valid_la = false; + else if (cec_msg_is_broadcast(msg) && + adap->log_addrs.cec_version >= CEC_OP_CEC_VERSION_2_0 && + !(dir_fl & BCAST2_0)) + valid_la = false; + } + if (valid_la && min_len) { + /* These messages have special length requirements */ + switch (cmd) { + case CEC_MSG_TIMER_STATUS: + if (msg->msg[2] & 0x10) { + switch (msg->msg[2] & 0xf) { + case CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE: + case CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE: + if (msg->len < 5) + valid_la = false; + break; + } + } else if ((msg->msg[2] & 0xf) == CEC_OP_PROG_ERROR_DUPLICATE) { + if (msg->len < 5) + valid_la = false; + } + break; + case CEC_MSG_RECORD_ON: + switch (msg->msg[2]) { + case CEC_OP_RECORD_SRC_OWN: + break; + case CEC_OP_RECORD_SRC_DIGITAL: + if (msg->len < 10) + valid_la = false; + break; + case CEC_OP_RECORD_SRC_ANALOG: + if (msg->len < 7) + valid_la = false; + break; + case CEC_OP_RECORD_SRC_EXT_PLUG: + if (msg->len < 4) + valid_la = false; + break; + case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR: + if (msg->len < 5) + valid_la = false; + break; + } + break; + } + } + + /* It's a valid message and not a poll or CDC message */ + if (valid_la && msg->len > 1 && cmd != CEC_MSG_CDC_MESSAGE) { + bool abort = cmd == CEC_MSG_FEATURE_ABORT; + + /* The aborted command is in msg[2] */ + if (abort) + cmd = msg->msg[2]; + + /* + * Walk over all transmitted messages that are waiting for a + * reply. + */ + list_for_each_entry(data, &adap->wait_queue, list) { + struct cec_msg *dst = &data->msg; + + /* + * The *only* CEC message that has two possible replies + * is CEC_MSG_INITIATE_ARC. + * In this case allow either of the two replies. + */ + if (!abort && dst->msg[1] == CEC_MSG_INITIATE_ARC && + (cmd == CEC_MSG_REPORT_ARC_INITIATED || + cmd == CEC_MSG_REPORT_ARC_TERMINATED) && + (dst->reply == CEC_MSG_REPORT_ARC_INITIATED || + dst->reply == CEC_MSG_REPORT_ARC_TERMINATED)) + dst->reply = cmd; + + /* Does the command match? */ + if ((abort && cmd != dst->msg[1]) || + (!abort && cmd != dst->reply)) + continue; + + /* Does the addressing match? */ + if (msg_init != cec_msg_destination(dst) && + !cec_msg_is_broadcast(dst)) + continue; + + /* We got a reply */ + memcpy(dst->msg, msg->msg, msg->len); + dst->len = msg->len; + dst->rx_ts = msg->rx_ts; + dst->rx_status = msg->rx_status; + if (abort) + dst->rx_status |= CEC_RX_STATUS_FEATURE_ABORT; + msg->flags = dst->flags; + /* Remove it from the wait_queue */ + list_del_init(&data->list); + + /* Cancel the pending timeout work */ + if (!cancel_delayed_work(&data->work)) { + mutex_unlock(&adap->lock); + flush_scheduled_work(); + mutex_lock(&adap->lock); + } + /* + * Mark this as a reply, provided someone is still + * waiting for the answer. + */ + if (data->fh) + is_reply = true; + cec_data_completed(data); + break; + } + } + mutex_unlock(&adap->lock); + + /* Pass the message on to any monitoring filehandles */ + cec_queue_msg_monitor(adap, msg, valid_la); + + /* We're done if it is not for us or a poll message */ + if (!valid_la || msg->len <= 1) + return; + + if (adap->log_addrs.log_addr_mask == 0) + return; + + /* + * Process the message on the protocol level. If is_reply is true, + * then cec_receive_notify() won't pass on the reply to the listener(s) + * since that was already done by cec_data_completed() above. + */ + cec_receive_notify(adap, msg, is_reply); +} +EXPORT_SYMBOL_GPL(cec_received_msg); + +/* Logical Address Handling */ + +/* + * Attempt to claim a specific logical address. + * + * This function is called with adap->lock held. + */ +static int cec_config_log_addr(struct cec_adapter *adap, + unsigned int idx, + unsigned int log_addr) +{ + struct cec_log_addrs *las = &adap->log_addrs; + struct cec_msg msg = { }; + int err; + + if (cec_has_log_addr(adap, log_addr)) + return 0; + + /* Send poll message */ + msg.len = 1; + msg.msg[0] = 0xf0 | log_addr; + err = cec_transmit_msg_fh(adap, &msg, NULL, true); + + /* + * While trying to poll the physical address was reset + * and the adapter was unconfigured, so bail out. + */ + if (!adap->is_configuring) + return -EINTR; + + if (err) + return err; + + if (msg.tx_status & CEC_TX_STATUS_OK) + return 0; + + /* + * Message not acknowledged, so this logical + * address is free to use. + */ + err = adap->ops->adap_log_addr(adap, log_addr); + if (err) + return err; + + las->log_addr[idx] = log_addr; + las->log_addr_mask |= 1 << log_addr; + adap->phys_addrs[log_addr] = adap->phys_addr; + + dprintk(2, "claimed addr %d (%d)\n", log_addr, + las->primary_device_type[idx]); + return 1; +} + +/* + * Unconfigure the adapter: clear all logical addresses and send + * the state changed event. + * + * This function is called with adap->lock held. + */ +static void cec_adap_unconfigure(struct cec_adapter *adap) +{ + WARN_ON(adap->ops->adap_log_addr(adap, CEC_LOG_ADDR_INVALID)); + adap->log_addrs.log_addr_mask = 0; + adap->is_configuring = false; + adap->is_configured = false; + memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs)); + wake_up_interruptible(&adap->kthread_waitq); + cec_post_state_event(adap); +} + +/* + * Attempt to claim the required logical addresses. + */ +static int cec_config_thread_func(void *arg) +{ + /* The various LAs for each type of device */ + static const u8 tv_log_addrs[] = { + CEC_LOG_ADDR_TV, CEC_LOG_ADDR_SPECIFIC, + CEC_LOG_ADDR_INVALID + }; + static const u8 record_log_addrs[] = { + CEC_LOG_ADDR_RECORD_1, CEC_LOG_ADDR_RECORD_2, + CEC_LOG_ADDR_RECORD_3, + CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, + CEC_LOG_ADDR_INVALID + }; + static const u8 tuner_log_addrs[] = { + CEC_LOG_ADDR_TUNER_1, CEC_LOG_ADDR_TUNER_2, + CEC_LOG_ADDR_TUNER_3, CEC_LOG_ADDR_TUNER_4, + CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, + CEC_LOG_ADDR_INVALID + }; + static const u8 playback_log_addrs[] = { + CEC_LOG_ADDR_PLAYBACK_1, CEC_LOG_ADDR_PLAYBACK_2, + CEC_LOG_ADDR_PLAYBACK_3, + CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, + CEC_LOG_ADDR_INVALID + }; + static const u8 audiosystem_log_addrs[] = { + CEC_LOG_ADDR_AUDIOSYSTEM, + CEC_LOG_ADDR_INVALID + }; + static const u8 specific_use_log_addrs[] = { + CEC_LOG_ADDR_SPECIFIC, + CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, + CEC_LOG_ADDR_INVALID + }; + static const u8 *type2addrs[6] = { + [CEC_LOG_ADDR_TYPE_TV] = tv_log_addrs, + [CEC_LOG_ADDR_TYPE_RECORD] = record_log_addrs, + [CEC_LOG_ADDR_TYPE_TUNER] = tuner_log_addrs, + [CEC_LOG_ADDR_TYPE_PLAYBACK] = playback_log_addrs, + [CEC_LOG_ADDR_TYPE_AUDIOSYSTEM] = audiosystem_log_addrs, + [CEC_LOG_ADDR_TYPE_SPECIFIC] = specific_use_log_addrs, + }; + static const u16 type2mask[] = { + [CEC_LOG_ADDR_TYPE_TV] = CEC_LOG_ADDR_MASK_TV, + [CEC_LOG_ADDR_TYPE_RECORD] = CEC_LOG_ADDR_MASK_RECORD, + [CEC_LOG_ADDR_TYPE_TUNER] = CEC_LOG_ADDR_MASK_TUNER, + [CEC_LOG_ADDR_TYPE_PLAYBACK] = CEC_LOG_ADDR_MASK_PLAYBACK, + [CEC_LOG_ADDR_TYPE_AUDIOSYSTEM] = CEC_LOG_ADDR_MASK_AUDIOSYSTEM, + [CEC_LOG_ADDR_TYPE_SPECIFIC] = CEC_LOG_ADDR_MASK_SPECIFIC, + }; + struct cec_adapter *adap = arg; + struct cec_log_addrs *las = &adap->log_addrs; + int err; + int i, j; + + mutex_lock(&adap->lock); + dprintk(1, "physical address: %x.%x.%x.%x, claim %d logical addresses\n", + cec_phys_addr_exp(adap->phys_addr), las->num_log_addrs); + las->log_addr_mask = 0; + + if (las->log_addr_type[0] == CEC_LOG_ADDR_TYPE_UNREGISTERED) + goto configured; + + for (i = 0; i < las->num_log_addrs; i++) { + unsigned int type = las->log_addr_type[i]; + const u8 *la_list; + u8 last_la; + + /* + * The TV functionality can only map to physical address 0. + * For any other address, try the Specific functionality + * instead as per the spec. + */ + if (adap->phys_addr && type == CEC_LOG_ADDR_TYPE_TV) + type = CEC_LOG_ADDR_TYPE_SPECIFIC; + + la_list = type2addrs[type]; + last_la = las->log_addr[i]; + las->log_addr[i] = CEC_LOG_ADDR_INVALID; + if (last_la == CEC_LOG_ADDR_INVALID || + last_la == CEC_LOG_ADDR_UNREGISTERED || + !(last_la & type2mask[type])) + last_la = la_list[0]; + + err = cec_config_log_addr(adap, i, last_la); + if (err > 0) /* Reused last LA */ + continue; + + if (err < 0) + goto unconfigure; + + for (j = 0; la_list[j] != CEC_LOG_ADDR_INVALID; j++) { + /* Tried this one already, skip it */ + if (la_list[j] == last_la) + continue; + /* The backup addresses are CEC 2.0 specific */ + if ((la_list[j] == CEC_LOG_ADDR_BACKUP_1 || + la_list[j] == CEC_LOG_ADDR_BACKUP_2) && + las->cec_version < CEC_OP_CEC_VERSION_2_0) + continue; + + err = cec_config_log_addr(adap, i, la_list[j]); + if (err == 0) /* LA is in use */ + continue; + if (err < 0) + goto unconfigure; + /* Done, claimed an LA */ + break; + } + + if (la_list[j] == CEC_LOG_ADDR_INVALID) + dprintk(1, "could not claim LA %d\n", i); + } + + if (adap->log_addrs.log_addr_mask == 0 && + !(las->flags & CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK)) + goto unconfigure; + +configured: + if (adap->log_addrs.log_addr_mask == 0) { + /* Fall back to unregistered */ + las->log_addr[0] = CEC_LOG_ADDR_UNREGISTERED; + las->log_addr_mask = 1 << las->log_addr[0]; + for (i = 1; i < las->num_log_addrs; i++) + las->log_addr[i] = CEC_LOG_ADDR_INVALID; + } + adap->is_configured = true; + adap->is_configuring = false; + cec_post_state_event(adap); + mutex_unlock(&adap->lock); + + for (i = 0; i < las->num_log_addrs; i++) { + if (las->log_addr[i] == CEC_LOG_ADDR_INVALID || + (las->flags & CEC_LOG_ADDRS_FL_CDC_ONLY)) + continue; + + /* + * Report Features must come first according + * to CEC 2.0 + */ + if (las->log_addr[i] != CEC_LOG_ADDR_UNREGISTERED) + cec_report_features(adap, i); + cec_report_phys_addr(adap, i); + } + for (i = las->num_log_addrs; i < CEC_MAX_LOG_ADDRS; i++) + las->log_addr[i] = CEC_LOG_ADDR_INVALID; + mutex_lock(&adap->lock); + adap->kthread_config = NULL; + mutex_unlock(&adap->lock); + complete(&adap->config_completion); + return 0; + +unconfigure: + for (i = 0; i < las->num_log_addrs; i++) + las->log_addr[i] = CEC_LOG_ADDR_INVALID; + cec_adap_unconfigure(adap); + adap->kthread_config = NULL; + mutex_unlock(&adap->lock); + complete(&adap->config_completion); + return 0; +} + +/* + * Called from either __cec_s_phys_addr or __cec_s_log_addrs to claim the + * logical addresses. + * + * This function is called with adap->lock held. + */ +static void cec_claim_log_addrs(struct cec_adapter *adap, bool block) +{ + if (WARN_ON(adap->is_configuring || adap->is_configured)) + return; + + init_completion(&adap->config_completion); + + /* Ready to kick off the thread */ + adap->is_configuring = true; + adap->kthread_config = kthread_run(cec_config_thread_func, adap, + "ceccfg-%s", adap->name); + if (IS_ERR(adap->kthread_config)) { + adap->kthread_config = NULL; + } else if (block) { + mutex_unlock(&adap->lock); + wait_for_completion(&adap->config_completion); + mutex_lock(&adap->lock); + } +} + +/* Set a new physical address and send an event notifying userspace of this. + * + * This function is called with adap->lock held. + */ +void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) +{ + if (phys_addr == adap->phys_addr || adap->devnode.unregistered) + return; + + if (phys_addr == CEC_PHYS_ADDR_INVALID || + adap->phys_addr != CEC_PHYS_ADDR_INVALID) { + adap->phys_addr = CEC_PHYS_ADDR_INVALID; + cec_post_state_event(adap); + cec_adap_unconfigure(adap); + /* Disabling monitor all mode should always succeed */ + if (adap->monitor_all_cnt) + WARN_ON(call_op(adap, adap_monitor_all_enable, false)); + WARN_ON(adap->ops->adap_enable(adap, false)); + if (phys_addr == CEC_PHYS_ADDR_INVALID) + return; + } + + if (adap->ops->adap_enable(adap, true)) + return; + + if (adap->monitor_all_cnt && + call_op(adap, adap_monitor_all_enable, true)) { + WARN_ON(adap->ops->adap_enable(adap, false)); + return; + } + adap->phys_addr = phys_addr; + cec_post_state_event(adap); + if (adap->log_addrs.num_log_addrs) + cec_claim_log_addrs(adap, block); +} + +void cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) +{ + if (IS_ERR_OR_NULL(adap)) + return; + + mutex_lock(&adap->lock); + __cec_s_phys_addr(adap, phys_addr, block); + mutex_unlock(&adap->lock); +} +EXPORT_SYMBOL_GPL(cec_s_phys_addr); + +/* + * Called from either the ioctl or a driver to set the logical addresses. + * + * This function is called with adap->lock held. + */ +int __cec_s_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs *log_addrs, bool block) +{ + u16 type_mask = 0; + int i; + + if (adap->devnode.unregistered) + return -ENODEV; + + if (!log_addrs || log_addrs->num_log_addrs == 0) { + adap->log_addrs.num_log_addrs = 0; + cec_adap_unconfigure(adap); + return 0; + } + + if (log_addrs->flags & CEC_LOG_ADDRS_FL_CDC_ONLY) { + /* + * Sanitize log_addrs fields if a CDC-Only device is + * requested. + */ + log_addrs->num_log_addrs = 1; + log_addrs->osd_name[0] = '\0'; + log_addrs->vendor_id = CEC_VENDOR_ID_NONE; + log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED; + /* + * This is just an internal convention since a CDC-Only device + * doesn't have to be a switch. But switches already use + * unregistered, so it makes some kind of sense to pick this + * as the primary device. Since a CDC-Only device never sends + * any 'normal' CEC messages this primary device type is never + * sent over the CEC bus. + */ + log_addrs->primary_device_type[0] = CEC_OP_PRIM_DEVTYPE_SWITCH; + log_addrs->all_device_types[0] = 0; + log_addrs->features[0][0] = 0; + log_addrs->features[0][1] = 0; + } + + /* Ensure the osd name is 0-terminated */ + log_addrs->osd_name[sizeof(log_addrs->osd_name) - 1] = '\0'; + + /* Sanity checks */ + if (log_addrs->num_log_addrs > adap->available_log_addrs) { + dprintk(1, "num_log_addrs > %d\n", adap->available_log_addrs); + return -EINVAL; + } + + /* + * Vendor ID is a 24 bit number, so check if the value is + * within the correct range. + */ + if (log_addrs->vendor_id != CEC_VENDOR_ID_NONE && + (log_addrs->vendor_id & 0xff000000) != 0) + return -EINVAL; + + if (log_addrs->cec_version != CEC_OP_CEC_VERSION_1_4 && + log_addrs->cec_version != CEC_OP_CEC_VERSION_2_0) + return -EINVAL; + + if (log_addrs->num_log_addrs > 1) + for (i = 0; i < log_addrs->num_log_addrs; i++) + if (log_addrs->log_addr_type[i] == + CEC_LOG_ADDR_TYPE_UNREGISTERED) { + dprintk(1, "num_log_addrs > 1 can't be combined with unregistered LA\n"); + return -EINVAL; + } + + for (i = 0; i < log_addrs->num_log_addrs; i++) { + const u8 feature_sz = ARRAY_SIZE(log_addrs->features[0]); + u8 *features = log_addrs->features[i]; + bool op_is_dev_features = false; + + log_addrs->log_addr[i] = CEC_LOG_ADDR_INVALID; + if (type_mask & (1 << log_addrs->log_addr_type[i])) { + dprintk(1, "duplicate logical address type\n"); + return -EINVAL; + } + type_mask |= 1 << log_addrs->log_addr_type[i]; + if ((type_mask & (1 << CEC_LOG_ADDR_TYPE_RECORD)) && + (type_mask & (1 << CEC_LOG_ADDR_TYPE_PLAYBACK))) { + /* Record already contains the playback functionality */ + dprintk(1, "invalid record + playback combination\n"); + return -EINVAL; + } + if (log_addrs->primary_device_type[i] > + CEC_OP_PRIM_DEVTYPE_PROCESSOR) { + dprintk(1, "unknown primary device type\n"); + return -EINVAL; + } + if (log_addrs->primary_device_type[i] == 2) { + dprintk(1, "invalid primary device type\n"); + return -EINVAL; + } + if (log_addrs->log_addr_type[i] > CEC_LOG_ADDR_TYPE_UNREGISTERED) { + dprintk(1, "unknown logical address type\n"); + return -EINVAL; + } + for (i = 0; i < feature_sz; i++) { + if ((features[i] & 0x80) == 0) { + if (op_is_dev_features) + break; + op_is_dev_features = true; + } + } + if (!op_is_dev_features || i == feature_sz) { + dprintk(1, "malformed features\n"); + return -EINVAL; + } + /* Zero unused part of the feature array */ + memset(features + i + 1, 0, feature_sz - i - 1); + } + + if (log_addrs->cec_version >= CEC_OP_CEC_VERSION_2_0) { + if (log_addrs->num_log_addrs > 2) { + dprintk(1, "CEC 2.0 allows no more than 2 logical addresses\n"); + return -EINVAL; + } + if (log_addrs->num_log_addrs == 2) { + if (!(type_mask & ((1 << CEC_LOG_ADDR_TYPE_AUDIOSYSTEM) | + (1 << CEC_LOG_ADDR_TYPE_TV)))) { + dprintk(1, "Two LAs is only allowed for audiosystem and TV\n"); + return -EINVAL; + } + if (!(type_mask & ((1 << CEC_LOG_ADDR_TYPE_PLAYBACK) | + (1 << CEC_LOG_ADDR_TYPE_RECORD)))) { + dprintk(1, "An audiosystem/TV can only be combined with record or playback\n"); + return -EINVAL; + } + } + } + + /* Zero unused LAs */ + for (i = log_addrs->num_log_addrs; i < CEC_MAX_LOG_ADDRS; i++) { + log_addrs->primary_device_type[i] = 0; + log_addrs->log_addr_type[i] = 0; + log_addrs->all_device_types[i] = 0; + memset(log_addrs->features[i], 0, + sizeof(log_addrs->features[i])); + } + + log_addrs->log_addr_mask = adap->log_addrs.log_addr_mask; + adap->log_addrs = *log_addrs; + if (adap->phys_addr != CEC_PHYS_ADDR_INVALID) + cec_claim_log_addrs(adap, block); + return 0; +} + +int cec_s_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs *log_addrs, bool block) +{ + int err; + + mutex_lock(&adap->lock); + err = __cec_s_log_addrs(adap, log_addrs, block); + mutex_unlock(&adap->lock); + return err; +} +EXPORT_SYMBOL_GPL(cec_s_log_addrs); + +/* High-level core CEC message handling */ + +/* Transmit the Report Features message */ +static int cec_report_features(struct cec_adapter *adap, unsigned int la_idx) +{ + struct cec_msg msg = { }; + const struct cec_log_addrs *las = &adap->log_addrs; + const u8 *features = las->features[la_idx]; + bool op_is_dev_features = false; + unsigned int idx; + + /* This is 2.0 and up only */ + if (adap->log_addrs.cec_version < CEC_OP_CEC_VERSION_2_0) + return 0; + + /* Report Features */ + msg.msg[0] = (las->log_addr[la_idx] << 4) | 0x0f; + msg.len = 4; + msg.msg[1] = CEC_MSG_REPORT_FEATURES; + msg.msg[2] = adap->log_addrs.cec_version; + msg.msg[3] = las->all_device_types[la_idx]; + + /* Write RC Profiles first, then Device Features */ + for (idx = 0; idx < ARRAY_SIZE(las->features[0]); idx++) { + msg.msg[msg.len++] = features[idx]; + if ((features[idx] & CEC_OP_FEAT_EXT) == 0) { + if (op_is_dev_features) + break; + op_is_dev_features = true; + } + } + return cec_transmit_msg(adap, &msg, false); +} + +/* Transmit the Report Physical Address message */ +static int cec_report_phys_addr(struct cec_adapter *adap, unsigned int la_idx) +{ + const struct cec_log_addrs *las = &adap->log_addrs; + struct cec_msg msg = { }; + + /* Report Physical Address */ + msg.msg[0] = (las->log_addr[la_idx] << 4) | 0x0f; + cec_msg_report_physical_addr(&msg, adap->phys_addr, + las->primary_device_type[la_idx]); + dprintk(2, "config: la %d pa %x.%x.%x.%x\n", + las->log_addr[la_idx], + cec_phys_addr_exp(adap->phys_addr)); + return cec_transmit_msg(adap, &msg, false); +} + +/* Transmit the Feature Abort message */ +static int cec_feature_abort_reason(struct cec_adapter *adap, + struct cec_msg *msg, u8 reason) +{ + struct cec_msg tx_msg = { }; + + /* + * Don't reply with CEC_MSG_FEATURE_ABORT to a CEC_MSG_FEATURE_ABORT + * message! + */ + if (msg->msg[1] == CEC_MSG_FEATURE_ABORT) + return 0; + cec_msg_set_reply_to(&tx_msg, msg); + cec_msg_feature_abort(&tx_msg, msg->msg[1], reason); + return cec_transmit_msg(adap, &tx_msg, false); +} + +static int cec_feature_abort(struct cec_adapter *adap, struct cec_msg *msg) +{ + return cec_feature_abort_reason(adap, msg, + CEC_OP_ABORT_UNRECOGNIZED_OP); +} + +static int cec_feature_refused(struct cec_adapter *adap, struct cec_msg *msg) +{ + return cec_feature_abort_reason(adap, msg, + CEC_OP_ABORT_REFUSED); +} + +/* + * Called when a CEC message is received. This function will do any + * necessary core processing. The is_reply bool is true if this message + * is a reply to an earlier transmit. + * + * The message is either a broadcast message or a valid directed message. + */ +static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, + bool is_reply) +{ + bool is_broadcast = cec_msg_is_broadcast(msg); + u8 dest_laddr = cec_msg_destination(msg); + u8 init_laddr = cec_msg_initiator(msg); + u8 devtype = cec_log_addr2dev(adap, dest_laddr); + int la_idx = cec_log_addr2idx(adap, dest_laddr); + bool from_unregistered = init_laddr == 0xf; + struct cec_msg tx_cec_msg = { }; + + dprintk(1, "cec_receive_notify: %*ph\n", msg->len, msg->msg); + + /* If this is a CDC-Only device, then ignore any non-CDC messages */ + if (cec_is_cdc_only(&adap->log_addrs) && + msg->msg[1] != CEC_MSG_CDC_MESSAGE) + return 0; + + if (adap->ops->received) { + /* Allow drivers to process the message first */ + if (adap->ops->received(adap, msg) != -ENOMSG) + return 0; + } + + /* + * REPORT_PHYSICAL_ADDR, CEC_MSG_USER_CONTROL_PRESSED and + * CEC_MSG_USER_CONTROL_RELEASED messages always have to be + * handled by the CEC core, even if the passthrough mode is on. + * The others are just ignored if passthrough mode is on. + */ + switch (msg->msg[1]) { + case CEC_MSG_GET_CEC_VERSION: + case CEC_MSG_GIVE_DEVICE_VENDOR_ID: + case CEC_MSG_ABORT: + case CEC_MSG_GIVE_DEVICE_POWER_STATUS: + case CEC_MSG_GIVE_PHYSICAL_ADDR: + case CEC_MSG_GIVE_OSD_NAME: + case CEC_MSG_GIVE_FEATURES: + /* + * Skip processing these messages if the passthrough mode + * is on. + */ + if (adap->passthrough) + goto skip_processing; + /* Ignore if addressing is wrong */ + if (is_broadcast || from_unregistered) + return 0; + break; + + case CEC_MSG_USER_CONTROL_PRESSED: + case CEC_MSG_USER_CONTROL_RELEASED: + /* Wrong addressing mode: don't process */ + if (is_broadcast || from_unregistered) + goto skip_processing; + break; + + case CEC_MSG_REPORT_PHYSICAL_ADDR: + /* + * This message is always processed, regardless of the + * passthrough setting. + * + * Exception: don't process if wrong addressing mode. + */ + if (!is_broadcast) + goto skip_processing; + break; + + default: + break; + } + + cec_msg_set_reply_to(&tx_cec_msg, msg); + + switch (msg->msg[1]) { + /* The following messages are processed but still passed through */ + case CEC_MSG_REPORT_PHYSICAL_ADDR: { + u16 pa = (msg->msg[2] << 8) | msg->msg[3]; + + if (!from_unregistered) + adap->phys_addrs[init_laddr] = pa; + dprintk(1, "Reported physical address %x.%x.%x.%x for logical address %d\n", + cec_phys_addr_exp(pa), init_laddr); + break; + } + + case CEC_MSG_USER_CONTROL_PRESSED: + if (!(adap->capabilities & CEC_CAP_RC) || + !(adap->log_addrs.flags & CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU)) + break; + +#if IS_REACHABLE(CONFIG_RC_CORE) + switch (msg->msg[2]) { + /* + * Play function, this message can have variable length + * depending on the specific play function that is used. + */ + case 0x60: + if (msg->len == 2) + rc_keydown(adap->rc, RC_TYPE_CEC, + msg->msg[2], 0); + else + rc_keydown(adap->rc, RC_TYPE_CEC, + msg->msg[2] << 8 | msg->msg[3], 0); + break; + /* + * Other function messages that are not handled. + * Currently the RC framework does not allow to supply an + * additional parameter to a keypress. These "keys" contain + * other information such as channel number, an input number + * etc. + * For the time being these messages are not processed by the + * framework and are simply forwarded to the user space. + */ + case 0x56: case 0x57: + case 0x67: case 0x68: case 0x69: case 0x6a: + break; + default: + rc_keydown(adap->rc, RC_TYPE_CEC, msg->msg[2], 0); + break; + } +#endif + break; + + case CEC_MSG_USER_CONTROL_RELEASED: + if (!(adap->capabilities & CEC_CAP_RC) || + !(adap->log_addrs.flags & CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU)) + break; +#if IS_REACHABLE(CONFIG_RC_CORE) + rc_keyup(adap->rc); +#endif + break; + + /* + * The remaining messages are only processed if the passthrough mode + * is off. + */ + case CEC_MSG_GET_CEC_VERSION: + cec_msg_cec_version(&tx_cec_msg, adap->log_addrs.cec_version); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_GIVE_PHYSICAL_ADDR: + /* Do nothing for CEC switches using addr 15 */ + if (devtype == CEC_OP_PRIM_DEVTYPE_SWITCH && dest_laddr == 15) + return 0; + cec_msg_report_physical_addr(&tx_cec_msg, adap->phys_addr, devtype); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_GIVE_DEVICE_VENDOR_ID: + if (adap->log_addrs.vendor_id == CEC_VENDOR_ID_NONE) + return cec_feature_abort(adap, msg); + cec_msg_device_vendor_id(&tx_cec_msg, adap->log_addrs.vendor_id); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_ABORT: + /* Do nothing for CEC switches */ + if (devtype == CEC_OP_PRIM_DEVTYPE_SWITCH) + return 0; + return cec_feature_refused(adap, msg); + + case CEC_MSG_GIVE_OSD_NAME: { + if (adap->log_addrs.osd_name[0] == 0) + return cec_feature_abort(adap, msg); + cec_msg_set_osd_name(&tx_cec_msg, adap->log_addrs.osd_name); + return cec_transmit_msg(adap, &tx_cec_msg, false); + } + + case CEC_MSG_GIVE_FEATURES: + if (adap->log_addrs.cec_version >= CEC_OP_CEC_VERSION_2_0) + return cec_report_features(adap, la_idx); + return 0; + + default: + /* + * Unprocessed messages are aborted if userspace isn't doing + * any processing either. + */ + if (!is_broadcast && !is_reply && !adap->follower_cnt && + !adap->cec_follower && msg->msg[1] != CEC_MSG_FEATURE_ABORT) + return cec_feature_abort(adap, msg); + break; + } + +skip_processing: + /* If this was a reply, then we're done, unless otherwise specified */ + if (is_reply && !(msg->flags & CEC_MSG_FL_REPLY_TO_FOLLOWERS)) + return 0; + + /* + * Send to the exclusive follower if there is one, otherwise send + * to all followers. + */ + if (adap->cec_follower) + cec_queue_msg_fh(adap->cec_follower, msg); + else + cec_queue_msg_followers(adap, msg); + return 0; +} + +/* + * Helper functions to keep track of the 'monitor all' use count. + * + * These functions are called with adap->lock held. + */ +int cec_monitor_all_cnt_inc(struct cec_adapter *adap) +{ + int ret = 0; + + if (adap->monitor_all_cnt == 0) + ret = call_op(adap, adap_monitor_all_enable, 1); + if (ret == 0) + adap->monitor_all_cnt++; + return ret; +} + +void cec_monitor_all_cnt_dec(struct cec_adapter *adap) +{ + adap->monitor_all_cnt--; + if (adap->monitor_all_cnt == 0) + WARN_ON(call_op(adap, adap_monitor_all_enable, 0)); +} + +#ifdef CONFIG_MEDIA_CEC_DEBUG +/* + * Log the current state of the CEC adapter. + * Very useful for debugging. + */ +int cec_adap_status(struct seq_file *file, void *priv) +{ + struct cec_adapter *adap = dev_get_drvdata(file->private); + struct cec_data *data; + + mutex_lock(&adap->lock); + seq_printf(file, "configured: %d\n", adap->is_configured); + seq_printf(file, "configuring: %d\n", adap->is_configuring); + seq_printf(file, "phys_addr: %x.%x.%x.%x\n", + cec_phys_addr_exp(adap->phys_addr)); + seq_printf(file, "number of LAs: %d\n", adap->log_addrs.num_log_addrs); + seq_printf(file, "LA mask: 0x%04x\n", adap->log_addrs.log_addr_mask); + if (adap->cec_follower) + seq_printf(file, "has CEC follower%s\n", + adap->passthrough ? " (in passthrough mode)" : ""); + if (adap->cec_initiator) + seq_puts(file, "has CEC initiator\n"); + if (adap->monitor_all_cnt) + seq_printf(file, "file handles in Monitor All mode: %u\n", + adap->monitor_all_cnt); + data = adap->transmitting; + if (data) + seq_printf(file, "transmitting message: %*ph (reply: %02x, timeout: %ums)\n", + data->msg.len, data->msg.msg, data->msg.reply, + data->msg.timeout); + seq_printf(file, "pending transmits: %u\n", adap->transmit_queue_sz); + list_for_each_entry(data, &adap->transmit_queue, list) { + seq_printf(file, "queued tx message: %*ph (reply: %02x, timeout: %ums)\n", + data->msg.len, data->msg.msg, data->msg.reply, + data->msg.timeout); + } + list_for_each_entry(data, &adap->wait_queue, list) { + seq_printf(file, "message waiting for reply: %*ph (reply: %02x, timeout: %ums)\n", + data->msg.len, data->msg.msg, data->msg.reply, + data->msg.timeout); + } + + call_void_op(adap, adap_status, file); + mutex_unlock(&adap->lock); + return 0; +} +#endif diff --git a/drivers/media/cec/cec-api.c b/drivers/media/cec/cec-api.c new file mode 100644 index 000000000000..d4bc4ee2c6e5 --- /dev/null +++ b/drivers/media/cec/cec-api.c @@ -0,0 +1,588 @@ +/* + * cec-api.c - HDMI Consumer Electronics Control framework - API + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cec-priv.h" + +static inline struct cec_devnode *cec_devnode_data(struct file *filp) +{ + struct cec_fh *fh = filp->private_data; + + return &fh->adap->devnode; +} + +/* CEC file operations */ + +static unsigned int cec_poll(struct file *filp, + struct poll_table_struct *poll) +{ + struct cec_devnode *devnode = cec_devnode_data(filp); + struct cec_fh *fh = filp->private_data; + struct cec_adapter *adap = fh->adap; + unsigned int res = 0; + + if (!devnode->registered) + return POLLERR | POLLHUP; + mutex_lock(&adap->lock); + if (adap->is_configured && + adap->transmit_queue_sz < CEC_MAX_MSG_TX_QUEUE_SZ) + res |= POLLOUT | POLLWRNORM; + if (fh->queued_msgs) + res |= POLLIN | POLLRDNORM; + if (fh->pending_events) + res |= POLLPRI; + poll_wait(filp, &fh->wait, poll); + mutex_unlock(&adap->lock); + return res; +} + +static bool cec_is_busy(const struct cec_adapter *adap, + const struct cec_fh *fh) +{ + bool valid_initiator = adap->cec_initiator && adap->cec_initiator == fh; + bool valid_follower = adap->cec_follower && adap->cec_follower == fh; + + /* + * Exclusive initiators and followers can always access the CEC adapter + */ + if (valid_initiator || valid_follower) + return false; + /* + * All others can only access the CEC adapter if there is no + * exclusive initiator and they are in INITIATOR mode. + */ + return adap->cec_initiator || + fh->mode_initiator == CEC_MODE_NO_INITIATOR; +} + +static long cec_adap_g_caps(struct cec_adapter *adap, + struct cec_caps __user *parg) +{ + struct cec_caps caps = {}; + + strlcpy(caps.driver, adap->devnode.parent->driver->name, + sizeof(caps.driver)); + strlcpy(caps.name, adap->name, sizeof(caps.name)); + caps.available_log_addrs = adap->available_log_addrs; + caps.capabilities = adap->capabilities; + caps.version = LINUX_VERSION_CODE; + if (copy_to_user(parg, &caps, sizeof(caps))) + return -EFAULT; + return 0; +} + +static long cec_adap_g_phys_addr(struct cec_adapter *adap, + __u16 __user *parg) +{ + u16 phys_addr; + + mutex_lock(&adap->lock); + phys_addr = adap->phys_addr; + mutex_unlock(&adap->lock); + if (copy_to_user(parg, &phys_addr, sizeof(phys_addr))) + return -EFAULT; + return 0; +} + +static long cec_adap_s_phys_addr(struct cec_adapter *adap, struct cec_fh *fh, + bool block, __u16 __user *parg) +{ + u16 phys_addr; + long err; + + if (!(adap->capabilities & CEC_CAP_PHYS_ADDR)) + return -ENOTTY; + if (copy_from_user(&phys_addr, parg, sizeof(phys_addr))) + return -EFAULT; + + err = cec_phys_addr_validate(phys_addr, NULL, NULL); + if (err) + return err; + mutex_lock(&adap->lock); + if (cec_is_busy(adap, fh)) + err = -EBUSY; + else + __cec_s_phys_addr(adap, phys_addr, block); + mutex_unlock(&adap->lock); + return err; +} + +static long cec_adap_g_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs __user *parg) +{ + struct cec_log_addrs log_addrs; + + mutex_lock(&adap->lock); + log_addrs = adap->log_addrs; + if (!adap->is_configured) + memset(log_addrs.log_addr, CEC_LOG_ADDR_INVALID, + sizeof(log_addrs.log_addr)); + mutex_unlock(&adap->lock); + + if (copy_to_user(parg, &log_addrs, sizeof(log_addrs))) + return -EFAULT; + return 0; +} + +static long cec_adap_s_log_addrs(struct cec_adapter *adap, struct cec_fh *fh, + bool block, struct cec_log_addrs __user *parg) +{ + struct cec_log_addrs log_addrs; + long err = -EBUSY; + + if (!(adap->capabilities & CEC_CAP_LOG_ADDRS)) + return -ENOTTY; + if (copy_from_user(&log_addrs, parg, sizeof(log_addrs))) + return -EFAULT; + log_addrs.flags &= CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK | + CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU | + CEC_LOG_ADDRS_FL_CDC_ONLY; + mutex_lock(&adap->lock); + if (!adap->is_configuring && + (!log_addrs.num_log_addrs || !adap->is_configured) && + !cec_is_busy(adap, fh)) { + err = __cec_s_log_addrs(adap, &log_addrs, block); + if (!err) + log_addrs = adap->log_addrs; + } + mutex_unlock(&adap->lock); + if (err) + return err; + if (copy_to_user(parg, &log_addrs, sizeof(log_addrs))) + return -EFAULT; + return 0; +} + +static long cec_transmit(struct cec_adapter *adap, struct cec_fh *fh, + bool block, struct cec_msg __user *parg) +{ + struct cec_msg msg = {}; + long err = 0; + + if (!(adap->capabilities & CEC_CAP_TRANSMIT)) + return -ENOTTY; + if (copy_from_user(&msg, parg, sizeof(msg))) + return -EFAULT; + + /* A CDC-Only device can only send CDC messages */ + if ((adap->log_addrs.flags & CEC_LOG_ADDRS_FL_CDC_ONLY) && + (msg.len == 1 || msg.msg[1] != CEC_MSG_CDC_MESSAGE)) + return -EINVAL; + + msg.flags &= CEC_MSG_FL_REPLY_TO_FOLLOWERS; + mutex_lock(&adap->lock); + if (!adap->is_configured) + err = -ENONET; + else if (cec_is_busy(adap, fh)) + err = -EBUSY; + else + err = cec_transmit_msg_fh(adap, &msg, fh, block); + mutex_unlock(&adap->lock); + if (err) + return err; + if (copy_to_user(parg, &msg, sizeof(msg))) + return -EFAULT; + return 0; +} + +/* Called by CEC_RECEIVE: wait for a message to arrive */ +static int cec_receive_msg(struct cec_fh *fh, struct cec_msg *msg, bool block) +{ + u32 timeout = msg->timeout; + int res; + + do { + mutex_lock(&fh->lock); + /* Are there received messages queued up? */ + if (fh->queued_msgs) { + /* Yes, return the first one */ + struct cec_msg_entry *entry = + list_first_entry(&fh->msgs, + struct cec_msg_entry, list); + + list_del(&entry->list); + *msg = entry->msg; + kfree(entry); + fh->queued_msgs--; + mutex_unlock(&fh->lock); + /* restore original timeout value */ + msg->timeout = timeout; + return 0; + } + + /* No, return EAGAIN in non-blocking mode or wait */ + mutex_unlock(&fh->lock); + + /* Return when in non-blocking mode */ + if (!block) + return -EAGAIN; + + if (msg->timeout) { + /* The user specified a timeout */ + res = wait_event_interruptible_timeout(fh->wait, + fh->queued_msgs, + msecs_to_jiffies(msg->timeout)); + if (res == 0) + res = -ETIMEDOUT; + else if (res > 0) + res = 0; + } else { + /* Wait indefinitely */ + res = wait_event_interruptible(fh->wait, + fh->queued_msgs); + } + /* Exit on error, otherwise loop to get the new message */ + } while (!res); + return res; +} + +static long cec_receive(struct cec_adapter *adap, struct cec_fh *fh, + bool block, struct cec_msg __user *parg) +{ + struct cec_msg msg = {}; + long err = 0; + + if (copy_from_user(&msg, parg, sizeof(msg))) + return -EFAULT; + mutex_lock(&adap->lock); + if (!adap->is_configured && fh->mode_follower < CEC_MODE_MONITOR) + err = -ENONET; + mutex_unlock(&adap->lock); + if (err) + return err; + + err = cec_receive_msg(fh, &msg, block); + if (err) + return err; + if (copy_to_user(parg, &msg, sizeof(msg))) + return -EFAULT; + return 0; +} + +static long cec_dqevent(struct cec_adapter *adap, struct cec_fh *fh, + bool block, struct cec_event __user *parg) +{ + struct cec_event *ev = NULL; + u64 ts = ~0ULL; + unsigned int i; + long err = 0; + + mutex_lock(&fh->lock); + while (!fh->pending_events && block) { + mutex_unlock(&fh->lock); + err = wait_event_interruptible(fh->wait, fh->pending_events); + if (err) + return err; + mutex_lock(&fh->lock); + } + + /* 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; + } + } + if (!ev) { + err = -EAGAIN; + goto unlock; + } + + if (copy_to_user(parg, ev, sizeof(*ev))) { + err = -EFAULT; + goto unlock; + } + + fh->pending_events &= ~(1 << ev->event); + +unlock: + mutex_unlock(&fh->lock); + return err; +} + +static long cec_g_mode(struct cec_adapter *adap, struct cec_fh *fh, + u32 __user *parg) +{ + u32 mode = fh->mode_initiator | fh->mode_follower; + + if (copy_to_user(parg, &mode, sizeof(mode))) + return -EFAULT; + return 0; +} + +static long cec_s_mode(struct cec_adapter *adap, struct cec_fh *fh, + u32 __user *parg) +{ + u32 mode; + u8 mode_initiator; + u8 mode_follower; + long err = 0; + + if (copy_from_user(&mode, parg, sizeof(mode))) + return -EFAULT; + if (mode & ~(CEC_MODE_INITIATOR_MSK | CEC_MODE_FOLLOWER_MSK)) + return -EINVAL; + + mode_initiator = mode & CEC_MODE_INITIATOR_MSK; + mode_follower = mode & CEC_MODE_FOLLOWER_MSK; + + if (mode_initiator > CEC_MODE_EXCL_INITIATOR || + mode_follower > CEC_MODE_MONITOR_ALL) + return -EINVAL; + + if (mode_follower == CEC_MODE_MONITOR_ALL && + !(adap->capabilities & CEC_CAP_MONITOR_ALL)) + return -EINVAL; + + /* Follower modes should always be able to send CEC messages */ + if ((mode_initiator == CEC_MODE_NO_INITIATOR || + !(adap->capabilities & CEC_CAP_TRANSMIT)) && + mode_follower >= CEC_MODE_FOLLOWER && + mode_follower <= CEC_MODE_EXCL_FOLLOWER_PASSTHRU) + return -EINVAL; + + /* Monitor modes require CEC_MODE_NO_INITIATOR */ + if (mode_initiator && mode_follower >= CEC_MODE_MONITOR) + return -EINVAL; + + /* Monitor modes require CAP_NET_ADMIN */ + if (mode_follower >= CEC_MODE_MONITOR && !capable(CAP_NET_ADMIN)) + return -EPERM; + + mutex_lock(&adap->lock); + /* + * You can't become exclusive follower if someone else already + * has that job. + */ + if ((mode_follower == CEC_MODE_EXCL_FOLLOWER || + mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU) && + adap->cec_follower && adap->cec_follower != fh) + err = -EBUSY; + /* + * You can't become exclusive initiator if someone else already + * has that job. + */ + if (mode_initiator == CEC_MODE_EXCL_INITIATOR && + adap->cec_initiator && adap->cec_initiator != fh) + err = -EBUSY; + + if (!err) { + bool old_mon_all = fh->mode_follower == CEC_MODE_MONITOR_ALL; + bool new_mon_all = mode_follower == CEC_MODE_MONITOR_ALL; + + if (old_mon_all != new_mon_all) { + if (new_mon_all) + err = cec_monitor_all_cnt_inc(adap); + else + cec_monitor_all_cnt_dec(adap); + } + } + + if (err) { + mutex_unlock(&adap->lock); + return err; + } + + if (fh->mode_follower == CEC_MODE_FOLLOWER) + adap->follower_cnt--; + if (mode_follower == CEC_MODE_FOLLOWER) + adap->follower_cnt++; + if (mode_follower == CEC_MODE_EXCL_FOLLOWER || + mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU) { + adap->passthrough = + mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU; + adap->cec_follower = fh; + } else if (adap->cec_follower == fh) { + adap->passthrough = false; + adap->cec_follower = NULL; + } + if (mode_initiator == CEC_MODE_EXCL_INITIATOR) + adap->cec_initiator = fh; + else if (adap->cec_initiator == fh) + adap->cec_initiator = NULL; + fh->mode_initiator = mode_initiator; + fh->mode_follower = mode_follower; + mutex_unlock(&adap->lock); + return 0; +} + +static long cec_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct cec_devnode *devnode = cec_devnode_data(filp); + struct cec_fh *fh = filp->private_data; + struct cec_adapter *adap = fh->adap; + bool block = !(filp->f_flags & O_NONBLOCK); + void __user *parg = (void __user *)arg; + + if (!devnode->registered) + return -ENODEV; + + switch (cmd) { + case CEC_ADAP_G_CAPS: + return cec_adap_g_caps(adap, parg); + + case CEC_ADAP_G_PHYS_ADDR: + return cec_adap_g_phys_addr(adap, parg); + + case CEC_ADAP_S_PHYS_ADDR: + return cec_adap_s_phys_addr(adap, fh, block, parg); + + case CEC_ADAP_G_LOG_ADDRS: + return cec_adap_g_log_addrs(adap, parg); + + case CEC_ADAP_S_LOG_ADDRS: + return cec_adap_s_log_addrs(adap, fh, block, parg); + + case CEC_TRANSMIT: + return cec_transmit(adap, fh, block, parg); + + case CEC_RECEIVE: + return cec_receive(adap, fh, block, parg); + + case CEC_DQEVENT: + return cec_dqevent(adap, fh, block, parg); + + case CEC_G_MODE: + return cec_g_mode(adap, fh, parg); + + case CEC_S_MODE: + return cec_s_mode(adap, fh, parg); + + default: + return -ENOTTY; + } +} + +static int cec_open(struct inode *inode, struct file *filp) +{ + struct cec_devnode *devnode = + container_of(inode->i_cdev, struct cec_devnode, cdev); + struct cec_adapter *adap = to_cec_adapter(devnode); + struct cec_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL); + /* + * Initial events that are automatically sent when the cec device is + * opened. + */ + struct cec_event ev_state = { + .event = CEC_EVENT_STATE_CHANGE, + .flags = CEC_EVENT_FL_INITIAL_STATE, + }; + int err; + + if (!fh) + return -ENOMEM; + + INIT_LIST_HEAD(&fh->msgs); + INIT_LIST_HEAD(&fh->xfer_list); + mutex_init(&fh->lock); + init_waitqueue_head(&fh->wait); + + fh->mode_initiator = CEC_MODE_INITIATOR; + fh->adap = adap; + + err = cec_get_device(devnode); + if (err) { + kfree(fh); + return err; + } + + filp->private_data = fh; + + mutex_lock(&devnode->lock); + /* Queue up initial state events */ + ev_state.state_change.phys_addr = adap->phys_addr; + ev_state.state_change.log_addr_mask = adap->log_addrs.log_addr_mask; + cec_queue_event_fh(fh, &ev_state, 0); + + list_add(&fh->list, &devnode->fhs); + mutex_unlock(&devnode->lock); + + return 0; +} + +/* Override for the release function */ +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; + + mutex_lock(&adap->lock); + if (adap->cec_initiator == fh) + adap->cec_initiator = NULL; + if (adap->cec_follower == fh) { + adap->cec_follower = NULL; + adap->passthrough = false; + } + if (fh->mode_follower == CEC_MODE_FOLLOWER) + adap->follower_cnt--; + if (fh->mode_follower == CEC_MODE_MONITOR_ALL) + cec_monitor_all_cnt_dec(adap); + mutex_unlock(&adap->lock); + + mutex_lock(&devnode->lock); + list_del(&fh->list); + mutex_unlock(&devnode->lock); + + /* Unhook pending transmits from this filehandle. */ + mutex_lock(&adap->lock); + while (!list_empty(&fh->xfer_list)) { + struct cec_data *data = + list_first_entry(&fh->xfer_list, struct cec_data, xfer_list); + + data->blocking = false; + data->fh = NULL; + list_del(&data->xfer_list); + } + mutex_unlock(&adap->lock); + while (!list_empty(&fh->msgs)) { + struct cec_msg_entry *entry = + list_first_entry(&fh->msgs, struct cec_msg_entry, list); + + list_del(&entry->list); + kfree(entry); + } + kfree(fh); + + cec_put_device(devnode); + filp->private_data = NULL; + return 0; +} + +const struct file_operations cec_devnode_fops = { + .owner = THIS_MODULE, + .open = cec_open, + .unlocked_ioctl = cec_ioctl, + .release = cec_release, + .poll = cec_poll, + .llseek = no_llseek, +}; diff --git a/drivers/media/cec/cec-core.c b/drivers/media/cec/cec-core.c new file mode 100644 index 000000000000..b0137e247dc9 --- /dev/null +++ b/drivers/media/cec/cec-core.c @@ -0,0 +1,411 @@ +/* + * cec-core.c - HDMI Consumer Electronics Control framework - Core + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cec-priv.h" + +#define CEC_NUM_DEVICES 256 +#define CEC_NAME "cec" + +int cec_debug; +module_param_named(debug, cec_debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +static dev_t cec_dev_t; + +/* Active devices */ +static DEFINE_MUTEX(cec_devnode_lock); +static DECLARE_BITMAP(cec_devnode_nums, CEC_NUM_DEVICES); + +static struct dentry *top_cec_dir; + +/* dev to cec_devnode */ +#define to_cec_devnode(cd) container_of(cd, struct cec_devnode, dev) + +int cec_get_device(struct cec_devnode *devnode) +{ + /* + * Check if the cec device is available. This needs to be done with + * the devnode->lock held to prevent an open/unregister race: + * without the lock, the device could be unregistered and freed between + * the devnode->registered check and get_device() calls, leading to + * a crash. + */ + mutex_lock(&devnode->lock); + /* + * return ENXIO if the cec device has been removed + * already or if it is not registered anymore. + */ + if (!devnode->registered) { + mutex_unlock(&devnode->lock); + return -ENXIO; + } + /* and increase the device refcount */ + get_device(&devnode->dev); + mutex_unlock(&devnode->lock); + return 0; +} + +void cec_put_device(struct cec_devnode *devnode) +{ + put_device(&devnode->dev); +} + +/* Called when the last user of the cec device exits. */ +static void cec_devnode_release(struct device *cd) +{ + struct cec_devnode *devnode = to_cec_devnode(cd); + + mutex_lock(&cec_devnode_lock); + /* Mark device node number as free */ + clear_bit(devnode->minor, cec_devnode_nums); + mutex_unlock(&cec_devnode_lock); + + cec_delete_adapter(to_cec_adapter(devnode)); +} + +static struct bus_type cec_bus_type = { + .name = CEC_NAME, +}; + +/* + * Register a cec device node + * + * The registration code assigns minor numbers and registers the new device node + * with the kernel. An error is returned if no free minor number can be found, + * or if the registration of the device node fails. + * + * Zero is returned on success. + * + * Note that if the cec_devnode_register call fails, the release() callback of + * the cec_devnode structure is *not* called, so the caller is responsible for + * freeing any data. + */ +static int __must_check cec_devnode_register(struct cec_devnode *devnode, + struct module *owner) +{ + int minor; + int ret; + + /* Initialization */ + INIT_LIST_HEAD(&devnode->fhs); + mutex_init(&devnode->lock); + + /* Part 1: Find a free minor number */ + mutex_lock(&cec_devnode_lock); + minor = find_next_zero_bit(cec_devnode_nums, CEC_NUM_DEVICES, 0); + if (minor == CEC_NUM_DEVICES) { + mutex_unlock(&cec_devnode_lock); + pr_err("could not get a free minor\n"); + return -ENFILE; + } + + set_bit(minor, cec_devnode_nums); + mutex_unlock(&cec_devnode_lock); + + devnode->minor = minor; + devnode->dev.bus = &cec_bus_type; + devnode->dev.devt = MKDEV(MAJOR(cec_dev_t), minor); + devnode->dev.release = cec_devnode_release; + devnode->dev.parent = devnode->parent; + dev_set_name(&devnode->dev, "cec%d", devnode->minor); + device_initialize(&devnode->dev); + + /* Part 2: Initialize and register the character device */ + cdev_init(&devnode->cdev, &cec_devnode_fops); + devnode->cdev.kobj.parent = &devnode->dev.kobj; + devnode->cdev.owner = owner; + + ret = cdev_add(&devnode->cdev, devnode->dev.devt, 1); + if (ret < 0) { + pr_err("%s: cdev_add failed\n", __func__); + goto clr_bit; + } + + ret = device_add(&devnode->dev); + if (ret) + goto cdev_del; + + devnode->registered = true; + return 0; + +cdev_del: + cdev_del(&devnode->cdev); +clr_bit: + mutex_lock(&cec_devnode_lock); + clear_bit(devnode->minor, cec_devnode_nums); + mutex_unlock(&cec_devnode_lock); + return ret; +} + +/* + * Unregister a cec device node + * + * This unregisters the passed device. Future open calls will be met with + * errors. + * + * This function can safely be called if the device node has never been + * registered or has already been unregistered. + */ +static void cec_devnode_unregister(struct cec_devnode *devnode) +{ + struct cec_fh *fh; + + mutex_lock(&devnode->lock); + + /* Check if devnode was never registered or already unregistered */ + if (!devnode->registered || devnode->unregistered) { + mutex_unlock(&devnode->lock); + return; + } + + list_for_each_entry(fh, &devnode->fhs, list) + wake_up_interruptible(&fh->wait); + + devnode->registered = false; + devnode->unregistered = true; + mutex_unlock(&devnode->lock); + + device_del(&devnode->dev); + cdev_del(&devnode->cdev); + put_device(&devnode->dev); +} + +struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops, + void *priv, const char *name, u32 caps, + u8 available_las, struct device *parent) +{ + struct cec_adapter *adap; + int res; + + if (WARN_ON(!parent)) + return ERR_PTR(-EINVAL); + if (WARN_ON(!caps)) + return ERR_PTR(-EINVAL); + if (WARN_ON(!ops)) + return ERR_PTR(-EINVAL); + if (WARN_ON(!available_las || available_las > CEC_MAX_LOG_ADDRS)) + return ERR_PTR(-EINVAL); + adap = kzalloc(sizeof(*adap), GFP_KERNEL); + if (!adap) + return ERR_PTR(-ENOMEM); + adap->owner = parent->driver->owner; + adap->devnode.parent = parent; + strlcpy(adap->name, name, sizeof(adap->name)); + adap->phys_addr = CEC_PHYS_ADDR_INVALID; + adap->log_addrs.cec_version = CEC_OP_CEC_VERSION_2_0; + adap->log_addrs.vendor_id = CEC_VENDOR_ID_NONE; + adap->capabilities = caps; + adap->available_log_addrs = available_las; + adap->sequence = 0; + adap->ops = ops; + adap->priv = priv; + memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs)); + mutex_init(&adap->lock); + INIT_LIST_HEAD(&adap->transmit_queue); + INIT_LIST_HEAD(&adap->wait_queue); + init_waitqueue_head(&adap->kthread_waitq); + + adap->kthread = kthread_run(cec_thread_func, adap, "cec-%s", name); + if (IS_ERR(adap->kthread)) { + pr_err("cec-%s: kernel_thread() failed\n", name); + res = PTR_ERR(adap->kthread); + kfree(adap); + return ERR_PTR(res); + } + + if (!(caps & CEC_CAP_RC)) + return adap; + +#if IS_REACHABLE(CONFIG_RC_CORE) + /* Prepare the RC input device */ + adap->rc = rc_allocate_device(); + if (!adap->rc) { + pr_err("cec-%s: failed to allocate memory for rc_dev\n", + name); + kthread_stop(adap->kthread); + kfree(adap); + return ERR_PTR(-ENOMEM); + } + + snprintf(adap->input_name, sizeof(adap->input_name), + "RC for %s", name); + snprintf(adap->input_phys, sizeof(adap->input_phys), + "%s/input0", name); + + adap->rc->input_name = adap->input_name; + adap->rc->input_phys = adap->input_phys; + adap->rc->input_id.bustype = BUS_CEC; + adap->rc->input_id.vendor = 0; + adap->rc->input_id.product = 0; + adap->rc->input_id.version = 1; + adap->rc->dev.parent = parent; + adap->rc->driver_type = RC_DRIVER_SCANCODE; + adap->rc->driver_name = CEC_NAME; + adap->rc->allowed_protocols = RC_BIT_CEC; + adap->rc->priv = adap; + adap->rc->map_name = RC_MAP_CEC; + adap->rc->timeout = MS_TO_NS(100); +#else + adap->capabilities &= ~CEC_CAP_RC; +#endif + return adap; +} +EXPORT_SYMBOL_GPL(cec_allocate_adapter); + +int cec_register_adapter(struct cec_adapter *adap) +{ + int res; + + if (IS_ERR_OR_NULL(adap)) + return 0; + +#if IS_REACHABLE(CONFIG_RC_CORE) + if (adap->capabilities & CEC_CAP_RC) { + res = rc_register_device(adap->rc); + + if (res) { + pr_err("cec-%s: failed to prepare input device\n", + adap->name); + rc_free_device(adap->rc); + adap->rc = NULL; + return res; + } + } +#endif + + res = cec_devnode_register(&adap->devnode, adap->owner); + if (res) { +#if IS_REACHABLE(CONFIG_RC_CORE) + /* Note: rc_unregister also calls rc_free */ + rc_unregister_device(adap->rc); + adap->rc = NULL; +#endif + return res; + } + + dev_set_drvdata(&adap->devnode.dev, adap); +#ifdef CONFIG_MEDIA_CEC_DEBUG + if (!top_cec_dir) + return 0; + + adap->cec_dir = debugfs_create_dir(dev_name(&adap->devnode.dev), top_cec_dir); + if (IS_ERR_OR_NULL(adap->cec_dir)) { + pr_warn("cec-%s: Failed to create debugfs dir\n", adap->name); + return 0; + } + adap->status_file = debugfs_create_devm_seqfile(&adap->devnode.dev, + "status", adap->cec_dir, cec_adap_status); + if (IS_ERR_OR_NULL(adap->status_file)) { + pr_warn("cec-%s: Failed to create status file\n", adap->name); + debugfs_remove_recursive(adap->cec_dir); + adap->cec_dir = NULL; + } +#endif + return 0; +} +EXPORT_SYMBOL_GPL(cec_register_adapter); + +void cec_unregister_adapter(struct cec_adapter *adap) +{ + if (IS_ERR_OR_NULL(adap)) + return; + +#if IS_REACHABLE(CONFIG_RC_CORE) + /* Note: rc_unregister also calls rc_free */ + rc_unregister_device(adap->rc); + adap->rc = NULL; +#endif + debugfs_remove_recursive(adap->cec_dir); + cec_devnode_unregister(&adap->devnode); +} +EXPORT_SYMBOL_GPL(cec_unregister_adapter); + +void cec_delete_adapter(struct cec_adapter *adap) +{ + if (IS_ERR_OR_NULL(adap)) + return; + mutex_lock(&adap->lock); + __cec_s_phys_addr(adap, CEC_PHYS_ADDR_INVALID, false); + mutex_unlock(&adap->lock); + kthread_stop(adap->kthread); + if (adap->kthread_config) + kthread_stop(adap->kthread_config); +#if IS_REACHABLE(CONFIG_RC_CORE) + rc_free_device(adap->rc); +#endif + kfree(adap); +} +EXPORT_SYMBOL_GPL(cec_delete_adapter); + +/* + * Initialise cec for linux + */ +static int __init cec_devnode_init(void) +{ + int ret; + + pr_info("Linux cec interface: v0.10\n"); + ret = alloc_chrdev_region(&cec_dev_t, 0, CEC_NUM_DEVICES, + CEC_NAME); + if (ret < 0) { + pr_warn("cec: unable to allocate major\n"); + return ret; + } + +#ifdef CONFIG_MEDIA_CEC_DEBUG + top_cec_dir = debugfs_create_dir("cec", NULL); + if (IS_ERR_OR_NULL(top_cec_dir)) { + pr_warn("cec: Failed to create debugfs cec dir\n"); + top_cec_dir = NULL; + } +#endif + + ret = bus_register(&cec_bus_type); + if (ret < 0) { + unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES); + pr_warn("cec: bus_register failed\n"); + return -EIO; + } + + return 0; +} + +static void __exit cec_devnode_exit(void) +{ + debugfs_remove_recursive(top_cec_dir); + bus_unregister(&cec_bus_type); + unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES); +} + +subsys_initcall(cec_devnode_init); +module_exit(cec_devnode_exit) + +MODULE_AUTHOR("Hans Verkuil "); +MODULE_DESCRIPTION("Device node registration for cec drivers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/cec/cec-priv.h b/drivers/media/cec/cec-priv.h new file mode 100644 index 000000000000..70767a7900f2 --- /dev/null +++ b/drivers/media/cec/cec-priv.h @@ -0,0 +1,56 @@ +/* + * cec-priv.h - HDMI Consumer Electronics Control internal header + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _CEC_PRIV_H +#define _CEC_PRIV_H + +#include +#include + +#define dprintk(lvl, fmt, arg...) \ + do { \ + if (lvl <= cec_debug) \ + pr_info("cec-%s: " fmt, adap->name, ## arg); \ + } while (0) + +/* devnode to cec_adapter */ +#define to_cec_adapter(node) container_of(node, struct cec_adapter, devnode) + +/* cec-core.c */ +extern int cec_debug; +int cec_get_device(struct cec_devnode *devnode); +void cec_put_device(struct cec_devnode *devnode); + +/* cec-adap.c */ +int cec_monitor_all_cnt_inc(struct cec_adapter *adap); +void cec_monitor_all_cnt_dec(struct cec_adapter *adap); +int cec_adap_status(struct seq_file *file, void *priv); +int cec_thread_func(void *_adap); +void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block); +int __cec_s_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs *log_addrs, bool block); +int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, + struct cec_fh *fh, bool block); +void cec_queue_event_fh(struct cec_fh *fh, + const struct cec_event *new_ev, u64 ts); + +/* cec-api.c */ +extern const struct file_operations cec_devnode_fops; + +#endif diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 2669b4bad910..b31fa6fae009 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -221,7 +221,7 @@ config VIDEO_ADV7604 config VIDEO_ADV7604_CEC bool "Enable Analog Devices ADV7604 CEC support" - depends on VIDEO_ADV7604 && MEDIA_CEC + depends on VIDEO_ADV7604 && MEDIA_CEC_SUPPORT ---help--- When selected the adv7604 will support the optional HDMI CEC feature. @@ -242,7 +242,7 @@ config VIDEO_ADV7842 config VIDEO_ADV7842_CEC bool "Enable Analog Devices ADV7842 CEC support" - depends on VIDEO_ADV7842 && MEDIA_CEC + depends on VIDEO_ADV7842 && MEDIA_CEC_SUPPORT ---help--- When selected the adv7842 will support the optional HDMI CEC feature. @@ -481,7 +481,7 @@ config VIDEO_ADV7511 config VIDEO_ADV7511_CEC bool "Enable Analog Devices ADV7511 CEC support" - depends on VIDEO_ADV7511 && MEDIA_CEC + depends on VIDEO_ADV7511 && MEDIA_CEC_SUPPORT ---help--- When selected the adv7511 will support the optional HDMI CEC feature. diff --git a/drivers/media/platform/vivid/Kconfig b/drivers/media/platform/vivid/Kconfig index 8e6918c5c87c..db0dd19d227a 100644 --- a/drivers/media/platform/vivid/Kconfig +++ b/drivers/media/platform/vivid/Kconfig @@ -25,7 +25,7 @@ config VIDEO_VIVID config VIDEO_VIVID_CEC bool "Enable CEC emulation support" - depends on VIDEO_VIVID && MEDIA_CEC + depends on VIDEO_VIVID && MEDIA_CEC_SUPPORT ---help--- When selected the vivid module will emulate the optional HDMI CEC feature. diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index 6620d96ee44d..0abe5ffb4934 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -21,8 +21,6 @@ if STAGING_MEDIA && MEDIA_SUPPORT # Please keep them in alphabetic order source "drivers/staging/media/bcm2048/Kconfig" -source "drivers/staging/media/cec/Kconfig" - source "drivers/staging/media/cxd2099/Kconfig" source "drivers/staging/media/davinci_vpfe/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 906257e94dda..246299eff80d 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -1,5 +1,4 @@ obj-$(CONFIG_I2C_BCM2048) += bcm2048/ -obj-$(CONFIG_MEDIA_CEC) += cec/ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_CEC) += s5p-cec/ obj-$(CONFIG_DVB_CXD2099) += cxd2099/ obj-$(CONFIG_LIRC_STAGING) += lirc/ diff --git a/drivers/staging/media/cec/Kconfig b/drivers/staging/media/cec/Kconfig deleted file mode 100644 index 6e12d41b1f86..000000000000 --- a/drivers/staging/media/cec/Kconfig +++ /dev/null @@ -1,12 +0,0 @@ -config MEDIA_CEC - bool "CEC API (EXPERIMENTAL)" - depends on MEDIA_SUPPORT - select MEDIA_CEC_EDID - ---help--- - Enable the CEC API. - -config MEDIA_CEC_DEBUG - bool "CEC debugfs interface (EXPERIMENTAL)" - depends on MEDIA_CEC && DEBUG_FS - ---help--- - Turns on the DebugFS interface for CEC devices. diff --git a/drivers/staging/media/cec/Makefile b/drivers/staging/media/cec/Makefile deleted file mode 100644 index bd7f3c593468..000000000000 --- a/drivers/staging/media/cec/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -cec-objs := cec-core.o cec-adap.o cec-api.o - -ifeq ($(CONFIG_MEDIA_CEC),y) - obj-$(CONFIG_MEDIA_SUPPORT) += cec.o -endif diff --git a/drivers/staging/media/cec/TODO b/drivers/staging/media/cec/TODO deleted file mode 100644 index 504d35c7ae95..000000000000 --- a/drivers/staging/media/cec/TODO +++ /dev/null @@ -1,9 +0,0 @@ -TODOs: - -- Once this is out of staging this should no longer be a separate - config option, instead it should be selected by drivers that want it. -- Revisit the IS_REACHABLE(RC_CORE): perhaps the RC_CORE support should - be enabled through a separate config option in drivers/media/Kconfig - or rc/Kconfig? - -Hans Verkuil diff --git a/drivers/staging/media/cec/cec-adap.c b/drivers/staging/media/cec/cec-adap.c deleted file mode 100644 index 054cd06e2247..000000000000 --- a/drivers/staging/media/cec/cec-adap.c +++ /dev/null @@ -1,1856 +0,0 @@ -/* - * cec-adap.c - HDMI Consumer Electronics Control framework - CEC adapter - * - * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - * - * This program is free software; you may redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cec-priv.h" - -static int cec_report_features(struct cec_adapter *adap, unsigned int la_idx); -static int cec_report_phys_addr(struct cec_adapter *adap, unsigned int la_idx); - -/* - * 400 ms is the time it takes for one 16 byte message to be - * transferred and 5 is the maximum number of retries. Add - * another 100 ms as a margin. So if the transmit doesn't - * finish before that time something is really wrong and we - * have to time out. - * - * This is a sign that something it really wrong and a warning - * will be issued. - */ -#define CEC_XFER_TIMEOUT_MS (5 * 400 + 100) - -#define call_op(adap, op, arg...) \ - (adap->ops->op ? adap->ops->op(adap, ## arg) : 0) - -#define call_void_op(adap, op, arg...) \ - do { \ - if (adap->ops->op) \ - adap->ops->op(adap, ## arg); \ - } while (0) - -static int cec_log_addr2idx(const struct cec_adapter *adap, u8 log_addr) -{ - int i; - - for (i = 0; i < adap->log_addrs.num_log_addrs; i++) - if (adap->log_addrs.log_addr[i] == log_addr) - return i; - return -1; -} - -static unsigned int cec_log_addr2dev(const struct cec_adapter *adap, u8 log_addr) -{ - int i = cec_log_addr2idx(adap, log_addr); - - return adap->log_addrs.primary_device_type[i < 0 ? 0 : i]; -} - -/* - * 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. - */ -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]; - - if (ts == 0) - ts = ktime_get_ns(); - - mutex_lock(&fh->lock); - 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; - goto unlock; - } - - /* - * Intermediate states are not interesting, so just - * overwrite any older event. - */ - *ev = *new_ev; - ev->ts = ts; - fh->pending_events |= 1 << new_ev->event; - -unlock: - mutex_unlock(&fh->lock); - wake_up_interruptible(&fh->wait); -} - -/* Queue a new event for all open filehandles. */ -static void cec_queue_event(struct cec_adapter *adap, - const struct cec_event *ev) -{ - u64 ts = ktime_get_ns(); - struct cec_fh *fh; - - mutex_lock(&adap->devnode.lock); - list_for_each_entry(fh, &adap->devnode.fhs, list) - cec_queue_event_fh(fh, ev, ts); - mutex_unlock(&adap->devnode.lock); -} - -/* - * Queue a new message for this filehandle. If there is no more room - * in the queue, then send the LOST_MSGS event instead. - */ -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, - .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; - - 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; - } - fh->queued_msgs++; - mutex_unlock(&fh->lock); - wake_up_interruptible(&fh->wait); - return; - -lost_msgs: - mutex_unlock(&fh->lock); - cec_queue_event_fh(fh, &ev_lost_msg, 0); -} - -/* - * Queue the message for those filehandles that are in monitor mode. - * If valid_la is true (this message is for us or was sent by us), - * then pass it on to any monitoring filehandle. If this message - * isn't for us or from us, then only give it to filehandles that - * are in MONITOR_ALL mode. - * - * This can only happen if the CEC_CAP_MONITOR_ALL capability is - * set and the CEC adapter was placed in 'monitor all' mode. - */ -static void cec_queue_msg_monitor(struct cec_adapter *adap, - const struct cec_msg *msg, - bool valid_la) -{ - struct cec_fh *fh; - u32 monitor_mode = valid_la ? CEC_MODE_MONITOR : - CEC_MODE_MONITOR_ALL; - - mutex_lock(&adap->devnode.lock); - list_for_each_entry(fh, &adap->devnode.fhs, list) { - if (fh->mode_follower >= monitor_mode) - cec_queue_msg_fh(fh, msg); - } - mutex_unlock(&adap->devnode.lock); -} - -/* - * Queue the message for follower filehandles. - */ -static void cec_queue_msg_followers(struct cec_adapter *adap, - const struct cec_msg *msg) -{ - struct cec_fh *fh; - - mutex_lock(&adap->devnode.lock); - list_for_each_entry(fh, &adap->devnode.fhs, list) { - if (fh->mode_follower == CEC_MODE_FOLLOWER) - cec_queue_msg_fh(fh, msg); - } - mutex_unlock(&adap->devnode.lock); -} - -/* Notify userspace of an adapter state change. */ -static void cec_post_state_event(struct cec_adapter *adap) -{ - struct cec_event ev = { - .event = CEC_EVENT_STATE_CHANGE, - }; - - ev.state_change.phys_addr = adap->phys_addr; - ev.state_change.log_addr_mask = adap->log_addrs.log_addr_mask; - cec_queue_event(adap, &ev); -} - -/* - * A CEC transmit (and a possible wait for reply) completed. - * If this was in blocking mode, then complete it, otherwise - * queue the message for userspace to dequeue later. - * - * This function is called with adap->lock held. - */ -static void cec_data_completed(struct cec_data *data) -{ - /* - * Delete this transmit from the filehandle's xfer_list since - * we're done with it. - * - * Note that if the filehandle is closed before this transmit - * finished, then the release() function will set data->fh to NULL. - * Without that we would be referring to a closed filehandle. - */ - if (data->fh) - list_del(&data->xfer_list); - - if (data->blocking) { - /* - * Someone is blocking so mark the message as completed - * and call complete. - */ - data->completed = true; - complete(&data->c); - } else { - /* - * No blocking, so just queue the message if needed and - * free the memory. - */ - if (data->fh) - cec_queue_msg_fh(data->fh, &data->msg); - kfree(data); - } -} - -/* - * A pending CEC transmit needs to be cancelled, either because the CEC - * adapter is disabled or the transmit takes an impossibly long time to - * finish. - * - * This function is called with adap->lock held. - */ -static void cec_data_cancel(struct cec_data *data) -{ - /* - * It's either the current transmit, or it is a pending - * transmit. Take the appropriate action to clear it. - */ - if (data->adap->transmitting == data) { - data->adap->transmitting = NULL; - } else { - list_del_init(&data->list); - if (!(data->msg.tx_status & CEC_TX_STATUS_OK)) - data->adap->transmit_queue_sz--; - } - - /* Mark it as an error */ - data->msg.tx_ts = ktime_get_ns(); - data->msg.tx_status = CEC_TX_STATUS_ERROR | - CEC_TX_STATUS_MAX_RETRIES; - data->attempts = 0; - data->msg.tx_error_cnt = 1; - /* Queue transmitted message for monitoring purposes */ - cec_queue_msg_monitor(data->adap, &data->msg, 1); - - cec_data_completed(data); -} - -/* - * Main CEC state machine - * - * Wait until the thread should be stopped, or we are not transmitting and - * a new transmit message is queued up, in which case we start transmitting - * that message. When the adapter finished transmitting the message it will - * call cec_transmit_done(). - * - * If the adapter is disabled, then remove all queued messages instead. - * - * If the current transmit times out, then cancel that transmit. - */ -int cec_thread_func(void *_adap) -{ - struct cec_adapter *adap = _adap; - - for (;;) { - unsigned int signal_free_time; - struct cec_data *data; - bool timeout = false; - u8 attempts; - - if (adap->transmitting) { - int err; - - /* - * We are transmitting a message, so add a timeout - * to prevent the state machine to get stuck waiting - * for this message to finalize and add a check to - * see if the adapter is disabled in which case the - * transmit should be canceled. - */ - err = wait_event_interruptible_timeout(adap->kthread_waitq, - kthread_should_stop() || - (!adap->is_configured && !adap->is_configuring) || - (!adap->transmitting && - !list_empty(&adap->transmit_queue)), - msecs_to_jiffies(CEC_XFER_TIMEOUT_MS)); - timeout = err == 0; - } else { - /* Otherwise we just wait for something to happen. */ - wait_event_interruptible(adap->kthread_waitq, - kthread_should_stop() || - (!adap->transmitting && - !list_empty(&adap->transmit_queue))); - } - - mutex_lock(&adap->lock); - - if ((!adap->is_configured && !adap->is_configuring) || - kthread_should_stop()) { - /* - * If the adapter is disabled, or we're asked to stop, - * then cancel any pending transmits. - */ - while (!list_empty(&adap->transmit_queue)) { - data = list_first_entry(&adap->transmit_queue, - struct cec_data, list); - cec_data_cancel(data); - } - if (adap->transmitting) - cec_data_cancel(adap->transmitting); - - /* - * Cancel the pending timeout work. We have to unlock - * the mutex when flushing the work since - * cec_wait_timeout() will take it. This is OK since - * no new entries can be added to wait_queue as long - * as adap->transmitting is NULL, which it is due to - * the cec_data_cancel() above. - */ - while (!list_empty(&adap->wait_queue)) { - data = list_first_entry(&adap->wait_queue, - struct cec_data, list); - - if (!cancel_delayed_work(&data->work)) { - mutex_unlock(&adap->lock); - flush_scheduled_work(); - mutex_lock(&adap->lock); - } - cec_data_cancel(data); - } - goto unlock; - } - - if (adap->transmitting && timeout) { - /* - * If we timeout, then log that. This really shouldn't - * happen and is an indication of a faulty CEC adapter - * driver, or the CEC bus is in some weird state. - */ - dprintk(0, "message %*ph timed out!\n", - adap->transmitting->msg.len, - adap->transmitting->msg.msg); - /* Just give up on this. */ - cec_data_cancel(adap->transmitting); - goto unlock; - } - - /* - * If we are still transmitting, or there is nothing new to - * transmit, then just continue waiting. - */ - if (adap->transmitting || list_empty(&adap->transmit_queue)) - goto unlock; - - /* Get a new message to transmit */ - data = list_first_entry(&adap->transmit_queue, - struct cec_data, list); - list_del_init(&data->list); - adap->transmit_queue_sz--; - /* Make this the current transmitting message */ - adap->transmitting = data; - - /* - * Suggested number of attempts as per the CEC 2.0 spec: - * 4 attempts is the default, except for 'secondary poll - * messages', i.e. poll messages not sent during the adapter - * configuration phase when it allocates logical addresses. - */ - if (data->msg.len == 1 && adap->is_configured) - attempts = 2; - else - attempts = 4; - - /* Set the suggested signal free time */ - if (data->attempts) { - /* should be >= 3 data bit periods for a retry */ - signal_free_time = CEC_SIGNAL_FREE_TIME_RETRY; - } else if (data->new_initiator) { - /* should be >= 5 data bit periods for new initiator */ - signal_free_time = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR; - } else { - /* - * should be >= 7 data bit periods for sending another - * frame immediately after another. - */ - signal_free_time = CEC_SIGNAL_FREE_TIME_NEXT_XFER; - } - if (data->attempts == 0) - data->attempts = attempts; - - /* Tell the adapter to transmit, cancel on error */ - if (adap->ops->adap_transmit(adap, data->attempts, - signal_free_time, &data->msg)) - cec_data_cancel(data); - -unlock: - mutex_unlock(&adap->lock); - - if (kthread_should_stop()) - break; - } - return 0; -} - -/* - * Called by the CEC adapter if a transmit finished. - */ -void cec_transmit_done(struct cec_adapter *adap, u8 status, u8 arb_lost_cnt, - u8 nack_cnt, u8 low_drive_cnt, u8 error_cnt) -{ - struct cec_data *data; - struct cec_msg *msg; - u64 ts = ktime_get_ns(); - - dprintk(2, "cec_transmit_done %02x\n", status); - mutex_lock(&adap->lock); - data = adap->transmitting; - if (!data) { - /* - * This can happen if a transmit was issued and the cable is - * unplugged while the transmit is ongoing. Ignore this - * transmit in that case. - */ - dprintk(1, "cec_transmit_done without an ongoing transmit!\n"); - goto unlock; - } - - msg = &data->msg; - - /* Drivers must fill in the status! */ - WARN_ON(status == 0); - msg->tx_ts = ts; - msg->tx_status |= status; - msg->tx_arb_lost_cnt += arb_lost_cnt; - msg->tx_nack_cnt += nack_cnt; - msg->tx_low_drive_cnt += low_drive_cnt; - msg->tx_error_cnt += error_cnt; - - /* Mark that we're done with this transmit */ - adap->transmitting = NULL; - - /* - * If there are still retry attempts left and there was an error and - * the hardware didn't signal that it retried itself (by setting - * CEC_TX_STATUS_MAX_RETRIES), then we will retry ourselves. - */ - if (data->attempts > 1 && - !(status & (CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_OK))) { - /* Retry this message */ - data->attempts--; - /* Add the message in front of the transmit queue */ - list_add(&data->list, &adap->transmit_queue); - adap->transmit_queue_sz++; - goto wake_thread; - } - - data->attempts = 0; - - /* Always set CEC_TX_STATUS_MAX_RETRIES on error */ - if (!(status & CEC_TX_STATUS_OK)) - msg->tx_status |= CEC_TX_STATUS_MAX_RETRIES; - - /* Queue transmitted message for monitoring purposes */ - cec_queue_msg_monitor(adap, msg, 1); - - if ((status & CEC_TX_STATUS_OK) && adap->is_configured && - msg->timeout) { - /* - * Queue the message into the wait queue if we want to wait - * for a reply. - */ - list_add_tail(&data->list, &adap->wait_queue); - schedule_delayed_work(&data->work, - msecs_to_jiffies(msg->timeout)); - } else { - /* Otherwise we're done */ - cec_data_completed(data); - } - -wake_thread: - /* - * Wake up the main thread to see if another message is ready - * for transmitting or to retry the current message. - */ - wake_up_interruptible(&adap->kthread_waitq); -unlock: - mutex_unlock(&adap->lock); -} -EXPORT_SYMBOL_GPL(cec_transmit_done); - -/* - * Called when waiting for a reply times out. - */ -static void cec_wait_timeout(struct work_struct *work) -{ - struct cec_data *data = container_of(work, struct cec_data, work.work); - struct cec_adapter *adap = data->adap; - - mutex_lock(&adap->lock); - /* - * Sanity check in case the timeout and the arrival of the message - * happened at the same time. - */ - if (list_empty(&data->list)) - goto unlock; - - /* Mark the message as timed out */ - list_del_init(&data->list); - data->msg.rx_ts = ktime_get_ns(); - data->msg.rx_status = CEC_RX_STATUS_TIMEOUT; - cec_data_completed(data); -unlock: - mutex_unlock(&adap->lock); -} - -/* - * Transmit a message. The fh argument may be NULL if the transmit is not - * associated with a specific filehandle. - * - * This function is called with adap->lock held. - */ -int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, - struct cec_fh *fh, bool block) -{ - struct cec_data *data; - u8 last_initiator = 0xff; - unsigned int timeout; - int res = 0; - - msg->rx_ts = 0; - msg->tx_ts = 0; - msg->rx_status = 0; - msg->tx_status = 0; - msg->tx_arb_lost_cnt = 0; - msg->tx_nack_cnt = 0; - msg->tx_low_drive_cnt = 0; - msg->tx_error_cnt = 0; - msg->sequence = ++adap->sequence; - if (!msg->sequence) - msg->sequence = ++adap->sequence; - - if (msg->reply && msg->timeout == 0) { - /* Make sure the timeout isn't 0. */ - msg->timeout = 1000; - } - - /* Sanity checks */ - if (msg->len == 0 || msg->len > CEC_MAX_MSG_SIZE) { - dprintk(1, "cec_transmit_msg: invalid length %d\n", msg->len); - return -EINVAL; - } - if (msg->timeout && msg->len == 1) { - dprintk(1, "cec_transmit_msg: can't reply for poll msg\n"); - return -EINVAL; - } - memset(msg->msg + msg->len, 0, sizeof(msg->msg) - msg->len); - if (msg->len == 1) { - if (cec_msg_initiator(msg) != 0xf || - cec_msg_destination(msg) == 0xf) { - dprintk(1, "cec_transmit_msg: invalid poll message\n"); - return -EINVAL; - } - if (cec_has_log_addr(adap, cec_msg_destination(msg))) { - /* - * If the destination is a logical address our adapter - * has already claimed, then just NACK this. - * It depends on the hardware what it will do with a - * POLL to itself (some OK this), so it is just as - * easy to handle it here so the behavior will be - * consistent. - */ - msg->tx_ts = ktime_get_ns(); - msg->tx_status = CEC_TX_STATUS_NACK | - CEC_TX_STATUS_MAX_RETRIES; - msg->tx_nack_cnt = 1; - return 0; - } - } - if (msg->len > 1 && !cec_msg_is_broadcast(msg) && - cec_has_log_addr(adap, cec_msg_destination(msg))) { - dprintk(1, "cec_transmit_msg: destination is the adapter itself\n"); - return -EINVAL; - } - if (cec_msg_initiator(msg) != 0xf && - !cec_has_log_addr(adap, cec_msg_initiator(msg))) { - dprintk(1, "cec_transmit_msg: initiator has unknown logical address %d\n", - cec_msg_initiator(msg)); - return -EINVAL; - } - if (!adap->is_configured && !adap->is_configuring) - return -ENONET; - - if (adap->transmit_queue_sz >= CEC_MAX_MSG_TX_QUEUE_SZ) - return -EBUSY; - - data = kzalloc(sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - if (msg->len > 1 && msg->msg[1] == CEC_MSG_CDC_MESSAGE) { - msg->msg[2] = adap->phys_addr >> 8; - msg->msg[3] = adap->phys_addr & 0xff; - } - - if (msg->timeout) - dprintk(2, "cec_transmit_msg: %*ph (wait for 0x%02x%s)\n", - msg->len, msg->msg, msg->reply, !block ? ", nb" : ""); - else - dprintk(2, "cec_transmit_msg: %*ph%s\n", - msg->len, msg->msg, !block ? " (nb)" : ""); - - data->msg = *msg; - data->fh = fh; - data->adap = adap; - data->blocking = block; - - /* - * Determine if this message follows a message from the same - * initiator. Needed to determine the free signal time later on. - */ - if (msg->len > 1) { - if (!(list_empty(&adap->transmit_queue))) { - const struct cec_data *last; - - last = list_last_entry(&adap->transmit_queue, - const struct cec_data, list); - last_initiator = cec_msg_initiator(&last->msg); - } else if (adap->transmitting) { - last_initiator = - cec_msg_initiator(&adap->transmitting->msg); - } - } - data->new_initiator = last_initiator != cec_msg_initiator(msg); - init_completion(&data->c); - INIT_DELAYED_WORK(&data->work, cec_wait_timeout); - - if (fh) - list_add_tail(&data->xfer_list, &fh->xfer_list); - list_add_tail(&data->list, &adap->transmit_queue); - adap->transmit_queue_sz++; - if (!adap->transmitting) - wake_up_interruptible(&adap->kthread_waitq); - - /* All done if we don't need to block waiting for completion */ - if (!block) - 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. - */ - mutex_unlock(&adap->lock); - res = wait_for_completion_killable_timeout(&data->c, - msecs_to_jiffies(timeout)); - mutex_lock(&adap->lock); - - if (data->completed) { - /* The transmit completed (possibly with an error) */ - *msg = data->msg; - 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 */ - /* Check if the reply or the transmit failed */ - if (msg->timeout && (msg->tx_status & CEC_TX_STATUS_OK)) - msg->rx_status = CEC_RX_STATUS_TIMEOUT; - else - msg->tx_status = CEC_TX_STATUS_MAX_RETRIES; - } - return res > 0 ? 0 : res; -} - -/* Helper function to be used by drivers and this framework. */ -int cec_transmit_msg(struct cec_adapter *adap, struct cec_msg *msg, - bool block) -{ - int ret; - - mutex_lock(&adap->lock); - ret = cec_transmit_msg_fh(adap, msg, NULL, block); - mutex_unlock(&adap->lock); - return ret; -} -EXPORT_SYMBOL_GPL(cec_transmit_msg); - -/* - * I don't like forward references but without this the low-level - * cec_received_msg() function would come after a bunch of high-level - * CEC protocol handling functions. That was very confusing. - */ -static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, - bool is_reply); - -#define DIRECTED 0x80 -#define BCAST1_4 0x40 -#define BCAST2_0 0x20 /* broadcast only allowed for >= 2.0 */ -#define BCAST (BCAST1_4 | BCAST2_0) -#define BOTH (BCAST | DIRECTED) - -/* - * Specify minimum length and whether the message is directed, broadcast - * or both. Messages that do not match the criteria are ignored as per - * the CEC specification. - */ -static const u8 cec_msg_size[256] = { - [CEC_MSG_ACTIVE_SOURCE] = 4 | BCAST, - [CEC_MSG_IMAGE_VIEW_ON] = 2 | DIRECTED, - [CEC_MSG_TEXT_VIEW_ON] = 2 | DIRECTED, - [CEC_MSG_INACTIVE_SOURCE] = 4 | DIRECTED, - [CEC_MSG_REQUEST_ACTIVE_SOURCE] = 2 | BCAST, - [CEC_MSG_ROUTING_CHANGE] = 6 | BCAST, - [CEC_MSG_ROUTING_INFORMATION] = 4 | BCAST, - [CEC_MSG_SET_STREAM_PATH] = 4 | BCAST, - [CEC_MSG_STANDBY] = 2 | BOTH, - [CEC_MSG_RECORD_OFF] = 2 | DIRECTED, - [CEC_MSG_RECORD_ON] = 3 | DIRECTED, - [CEC_MSG_RECORD_STATUS] = 3 | DIRECTED, - [CEC_MSG_RECORD_TV_SCREEN] = 2 | DIRECTED, - [CEC_MSG_CLEAR_ANALOGUE_TIMER] = 13 | DIRECTED, - [CEC_MSG_CLEAR_DIGITAL_TIMER] = 16 | DIRECTED, - [CEC_MSG_CLEAR_EXT_TIMER] = 13 | DIRECTED, - [CEC_MSG_SET_ANALOGUE_TIMER] = 13 | DIRECTED, - [CEC_MSG_SET_DIGITAL_TIMER] = 16 | DIRECTED, - [CEC_MSG_SET_EXT_TIMER] = 13 | DIRECTED, - [CEC_MSG_SET_TIMER_PROGRAM_TITLE] = 2 | DIRECTED, - [CEC_MSG_TIMER_CLEARED_STATUS] = 3 | DIRECTED, - [CEC_MSG_TIMER_STATUS] = 3 | DIRECTED, - [CEC_MSG_CEC_VERSION] = 3 | DIRECTED, - [CEC_MSG_GET_CEC_VERSION] = 2 | DIRECTED, - [CEC_MSG_GIVE_PHYSICAL_ADDR] = 2 | DIRECTED, - [CEC_MSG_GET_MENU_LANGUAGE] = 2 | DIRECTED, - [CEC_MSG_REPORT_PHYSICAL_ADDR] = 5 | BCAST, - [CEC_MSG_SET_MENU_LANGUAGE] = 5 | BCAST, - [CEC_MSG_REPORT_FEATURES] = 6 | BCAST, - [CEC_MSG_GIVE_FEATURES] = 2 | DIRECTED, - [CEC_MSG_DECK_CONTROL] = 3 | DIRECTED, - [CEC_MSG_DECK_STATUS] = 3 | DIRECTED, - [CEC_MSG_GIVE_DECK_STATUS] = 3 | DIRECTED, - [CEC_MSG_PLAY] = 3 | DIRECTED, - [CEC_MSG_GIVE_TUNER_DEVICE_STATUS] = 3 | DIRECTED, - [CEC_MSG_SELECT_ANALOGUE_SERVICE] = 6 | DIRECTED, - [CEC_MSG_SELECT_DIGITAL_SERVICE] = 9 | DIRECTED, - [CEC_MSG_TUNER_DEVICE_STATUS] = 7 | DIRECTED, - [CEC_MSG_TUNER_STEP_DECREMENT] = 2 | DIRECTED, - [CEC_MSG_TUNER_STEP_INCREMENT] = 2 | DIRECTED, - [CEC_MSG_DEVICE_VENDOR_ID] = 5 | BCAST, - [CEC_MSG_GIVE_DEVICE_VENDOR_ID] = 2 | DIRECTED, - [CEC_MSG_VENDOR_COMMAND] = 2 | DIRECTED, - [CEC_MSG_VENDOR_COMMAND_WITH_ID] = 5 | BOTH, - [CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN] = 2 | BOTH, - [CEC_MSG_VENDOR_REMOTE_BUTTON_UP] = 2 | BOTH, - [CEC_MSG_SET_OSD_STRING] = 3 | DIRECTED, - [CEC_MSG_GIVE_OSD_NAME] = 2 | DIRECTED, - [CEC_MSG_SET_OSD_NAME] = 2 | DIRECTED, - [CEC_MSG_MENU_REQUEST] = 3 | DIRECTED, - [CEC_MSG_MENU_STATUS] = 3 | DIRECTED, - [CEC_MSG_USER_CONTROL_PRESSED] = 3 | DIRECTED, - [CEC_MSG_USER_CONTROL_RELEASED] = 2 | DIRECTED, - [CEC_MSG_GIVE_DEVICE_POWER_STATUS] = 2 | DIRECTED, - [CEC_MSG_REPORT_POWER_STATUS] = 3 | DIRECTED | BCAST2_0, - [CEC_MSG_FEATURE_ABORT] = 4 | DIRECTED, - [CEC_MSG_ABORT] = 2 | DIRECTED, - [CEC_MSG_GIVE_AUDIO_STATUS] = 2 | DIRECTED, - [CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS] = 2 | DIRECTED, - [CEC_MSG_REPORT_AUDIO_STATUS] = 3 | DIRECTED, - [CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR] = 2 | DIRECTED, - [CEC_MSG_REQUEST_SHORT_AUDIO_DESCRIPTOR] = 2 | DIRECTED, - [CEC_MSG_SET_SYSTEM_AUDIO_MODE] = 3 | BOTH, - [CEC_MSG_SYSTEM_AUDIO_MODE_REQUEST] = 2 | DIRECTED, - [CEC_MSG_SYSTEM_AUDIO_MODE_STATUS] = 3 | DIRECTED, - [CEC_MSG_SET_AUDIO_RATE] = 3 | DIRECTED, - [CEC_MSG_INITIATE_ARC] = 2 | DIRECTED, - [CEC_MSG_REPORT_ARC_INITIATED] = 2 | DIRECTED, - [CEC_MSG_REPORT_ARC_TERMINATED] = 2 | DIRECTED, - [CEC_MSG_REQUEST_ARC_INITIATION] = 2 | DIRECTED, - [CEC_MSG_REQUEST_ARC_TERMINATION] = 2 | DIRECTED, - [CEC_MSG_TERMINATE_ARC] = 2 | DIRECTED, - [CEC_MSG_REQUEST_CURRENT_LATENCY] = 4 | BCAST, - [CEC_MSG_REPORT_CURRENT_LATENCY] = 7 | BCAST, - [CEC_MSG_CDC_MESSAGE] = 2 | BCAST, -}; - -/* Called by the CEC adapter if a message is received */ -void cec_received_msg(struct cec_adapter *adap, struct cec_msg *msg) -{ - struct cec_data *data; - u8 msg_init = cec_msg_initiator(msg); - u8 msg_dest = cec_msg_destination(msg); - u8 cmd = msg->msg[1]; - bool is_reply = false; - bool valid_la = true; - u8 min_len = 0; - - if (WARN_ON(!msg->len || msg->len > CEC_MAX_MSG_SIZE)) - return; - - msg->rx_ts = ktime_get_ns(); - msg->rx_status = CEC_RX_STATUS_OK; - msg->sequence = msg->reply = msg->timeout = 0; - msg->tx_status = 0; - msg->tx_ts = 0; - msg->flags = 0; - memset(msg->msg + msg->len, 0, sizeof(msg->msg) - msg->len); - - mutex_lock(&adap->lock); - dprintk(2, "cec_received_msg: %*ph\n", msg->len, msg->msg); - - /* Check if this message was for us (directed or broadcast). */ - if (!cec_msg_is_broadcast(msg)) - valid_la = cec_has_log_addr(adap, msg_dest); - - /* - * Check if the length is not too short or if the message is a - * broadcast message where a directed message was expected or - * vice versa. If so, then the message has to be ignored (according - * to section CEC 7.3 and CEC 12.2). - */ - if (valid_la && msg->len > 1 && cec_msg_size[cmd]) { - u8 dir_fl = cec_msg_size[cmd] & BOTH; - - min_len = cec_msg_size[cmd] & 0x1f; - if (msg->len < min_len) - valid_la = false; - else if (!cec_msg_is_broadcast(msg) && !(dir_fl & DIRECTED)) - valid_la = false; - else if (cec_msg_is_broadcast(msg) && !(dir_fl & BCAST1_4)) - valid_la = false; - else if (cec_msg_is_broadcast(msg) && - adap->log_addrs.cec_version >= CEC_OP_CEC_VERSION_2_0 && - !(dir_fl & BCAST2_0)) - valid_la = false; - } - if (valid_la && min_len) { - /* These messages have special length requirements */ - switch (cmd) { - case CEC_MSG_TIMER_STATUS: - if (msg->msg[2] & 0x10) { - switch (msg->msg[2] & 0xf) { - case CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE: - case CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE: - if (msg->len < 5) - valid_la = false; - break; - } - } else if ((msg->msg[2] & 0xf) == CEC_OP_PROG_ERROR_DUPLICATE) { - if (msg->len < 5) - valid_la = false; - } - break; - case CEC_MSG_RECORD_ON: - switch (msg->msg[2]) { - case CEC_OP_RECORD_SRC_OWN: - break; - case CEC_OP_RECORD_SRC_DIGITAL: - if (msg->len < 10) - valid_la = false; - break; - case CEC_OP_RECORD_SRC_ANALOG: - if (msg->len < 7) - valid_la = false; - break; - case CEC_OP_RECORD_SRC_EXT_PLUG: - if (msg->len < 4) - valid_la = false; - break; - case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR: - if (msg->len < 5) - valid_la = false; - break; - } - break; - } - } - - /* It's a valid message and not a poll or CDC message */ - if (valid_la && msg->len > 1 && cmd != CEC_MSG_CDC_MESSAGE) { - bool abort = cmd == CEC_MSG_FEATURE_ABORT; - - /* The aborted command is in msg[2] */ - if (abort) - cmd = msg->msg[2]; - - /* - * Walk over all transmitted messages that are waiting for a - * reply. - */ - list_for_each_entry(data, &adap->wait_queue, list) { - struct cec_msg *dst = &data->msg; - - /* - * The *only* CEC message that has two possible replies - * is CEC_MSG_INITIATE_ARC. - * In this case allow either of the two replies. - */ - if (!abort && dst->msg[1] == CEC_MSG_INITIATE_ARC && - (cmd == CEC_MSG_REPORT_ARC_INITIATED || - cmd == CEC_MSG_REPORT_ARC_TERMINATED) && - (dst->reply == CEC_MSG_REPORT_ARC_INITIATED || - dst->reply == CEC_MSG_REPORT_ARC_TERMINATED)) - dst->reply = cmd; - - /* Does the command match? */ - if ((abort && cmd != dst->msg[1]) || - (!abort && cmd != dst->reply)) - continue; - - /* Does the addressing match? */ - if (msg_init != cec_msg_destination(dst) && - !cec_msg_is_broadcast(dst)) - continue; - - /* We got a reply */ - memcpy(dst->msg, msg->msg, msg->len); - dst->len = msg->len; - dst->rx_ts = msg->rx_ts; - dst->rx_status = msg->rx_status; - if (abort) - dst->rx_status |= CEC_RX_STATUS_FEATURE_ABORT; - msg->flags = dst->flags; - /* Remove it from the wait_queue */ - list_del_init(&data->list); - - /* Cancel the pending timeout work */ - if (!cancel_delayed_work(&data->work)) { - mutex_unlock(&adap->lock); - flush_scheduled_work(); - mutex_lock(&adap->lock); - } - /* - * Mark this as a reply, provided someone is still - * waiting for the answer. - */ - if (data->fh) - is_reply = true; - cec_data_completed(data); - break; - } - } - mutex_unlock(&adap->lock); - - /* Pass the message on to any monitoring filehandles */ - cec_queue_msg_monitor(adap, msg, valid_la); - - /* We're done if it is not for us or a poll message */ - if (!valid_la || msg->len <= 1) - return; - - if (adap->log_addrs.log_addr_mask == 0) - return; - - /* - * Process the message on the protocol level. If is_reply is true, - * then cec_receive_notify() won't pass on the reply to the listener(s) - * since that was already done by cec_data_completed() above. - */ - cec_receive_notify(adap, msg, is_reply); -} -EXPORT_SYMBOL_GPL(cec_received_msg); - -/* Logical Address Handling */ - -/* - * Attempt to claim a specific logical address. - * - * This function is called with adap->lock held. - */ -static int cec_config_log_addr(struct cec_adapter *adap, - unsigned int idx, - unsigned int log_addr) -{ - struct cec_log_addrs *las = &adap->log_addrs; - struct cec_msg msg = { }; - int err; - - if (cec_has_log_addr(adap, log_addr)) - return 0; - - /* Send poll message */ - msg.len = 1; - msg.msg[0] = 0xf0 | log_addr; - err = cec_transmit_msg_fh(adap, &msg, NULL, true); - - /* - * While trying to poll the physical address was reset - * and the adapter was unconfigured, so bail out. - */ - if (!adap->is_configuring) - return -EINTR; - - if (err) - return err; - - if (msg.tx_status & CEC_TX_STATUS_OK) - return 0; - - /* - * Message not acknowledged, so this logical - * address is free to use. - */ - err = adap->ops->adap_log_addr(adap, log_addr); - if (err) - return err; - - las->log_addr[idx] = log_addr; - las->log_addr_mask |= 1 << log_addr; - adap->phys_addrs[log_addr] = adap->phys_addr; - - dprintk(2, "claimed addr %d (%d)\n", log_addr, - las->primary_device_type[idx]); - return 1; -} - -/* - * Unconfigure the adapter: clear all logical addresses and send - * the state changed event. - * - * This function is called with adap->lock held. - */ -static void cec_adap_unconfigure(struct cec_adapter *adap) -{ - WARN_ON(adap->ops->adap_log_addr(adap, CEC_LOG_ADDR_INVALID)); - adap->log_addrs.log_addr_mask = 0; - adap->is_configuring = false; - adap->is_configured = false; - memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs)); - wake_up_interruptible(&adap->kthread_waitq); - cec_post_state_event(adap); -} - -/* - * Attempt to claim the required logical addresses. - */ -static int cec_config_thread_func(void *arg) -{ - /* The various LAs for each type of device */ - static const u8 tv_log_addrs[] = { - CEC_LOG_ADDR_TV, CEC_LOG_ADDR_SPECIFIC, - CEC_LOG_ADDR_INVALID - }; - static const u8 record_log_addrs[] = { - CEC_LOG_ADDR_RECORD_1, CEC_LOG_ADDR_RECORD_2, - CEC_LOG_ADDR_RECORD_3, - CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, - CEC_LOG_ADDR_INVALID - }; - static const u8 tuner_log_addrs[] = { - CEC_LOG_ADDR_TUNER_1, CEC_LOG_ADDR_TUNER_2, - CEC_LOG_ADDR_TUNER_3, CEC_LOG_ADDR_TUNER_4, - CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, - CEC_LOG_ADDR_INVALID - }; - static const u8 playback_log_addrs[] = { - CEC_LOG_ADDR_PLAYBACK_1, CEC_LOG_ADDR_PLAYBACK_2, - CEC_LOG_ADDR_PLAYBACK_3, - CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, - CEC_LOG_ADDR_INVALID - }; - static const u8 audiosystem_log_addrs[] = { - CEC_LOG_ADDR_AUDIOSYSTEM, - CEC_LOG_ADDR_INVALID - }; - static const u8 specific_use_log_addrs[] = { - CEC_LOG_ADDR_SPECIFIC, - CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, - CEC_LOG_ADDR_INVALID - }; - static const u8 *type2addrs[6] = { - [CEC_LOG_ADDR_TYPE_TV] = tv_log_addrs, - [CEC_LOG_ADDR_TYPE_RECORD] = record_log_addrs, - [CEC_LOG_ADDR_TYPE_TUNER] = tuner_log_addrs, - [CEC_LOG_ADDR_TYPE_PLAYBACK] = playback_log_addrs, - [CEC_LOG_ADDR_TYPE_AUDIOSYSTEM] = audiosystem_log_addrs, - [CEC_LOG_ADDR_TYPE_SPECIFIC] = specific_use_log_addrs, - }; - static const u16 type2mask[] = { - [CEC_LOG_ADDR_TYPE_TV] = CEC_LOG_ADDR_MASK_TV, - [CEC_LOG_ADDR_TYPE_RECORD] = CEC_LOG_ADDR_MASK_RECORD, - [CEC_LOG_ADDR_TYPE_TUNER] = CEC_LOG_ADDR_MASK_TUNER, - [CEC_LOG_ADDR_TYPE_PLAYBACK] = CEC_LOG_ADDR_MASK_PLAYBACK, - [CEC_LOG_ADDR_TYPE_AUDIOSYSTEM] = CEC_LOG_ADDR_MASK_AUDIOSYSTEM, - [CEC_LOG_ADDR_TYPE_SPECIFIC] = CEC_LOG_ADDR_MASK_SPECIFIC, - }; - struct cec_adapter *adap = arg; - struct cec_log_addrs *las = &adap->log_addrs; - int err; - int i, j; - - mutex_lock(&adap->lock); - dprintk(1, "physical address: %x.%x.%x.%x, claim %d logical addresses\n", - cec_phys_addr_exp(adap->phys_addr), las->num_log_addrs); - las->log_addr_mask = 0; - - if (las->log_addr_type[0] == CEC_LOG_ADDR_TYPE_UNREGISTERED) - goto configured; - - for (i = 0; i < las->num_log_addrs; i++) { - unsigned int type = las->log_addr_type[i]; - const u8 *la_list; - u8 last_la; - - /* - * The TV functionality can only map to physical address 0. - * For any other address, try the Specific functionality - * instead as per the spec. - */ - if (adap->phys_addr && type == CEC_LOG_ADDR_TYPE_TV) - type = CEC_LOG_ADDR_TYPE_SPECIFIC; - - la_list = type2addrs[type]; - last_la = las->log_addr[i]; - las->log_addr[i] = CEC_LOG_ADDR_INVALID; - if (last_la == CEC_LOG_ADDR_INVALID || - last_la == CEC_LOG_ADDR_UNREGISTERED || - !(last_la & type2mask[type])) - last_la = la_list[0]; - - err = cec_config_log_addr(adap, i, last_la); - if (err > 0) /* Reused last LA */ - continue; - - if (err < 0) - goto unconfigure; - - for (j = 0; la_list[j] != CEC_LOG_ADDR_INVALID; j++) { - /* Tried this one already, skip it */ - if (la_list[j] == last_la) - continue; - /* The backup addresses are CEC 2.0 specific */ - if ((la_list[j] == CEC_LOG_ADDR_BACKUP_1 || - la_list[j] == CEC_LOG_ADDR_BACKUP_2) && - las->cec_version < CEC_OP_CEC_VERSION_2_0) - continue; - - err = cec_config_log_addr(adap, i, la_list[j]); - if (err == 0) /* LA is in use */ - continue; - if (err < 0) - goto unconfigure; - /* Done, claimed an LA */ - break; - } - - if (la_list[j] == CEC_LOG_ADDR_INVALID) - dprintk(1, "could not claim LA %d\n", i); - } - - if (adap->log_addrs.log_addr_mask == 0 && - !(las->flags & CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK)) - goto unconfigure; - -configured: - if (adap->log_addrs.log_addr_mask == 0) { - /* Fall back to unregistered */ - las->log_addr[0] = CEC_LOG_ADDR_UNREGISTERED; - las->log_addr_mask = 1 << las->log_addr[0]; - for (i = 1; i < las->num_log_addrs; i++) - las->log_addr[i] = CEC_LOG_ADDR_INVALID; - } - adap->is_configured = true; - adap->is_configuring = false; - cec_post_state_event(adap); - mutex_unlock(&adap->lock); - - for (i = 0; i < las->num_log_addrs; i++) { - if (las->log_addr[i] == CEC_LOG_ADDR_INVALID || - (las->flags & CEC_LOG_ADDRS_FL_CDC_ONLY)) - continue; - - /* - * Report Features must come first according - * to CEC 2.0 - */ - if (las->log_addr[i] != CEC_LOG_ADDR_UNREGISTERED) - cec_report_features(adap, i); - cec_report_phys_addr(adap, i); - } - for (i = las->num_log_addrs; i < CEC_MAX_LOG_ADDRS; i++) - las->log_addr[i] = CEC_LOG_ADDR_INVALID; - mutex_lock(&adap->lock); - adap->kthread_config = NULL; - mutex_unlock(&adap->lock); - complete(&adap->config_completion); - return 0; - -unconfigure: - for (i = 0; i < las->num_log_addrs; i++) - las->log_addr[i] = CEC_LOG_ADDR_INVALID; - cec_adap_unconfigure(adap); - adap->kthread_config = NULL; - mutex_unlock(&adap->lock); - complete(&adap->config_completion); - return 0; -} - -/* - * Called from either __cec_s_phys_addr or __cec_s_log_addrs to claim the - * logical addresses. - * - * This function is called with adap->lock held. - */ -static void cec_claim_log_addrs(struct cec_adapter *adap, bool block) -{ - if (WARN_ON(adap->is_configuring || adap->is_configured)) - return; - - init_completion(&adap->config_completion); - - /* Ready to kick off the thread */ - adap->is_configuring = true; - adap->kthread_config = kthread_run(cec_config_thread_func, adap, - "ceccfg-%s", adap->name); - if (IS_ERR(adap->kthread_config)) { - adap->kthread_config = NULL; - } else if (block) { - mutex_unlock(&adap->lock); - wait_for_completion(&adap->config_completion); - mutex_lock(&adap->lock); - } -} - -/* Set a new physical address and send an event notifying userspace of this. - * - * This function is called with adap->lock held. - */ -void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) -{ - if (phys_addr == adap->phys_addr || adap->devnode.unregistered) - return; - - if (phys_addr == CEC_PHYS_ADDR_INVALID || - adap->phys_addr != CEC_PHYS_ADDR_INVALID) { - adap->phys_addr = CEC_PHYS_ADDR_INVALID; - cec_post_state_event(adap); - cec_adap_unconfigure(adap); - /* Disabling monitor all mode should always succeed */ - if (adap->monitor_all_cnt) - WARN_ON(call_op(adap, adap_monitor_all_enable, false)); - WARN_ON(adap->ops->adap_enable(adap, false)); - if (phys_addr == CEC_PHYS_ADDR_INVALID) - return; - } - - if (adap->ops->adap_enable(adap, true)) - return; - - if (adap->monitor_all_cnt && - call_op(adap, adap_monitor_all_enable, true)) { - WARN_ON(adap->ops->adap_enable(adap, false)); - return; - } - adap->phys_addr = phys_addr; - cec_post_state_event(adap); - if (adap->log_addrs.num_log_addrs) - cec_claim_log_addrs(adap, block); -} - -void cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) -{ - if (IS_ERR_OR_NULL(adap)) - return; - - mutex_lock(&adap->lock); - __cec_s_phys_addr(adap, phys_addr, block); - mutex_unlock(&adap->lock); -} -EXPORT_SYMBOL_GPL(cec_s_phys_addr); - -/* - * Called from either the ioctl or a driver to set the logical addresses. - * - * This function is called with adap->lock held. - */ -int __cec_s_log_addrs(struct cec_adapter *adap, - struct cec_log_addrs *log_addrs, bool block) -{ - u16 type_mask = 0; - int i; - - if (adap->devnode.unregistered) - return -ENODEV; - - if (!log_addrs || log_addrs->num_log_addrs == 0) { - adap->log_addrs.num_log_addrs = 0; - cec_adap_unconfigure(adap); - return 0; - } - - if (log_addrs->flags & CEC_LOG_ADDRS_FL_CDC_ONLY) { - /* - * Sanitize log_addrs fields if a CDC-Only device is - * requested. - */ - log_addrs->num_log_addrs = 1; - log_addrs->osd_name[0] = '\0'; - log_addrs->vendor_id = CEC_VENDOR_ID_NONE; - log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED; - /* - * This is just an internal convention since a CDC-Only device - * doesn't have to be a switch. But switches already use - * unregistered, so it makes some kind of sense to pick this - * as the primary device. Since a CDC-Only device never sends - * any 'normal' CEC messages this primary device type is never - * sent over the CEC bus. - */ - log_addrs->primary_device_type[0] = CEC_OP_PRIM_DEVTYPE_SWITCH; - log_addrs->all_device_types[0] = 0; - log_addrs->features[0][0] = 0; - log_addrs->features[0][1] = 0; - } - - /* Ensure the osd name is 0-terminated */ - log_addrs->osd_name[sizeof(log_addrs->osd_name) - 1] = '\0'; - - /* Sanity checks */ - if (log_addrs->num_log_addrs > adap->available_log_addrs) { - dprintk(1, "num_log_addrs > %d\n", adap->available_log_addrs); - return -EINVAL; - } - - /* - * Vendor ID is a 24 bit number, so check if the value is - * within the correct range. - */ - if (log_addrs->vendor_id != CEC_VENDOR_ID_NONE && - (log_addrs->vendor_id & 0xff000000) != 0) - return -EINVAL; - - if (log_addrs->cec_version != CEC_OP_CEC_VERSION_1_4 && - log_addrs->cec_version != CEC_OP_CEC_VERSION_2_0) - return -EINVAL; - - if (log_addrs->num_log_addrs > 1) - for (i = 0; i < log_addrs->num_log_addrs; i++) - if (log_addrs->log_addr_type[i] == - CEC_LOG_ADDR_TYPE_UNREGISTERED) { - dprintk(1, "num_log_addrs > 1 can't be combined with unregistered LA\n"); - return -EINVAL; - } - - for (i = 0; i < log_addrs->num_log_addrs; i++) { - const u8 feature_sz = ARRAY_SIZE(log_addrs->features[0]); - u8 *features = log_addrs->features[i]; - bool op_is_dev_features = false; - - log_addrs->log_addr[i] = CEC_LOG_ADDR_INVALID; - if (type_mask & (1 << log_addrs->log_addr_type[i])) { - dprintk(1, "duplicate logical address type\n"); - return -EINVAL; - } - type_mask |= 1 << log_addrs->log_addr_type[i]; - if ((type_mask & (1 << CEC_LOG_ADDR_TYPE_RECORD)) && - (type_mask & (1 << CEC_LOG_ADDR_TYPE_PLAYBACK))) { - /* Record already contains the playback functionality */ - dprintk(1, "invalid record + playback combination\n"); - return -EINVAL; - } - if (log_addrs->primary_device_type[i] > - CEC_OP_PRIM_DEVTYPE_PROCESSOR) { - dprintk(1, "unknown primary device type\n"); - return -EINVAL; - } - if (log_addrs->primary_device_type[i] == 2) { - dprintk(1, "invalid primary device type\n"); - return -EINVAL; - } - if (log_addrs->log_addr_type[i] > CEC_LOG_ADDR_TYPE_UNREGISTERED) { - dprintk(1, "unknown logical address type\n"); - return -EINVAL; - } - for (i = 0; i < feature_sz; i++) { - if ((features[i] & 0x80) == 0) { - if (op_is_dev_features) - break; - op_is_dev_features = true; - } - } - if (!op_is_dev_features || i == feature_sz) { - dprintk(1, "malformed features\n"); - return -EINVAL; - } - /* Zero unused part of the feature array */ - memset(features + i + 1, 0, feature_sz - i - 1); - } - - if (log_addrs->cec_version >= CEC_OP_CEC_VERSION_2_0) { - if (log_addrs->num_log_addrs > 2) { - dprintk(1, "CEC 2.0 allows no more than 2 logical addresses\n"); - return -EINVAL; - } - if (log_addrs->num_log_addrs == 2) { - if (!(type_mask & ((1 << CEC_LOG_ADDR_TYPE_AUDIOSYSTEM) | - (1 << CEC_LOG_ADDR_TYPE_TV)))) { - dprintk(1, "Two LAs is only allowed for audiosystem and TV\n"); - return -EINVAL; - } - if (!(type_mask & ((1 << CEC_LOG_ADDR_TYPE_PLAYBACK) | - (1 << CEC_LOG_ADDR_TYPE_RECORD)))) { - dprintk(1, "An audiosystem/TV can only be combined with record or playback\n"); - return -EINVAL; - } - } - } - - /* Zero unused LAs */ - for (i = log_addrs->num_log_addrs; i < CEC_MAX_LOG_ADDRS; i++) { - log_addrs->primary_device_type[i] = 0; - log_addrs->log_addr_type[i] = 0; - log_addrs->all_device_types[i] = 0; - memset(log_addrs->features[i], 0, - sizeof(log_addrs->features[i])); - } - - log_addrs->log_addr_mask = adap->log_addrs.log_addr_mask; - adap->log_addrs = *log_addrs; - if (adap->phys_addr != CEC_PHYS_ADDR_INVALID) - cec_claim_log_addrs(adap, block); - return 0; -} - -int cec_s_log_addrs(struct cec_adapter *adap, - struct cec_log_addrs *log_addrs, bool block) -{ - int err; - - mutex_lock(&adap->lock); - err = __cec_s_log_addrs(adap, log_addrs, block); - mutex_unlock(&adap->lock); - return err; -} -EXPORT_SYMBOL_GPL(cec_s_log_addrs); - -/* High-level core CEC message handling */ - -/* Transmit the Report Features message */ -static int cec_report_features(struct cec_adapter *adap, unsigned int la_idx) -{ - struct cec_msg msg = { }; - const struct cec_log_addrs *las = &adap->log_addrs; - const u8 *features = las->features[la_idx]; - bool op_is_dev_features = false; - unsigned int idx; - - /* This is 2.0 and up only */ - if (adap->log_addrs.cec_version < CEC_OP_CEC_VERSION_2_0) - return 0; - - /* Report Features */ - msg.msg[0] = (las->log_addr[la_idx] << 4) | 0x0f; - msg.len = 4; - msg.msg[1] = CEC_MSG_REPORT_FEATURES; - msg.msg[2] = adap->log_addrs.cec_version; - msg.msg[3] = las->all_device_types[la_idx]; - - /* Write RC Profiles first, then Device Features */ - for (idx = 0; idx < ARRAY_SIZE(las->features[0]); idx++) { - msg.msg[msg.len++] = features[idx]; - if ((features[idx] & CEC_OP_FEAT_EXT) == 0) { - if (op_is_dev_features) - break; - op_is_dev_features = true; - } - } - return cec_transmit_msg(adap, &msg, false); -} - -/* Transmit the Report Physical Address message */ -static int cec_report_phys_addr(struct cec_adapter *adap, unsigned int la_idx) -{ - const struct cec_log_addrs *las = &adap->log_addrs; - struct cec_msg msg = { }; - - /* Report Physical Address */ - msg.msg[0] = (las->log_addr[la_idx] << 4) | 0x0f; - cec_msg_report_physical_addr(&msg, adap->phys_addr, - las->primary_device_type[la_idx]); - dprintk(2, "config: la %d pa %x.%x.%x.%x\n", - las->log_addr[la_idx], - cec_phys_addr_exp(adap->phys_addr)); - return cec_transmit_msg(adap, &msg, false); -} - -/* Transmit the Feature Abort message */ -static int cec_feature_abort_reason(struct cec_adapter *adap, - struct cec_msg *msg, u8 reason) -{ - struct cec_msg tx_msg = { }; - - /* - * Don't reply with CEC_MSG_FEATURE_ABORT to a CEC_MSG_FEATURE_ABORT - * message! - */ - if (msg->msg[1] == CEC_MSG_FEATURE_ABORT) - return 0; - cec_msg_set_reply_to(&tx_msg, msg); - cec_msg_feature_abort(&tx_msg, msg->msg[1], reason); - return cec_transmit_msg(adap, &tx_msg, false); -} - -static int cec_feature_abort(struct cec_adapter *adap, struct cec_msg *msg) -{ - return cec_feature_abort_reason(adap, msg, - CEC_OP_ABORT_UNRECOGNIZED_OP); -} - -static int cec_feature_refused(struct cec_adapter *adap, struct cec_msg *msg) -{ - return cec_feature_abort_reason(adap, msg, - CEC_OP_ABORT_REFUSED); -} - -/* - * Called when a CEC message is received. This function will do any - * necessary core processing. The is_reply bool is true if this message - * is a reply to an earlier transmit. - * - * The message is either a broadcast message or a valid directed message. - */ -static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, - bool is_reply) -{ - bool is_broadcast = cec_msg_is_broadcast(msg); - u8 dest_laddr = cec_msg_destination(msg); - u8 init_laddr = cec_msg_initiator(msg); - u8 devtype = cec_log_addr2dev(adap, dest_laddr); - int la_idx = cec_log_addr2idx(adap, dest_laddr); - bool from_unregistered = init_laddr == 0xf; - struct cec_msg tx_cec_msg = { }; - - dprintk(1, "cec_receive_notify: %*ph\n", msg->len, msg->msg); - - /* If this is a CDC-Only device, then ignore any non-CDC messages */ - if (cec_is_cdc_only(&adap->log_addrs) && - msg->msg[1] != CEC_MSG_CDC_MESSAGE) - return 0; - - if (adap->ops->received) { - /* Allow drivers to process the message first */ - if (adap->ops->received(adap, msg) != -ENOMSG) - return 0; - } - - /* - * REPORT_PHYSICAL_ADDR, CEC_MSG_USER_CONTROL_PRESSED and - * CEC_MSG_USER_CONTROL_RELEASED messages always have to be - * handled by the CEC core, even if the passthrough mode is on. - * The others are just ignored if passthrough mode is on. - */ - switch (msg->msg[1]) { - case CEC_MSG_GET_CEC_VERSION: - case CEC_MSG_GIVE_DEVICE_VENDOR_ID: - case CEC_MSG_ABORT: - case CEC_MSG_GIVE_DEVICE_POWER_STATUS: - case CEC_MSG_GIVE_PHYSICAL_ADDR: - case CEC_MSG_GIVE_OSD_NAME: - case CEC_MSG_GIVE_FEATURES: - /* - * Skip processing these messages if the passthrough mode - * is on. - */ - if (adap->passthrough) - goto skip_processing; - /* Ignore if addressing is wrong */ - if (is_broadcast || from_unregistered) - return 0; - break; - - case CEC_MSG_USER_CONTROL_PRESSED: - case CEC_MSG_USER_CONTROL_RELEASED: - /* Wrong addressing mode: don't process */ - if (is_broadcast || from_unregistered) - goto skip_processing; - break; - - case CEC_MSG_REPORT_PHYSICAL_ADDR: - /* - * This message is always processed, regardless of the - * passthrough setting. - * - * Exception: don't process if wrong addressing mode. - */ - if (!is_broadcast) - goto skip_processing; - break; - - default: - break; - } - - cec_msg_set_reply_to(&tx_cec_msg, msg); - - switch (msg->msg[1]) { - /* The following messages are processed but still passed through */ - case CEC_MSG_REPORT_PHYSICAL_ADDR: { - u16 pa = (msg->msg[2] << 8) | msg->msg[3]; - - if (!from_unregistered) - adap->phys_addrs[init_laddr] = pa; - dprintk(1, "Reported physical address %x.%x.%x.%x for logical address %d\n", - cec_phys_addr_exp(pa), init_laddr); - break; - } - - case CEC_MSG_USER_CONTROL_PRESSED: - if (!(adap->capabilities & CEC_CAP_RC) || - !(adap->log_addrs.flags & CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU)) - break; - -#if IS_REACHABLE(CONFIG_RC_CORE) - switch (msg->msg[2]) { - /* - * Play function, this message can have variable length - * depending on the specific play function that is used. - */ - case 0x60: - if (msg->len == 2) - rc_keydown(adap->rc, RC_TYPE_CEC, - msg->msg[2], 0); - else - rc_keydown(adap->rc, RC_TYPE_CEC, - msg->msg[2] << 8 | msg->msg[3], 0); - break; - /* - * Other function messages that are not handled. - * Currently the RC framework does not allow to supply an - * additional parameter to a keypress. These "keys" contain - * other information such as channel number, an input number - * etc. - * For the time being these messages are not processed by the - * framework and are simply forwarded to the user space. - */ - case 0x56: case 0x57: - case 0x67: case 0x68: case 0x69: case 0x6a: - break; - default: - rc_keydown(adap->rc, RC_TYPE_CEC, msg->msg[2], 0); - break; - } -#endif - break; - - case CEC_MSG_USER_CONTROL_RELEASED: - if (!(adap->capabilities & CEC_CAP_RC) || - !(adap->log_addrs.flags & CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU)) - break; -#if IS_REACHABLE(CONFIG_RC_CORE) - rc_keyup(adap->rc); -#endif - break; - - /* - * The remaining messages are only processed if the passthrough mode - * is off. - */ - case CEC_MSG_GET_CEC_VERSION: - cec_msg_cec_version(&tx_cec_msg, adap->log_addrs.cec_version); - return cec_transmit_msg(adap, &tx_cec_msg, false); - - case CEC_MSG_GIVE_PHYSICAL_ADDR: - /* Do nothing for CEC switches using addr 15 */ - if (devtype == CEC_OP_PRIM_DEVTYPE_SWITCH && dest_laddr == 15) - return 0; - cec_msg_report_physical_addr(&tx_cec_msg, adap->phys_addr, devtype); - return cec_transmit_msg(adap, &tx_cec_msg, false); - - case CEC_MSG_GIVE_DEVICE_VENDOR_ID: - if (adap->log_addrs.vendor_id == CEC_VENDOR_ID_NONE) - return cec_feature_abort(adap, msg); - cec_msg_device_vendor_id(&tx_cec_msg, adap->log_addrs.vendor_id); - return cec_transmit_msg(adap, &tx_cec_msg, false); - - case CEC_MSG_ABORT: - /* Do nothing for CEC switches */ - if (devtype == CEC_OP_PRIM_DEVTYPE_SWITCH) - return 0; - return cec_feature_refused(adap, msg); - - case CEC_MSG_GIVE_OSD_NAME: { - if (adap->log_addrs.osd_name[0] == 0) - return cec_feature_abort(adap, msg); - cec_msg_set_osd_name(&tx_cec_msg, adap->log_addrs.osd_name); - return cec_transmit_msg(adap, &tx_cec_msg, false); - } - - case CEC_MSG_GIVE_FEATURES: - if (adap->log_addrs.cec_version >= CEC_OP_CEC_VERSION_2_0) - return cec_report_features(adap, la_idx); - return 0; - - default: - /* - * Unprocessed messages are aborted if userspace isn't doing - * any processing either. - */ - if (!is_broadcast && !is_reply && !adap->follower_cnt && - !adap->cec_follower && msg->msg[1] != CEC_MSG_FEATURE_ABORT) - return cec_feature_abort(adap, msg); - break; - } - -skip_processing: - /* If this was a reply, then we're done, unless otherwise specified */ - if (is_reply && !(msg->flags & CEC_MSG_FL_REPLY_TO_FOLLOWERS)) - return 0; - - /* - * Send to the exclusive follower if there is one, otherwise send - * to all followers. - */ - if (adap->cec_follower) - cec_queue_msg_fh(adap->cec_follower, msg); - else - cec_queue_msg_followers(adap, msg); - return 0; -} - -/* - * Helper functions to keep track of the 'monitor all' use count. - * - * These functions are called with adap->lock held. - */ -int cec_monitor_all_cnt_inc(struct cec_adapter *adap) -{ - int ret = 0; - - if (adap->monitor_all_cnt == 0) - ret = call_op(adap, adap_monitor_all_enable, 1); - if (ret == 0) - adap->monitor_all_cnt++; - return ret; -} - -void cec_monitor_all_cnt_dec(struct cec_adapter *adap) -{ - adap->monitor_all_cnt--; - if (adap->monitor_all_cnt == 0) - WARN_ON(call_op(adap, adap_monitor_all_enable, 0)); -} - -#ifdef CONFIG_MEDIA_CEC_DEBUG -/* - * Log the current state of the CEC adapter. - * Very useful for debugging. - */ -int cec_adap_status(struct seq_file *file, void *priv) -{ - struct cec_adapter *adap = dev_get_drvdata(file->private); - struct cec_data *data; - - mutex_lock(&adap->lock); - seq_printf(file, "configured: %d\n", adap->is_configured); - seq_printf(file, "configuring: %d\n", adap->is_configuring); - seq_printf(file, "phys_addr: %x.%x.%x.%x\n", - cec_phys_addr_exp(adap->phys_addr)); - seq_printf(file, "number of LAs: %d\n", adap->log_addrs.num_log_addrs); - seq_printf(file, "LA mask: 0x%04x\n", adap->log_addrs.log_addr_mask); - if (adap->cec_follower) - seq_printf(file, "has CEC follower%s\n", - adap->passthrough ? " (in passthrough mode)" : ""); - if (adap->cec_initiator) - seq_puts(file, "has CEC initiator\n"); - if (adap->monitor_all_cnt) - seq_printf(file, "file handles in Monitor All mode: %u\n", - adap->monitor_all_cnt); - data = adap->transmitting; - if (data) - seq_printf(file, "transmitting message: %*ph (reply: %02x, timeout: %ums)\n", - data->msg.len, data->msg.msg, data->msg.reply, - data->msg.timeout); - seq_printf(file, "pending transmits: %u\n", adap->transmit_queue_sz); - list_for_each_entry(data, &adap->transmit_queue, list) { - seq_printf(file, "queued tx message: %*ph (reply: %02x, timeout: %ums)\n", - data->msg.len, data->msg.msg, data->msg.reply, - data->msg.timeout); - } - list_for_each_entry(data, &adap->wait_queue, list) { - seq_printf(file, "message waiting for reply: %*ph (reply: %02x, timeout: %ums)\n", - data->msg.len, data->msg.msg, data->msg.reply, - data->msg.timeout); - } - - call_void_op(adap, adap_status, file); - mutex_unlock(&adap->lock); - return 0; -} -#endif diff --git a/drivers/staging/media/cec/cec-api.c b/drivers/staging/media/cec/cec-api.c deleted file mode 100644 index d4bc4ee2c6e5..000000000000 --- a/drivers/staging/media/cec/cec-api.c +++ /dev/null @@ -1,588 +0,0 @@ -/* - * cec-api.c - HDMI Consumer Electronics Control framework - API - * - * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - * - * This program is free software; you may redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cec-priv.h" - -static inline struct cec_devnode *cec_devnode_data(struct file *filp) -{ - struct cec_fh *fh = filp->private_data; - - return &fh->adap->devnode; -} - -/* CEC file operations */ - -static unsigned int cec_poll(struct file *filp, - struct poll_table_struct *poll) -{ - struct cec_devnode *devnode = cec_devnode_data(filp); - struct cec_fh *fh = filp->private_data; - struct cec_adapter *adap = fh->adap; - unsigned int res = 0; - - if (!devnode->registered) - return POLLERR | POLLHUP; - mutex_lock(&adap->lock); - if (adap->is_configured && - adap->transmit_queue_sz < CEC_MAX_MSG_TX_QUEUE_SZ) - res |= POLLOUT | POLLWRNORM; - if (fh->queued_msgs) - res |= POLLIN | POLLRDNORM; - if (fh->pending_events) - res |= POLLPRI; - poll_wait(filp, &fh->wait, poll); - mutex_unlock(&adap->lock); - return res; -} - -static bool cec_is_busy(const struct cec_adapter *adap, - const struct cec_fh *fh) -{ - bool valid_initiator = adap->cec_initiator && adap->cec_initiator == fh; - bool valid_follower = adap->cec_follower && adap->cec_follower == fh; - - /* - * Exclusive initiators and followers can always access the CEC adapter - */ - if (valid_initiator || valid_follower) - return false; - /* - * All others can only access the CEC adapter if there is no - * exclusive initiator and they are in INITIATOR mode. - */ - return adap->cec_initiator || - fh->mode_initiator == CEC_MODE_NO_INITIATOR; -} - -static long cec_adap_g_caps(struct cec_adapter *adap, - struct cec_caps __user *parg) -{ - struct cec_caps caps = {}; - - strlcpy(caps.driver, adap->devnode.parent->driver->name, - sizeof(caps.driver)); - strlcpy(caps.name, adap->name, sizeof(caps.name)); - caps.available_log_addrs = adap->available_log_addrs; - caps.capabilities = adap->capabilities; - caps.version = LINUX_VERSION_CODE; - if (copy_to_user(parg, &caps, sizeof(caps))) - return -EFAULT; - return 0; -} - -static long cec_adap_g_phys_addr(struct cec_adapter *adap, - __u16 __user *parg) -{ - u16 phys_addr; - - mutex_lock(&adap->lock); - phys_addr = adap->phys_addr; - mutex_unlock(&adap->lock); - if (copy_to_user(parg, &phys_addr, sizeof(phys_addr))) - return -EFAULT; - return 0; -} - -static long cec_adap_s_phys_addr(struct cec_adapter *adap, struct cec_fh *fh, - bool block, __u16 __user *parg) -{ - u16 phys_addr; - long err; - - if (!(adap->capabilities & CEC_CAP_PHYS_ADDR)) - return -ENOTTY; - if (copy_from_user(&phys_addr, parg, sizeof(phys_addr))) - return -EFAULT; - - err = cec_phys_addr_validate(phys_addr, NULL, NULL); - if (err) - return err; - mutex_lock(&adap->lock); - if (cec_is_busy(adap, fh)) - err = -EBUSY; - else - __cec_s_phys_addr(adap, phys_addr, block); - mutex_unlock(&adap->lock); - return err; -} - -static long cec_adap_g_log_addrs(struct cec_adapter *adap, - struct cec_log_addrs __user *parg) -{ - struct cec_log_addrs log_addrs; - - mutex_lock(&adap->lock); - log_addrs = adap->log_addrs; - if (!adap->is_configured) - memset(log_addrs.log_addr, CEC_LOG_ADDR_INVALID, - sizeof(log_addrs.log_addr)); - mutex_unlock(&adap->lock); - - if (copy_to_user(parg, &log_addrs, sizeof(log_addrs))) - return -EFAULT; - return 0; -} - -static long cec_adap_s_log_addrs(struct cec_adapter *adap, struct cec_fh *fh, - bool block, struct cec_log_addrs __user *parg) -{ - struct cec_log_addrs log_addrs; - long err = -EBUSY; - - if (!(adap->capabilities & CEC_CAP_LOG_ADDRS)) - return -ENOTTY; - if (copy_from_user(&log_addrs, parg, sizeof(log_addrs))) - return -EFAULT; - log_addrs.flags &= CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK | - CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU | - CEC_LOG_ADDRS_FL_CDC_ONLY; - mutex_lock(&adap->lock); - if (!adap->is_configuring && - (!log_addrs.num_log_addrs || !adap->is_configured) && - !cec_is_busy(adap, fh)) { - err = __cec_s_log_addrs(adap, &log_addrs, block); - if (!err) - log_addrs = adap->log_addrs; - } - mutex_unlock(&adap->lock); - if (err) - return err; - if (copy_to_user(parg, &log_addrs, sizeof(log_addrs))) - return -EFAULT; - return 0; -} - -static long cec_transmit(struct cec_adapter *adap, struct cec_fh *fh, - bool block, struct cec_msg __user *parg) -{ - struct cec_msg msg = {}; - long err = 0; - - if (!(adap->capabilities & CEC_CAP_TRANSMIT)) - return -ENOTTY; - if (copy_from_user(&msg, parg, sizeof(msg))) - return -EFAULT; - - /* A CDC-Only device can only send CDC messages */ - if ((adap->log_addrs.flags & CEC_LOG_ADDRS_FL_CDC_ONLY) && - (msg.len == 1 || msg.msg[1] != CEC_MSG_CDC_MESSAGE)) - return -EINVAL; - - msg.flags &= CEC_MSG_FL_REPLY_TO_FOLLOWERS; - mutex_lock(&adap->lock); - if (!adap->is_configured) - err = -ENONET; - else if (cec_is_busy(adap, fh)) - err = -EBUSY; - else - err = cec_transmit_msg_fh(adap, &msg, fh, block); - mutex_unlock(&adap->lock); - if (err) - return err; - if (copy_to_user(parg, &msg, sizeof(msg))) - return -EFAULT; - return 0; -} - -/* Called by CEC_RECEIVE: wait for a message to arrive */ -static int cec_receive_msg(struct cec_fh *fh, struct cec_msg *msg, bool block) -{ - u32 timeout = msg->timeout; - int res; - - do { - mutex_lock(&fh->lock); - /* Are there received messages queued up? */ - if (fh->queued_msgs) { - /* Yes, return the first one */ - struct cec_msg_entry *entry = - list_first_entry(&fh->msgs, - struct cec_msg_entry, list); - - list_del(&entry->list); - *msg = entry->msg; - kfree(entry); - fh->queued_msgs--; - mutex_unlock(&fh->lock); - /* restore original timeout value */ - msg->timeout = timeout; - return 0; - } - - /* No, return EAGAIN in non-blocking mode or wait */ - mutex_unlock(&fh->lock); - - /* Return when in non-blocking mode */ - if (!block) - return -EAGAIN; - - if (msg->timeout) { - /* The user specified a timeout */ - res = wait_event_interruptible_timeout(fh->wait, - fh->queued_msgs, - msecs_to_jiffies(msg->timeout)); - if (res == 0) - res = -ETIMEDOUT; - else if (res > 0) - res = 0; - } else { - /* Wait indefinitely */ - res = wait_event_interruptible(fh->wait, - fh->queued_msgs); - } - /* Exit on error, otherwise loop to get the new message */ - } while (!res); - return res; -} - -static long cec_receive(struct cec_adapter *adap, struct cec_fh *fh, - bool block, struct cec_msg __user *parg) -{ - struct cec_msg msg = {}; - long err = 0; - - if (copy_from_user(&msg, parg, sizeof(msg))) - return -EFAULT; - mutex_lock(&adap->lock); - if (!adap->is_configured && fh->mode_follower < CEC_MODE_MONITOR) - err = -ENONET; - mutex_unlock(&adap->lock); - if (err) - return err; - - err = cec_receive_msg(fh, &msg, block); - if (err) - return err; - if (copy_to_user(parg, &msg, sizeof(msg))) - return -EFAULT; - return 0; -} - -static long cec_dqevent(struct cec_adapter *adap, struct cec_fh *fh, - bool block, struct cec_event __user *parg) -{ - struct cec_event *ev = NULL; - u64 ts = ~0ULL; - unsigned int i; - long err = 0; - - mutex_lock(&fh->lock); - while (!fh->pending_events && block) { - mutex_unlock(&fh->lock); - err = wait_event_interruptible(fh->wait, fh->pending_events); - if (err) - return err; - mutex_lock(&fh->lock); - } - - /* 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; - } - } - if (!ev) { - err = -EAGAIN; - goto unlock; - } - - if (copy_to_user(parg, ev, sizeof(*ev))) { - err = -EFAULT; - goto unlock; - } - - fh->pending_events &= ~(1 << ev->event); - -unlock: - mutex_unlock(&fh->lock); - return err; -} - -static long cec_g_mode(struct cec_adapter *adap, struct cec_fh *fh, - u32 __user *parg) -{ - u32 mode = fh->mode_initiator | fh->mode_follower; - - if (copy_to_user(parg, &mode, sizeof(mode))) - return -EFAULT; - return 0; -} - -static long cec_s_mode(struct cec_adapter *adap, struct cec_fh *fh, - u32 __user *parg) -{ - u32 mode; - u8 mode_initiator; - u8 mode_follower; - long err = 0; - - if (copy_from_user(&mode, parg, sizeof(mode))) - return -EFAULT; - if (mode & ~(CEC_MODE_INITIATOR_MSK | CEC_MODE_FOLLOWER_MSK)) - return -EINVAL; - - mode_initiator = mode & CEC_MODE_INITIATOR_MSK; - mode_follower = mode & CEC_MODE_FOLLOWER_MSK; - - if (mode_initiator > CEC_MODE_EXCL_INITIATOR || - mode_follower > CEC_MODE_MONITOR_ALL) - return -EINVAL; - - if (mode_follower == CEC_MODE_MONITOR_ALL && - !(adap->capabilities & CEC_CAP_MONITOR_ALL)) - return -EINVAL; - - /* Follower modes should always be able to send CEC messages */ - if ((mode_initiator == CEC_MODE_NO_INITIATOR || - !(adap->capabilities & CEC_CAP_TRANSMIT)) && - mode_follower >= CEC_MODE_FOLLOWER && - mode_follower <= CEC_MODE_EXCL_FOLLOWER_PASSTHRU) - return -EINVAL; - - /* Monitor modes require CEC_MODE_NO_INITIATOR */ - if (mode_initiator && mode_follower >= CEC_MODE_MONITOR) - return -EINVAL; - - /* Monitor modes require CAP_NET_ADMIN */ - if (mode_follower >= CEC_MODE_MONITOR && !capable(CAP_NET_ADMIN)) - return -EPERM; - - mutex_lock(&adap->lock); - /* - * You can't become exclusive follower if someone else already - * has that job. - */ - if ((mode_follower == CEC_MODE_EXCL_FOLLOWER || - mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU) && - adap->cec_follower && adap->cec_follower != fh) - err = -EBUSY; - /* - * You can't become exclusive initiator if someone else already - * has that job. - */ - if (mode_initiator == CEC_MODE_EXCL_INITIATOR && - adap->cec_initiator && adap->cec_initiator != fh) - err = -EBUSY; - - if (!err) { - bool old_mon_all = fh->mode_follower == CEC_MODE_MONITOR_ALL; - bool new_mon_all = mode_follower == CEC_MODE_MONITOR_ALL; - - if (old_mon_all != new_mon_all) { - if (new_mon_all) - err = cec_monitor_all_cnt_inc(adap); - else - cec_monitor_all_cnt_dec(adap); - } - } - - if (err) { - mutex_unlock(&adap->lock); - return err; - } - - if (fh->mode_follower == CEC_MODE_FOLLOWER) - adap->follower_cnt--; - if (mode_follower == CEC_MODE_FOLLOWER) - adap->follower_cnt++; - if (mode_follower == CEC_MODE_EXCL_FOLLOWER || - mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU) { - adap->passthrough = - mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU; - adap->cec_follower = fh; - } else if (adap->cec_follower == fh) { - adap->passthrough = false; - adap->cec_follower = NULL; - } - if (mode_initiator == CEC_MODE_EXCL_INITIATOR) - adap->cec_initiator = fh; - else if (adap->cec_initiator == fh) - adap->cec_initiator = NULL; - fh->mode_initiator = mode_initiator; - fh->mode_follower = mode_follower; - mutex_unlock(&adap->lock); - return 0; -} - -static long cec_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) -{ - struct cec_devnode *devnode = cec_devnode_data(filp); - struct cec_fh *fh = filp->private_data; - struct cec_adapter *adap = fh->adap; - bool block = !(filp->f_flags & O_NONBLOCK); - void __user *parg = (void __user *)arg; - - if (!devnode->registered) - return -ENODEV; - - switch (cmd) { - case CEC_ADAP_G_CAPS: - return cec_adap_g_caps(adap, parg); - - case CEC_ADAP_G_PHYS_ADDR: - return cec_adap_g_phys_addr(adap, parg); - - case CEC_ADAP_S_PHYS_ADDR: - return cec_adap_s_phys_addr(adap, fh, block, parg); - - case CEC_ADAP_G_LOG_ADDRS: - return cec_adap_g_log_addrs(adap, parg); - - case CEC_ADAP_S_LOG_ADDRS: - return cec_adap_s_log_addrs(adap, fh, block, parg); - - case CEC_TRANSMIT: - return cec_transmit(adap, fh, block, parg); - - case CEC_RECEIVE: - return cec_receive(adap, fh, block, parg); - - case CEC_DQEVENT: - return cec_dqevent(adap, fh, block, parg); - - case CEC_G_MODE: - return cec_g_mode(adap, fh, parg); - - case CEC_S_MODE: - return cec_s_mode(adap, fh, parg); - - default: - return -ENOTTY; - } -} - -static int cec_open(struct inode *inode, struct file *filp) -{ - struct cec_devnode *devnode = - container_of(inode->i_cdev, struct cec_devnode, cdev); - struct cec_adapter *adap = to_cec_adapter(devnode); - struct cec_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL); - /* - * Initial events that are automatically sent when the cec device is - * opened. - */ - struct cec_event ev_state = { - .event = CEC_EVENT_STATE_CHANGE, - .flags = CEC_EVENT_FL_INITIAL_STATE, - }; - int err; - - if (!fh) - return -ENOMEM; - - INIT_LIST_HEAD(&fh->msgs); - INIT_LIST_HEAD(&fh->xfer_list); - mutex_init(&fh->lock); - init_waitqueue_head(&fh->wait); - - fh->mode_initiator = CEC_MODE_INITIATOR; - fh->adap = adap; - - err = cec_get_device(devnode); - if (err) { - kfree(fh); - return err; - } - - filp->private_data = fh; - - mutex_lock(&devnode->lock); - /* Queue up initial state events */ - ev_state.state_change.phys_addr = adap->phys_addr; - ev_state.state_change.log_addr_mask = adap->log_addrs.log_addr_mask; - cec_queue_event_fh(fh, &ev_state, 0); - - list_add(&fh->list, &devnode->fhs); - mutex_unlock(&devnode->lock); - - return 0; -} - -/* Override for the release function */ -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; - - mutex_lock(&adap->lock); - if (adap->cec_initiator == fh) - adap->cec_initiator = NULL; - if (adap->cec_follower == fh) { - adap->cec_follower = NULL; - adap->passthrough = false; - } - if (fh->mode_follower == CEC_MODE_FOLLOWER) - adap->follower_cnt--; - if (fh->mode_follower == CEC_MODE_MONITOR_ALL) - cec_monitor_all_cnt_dec(adap); - mutex_unlock(&adap->lock); - - mutex_lock(&devnode->lock); - list_del(&fh->list); - mutex_unlock(&devnode->lock); - - /* Unhook pending transmits from this filehandle. */ - mutex_lock(&adap->lock); - while (!list_empty(&fh->xfer_list)) { - struct cec_data *data = - list_first_entry(&fh->xfer_list, struct cec_data, xfer_list); - - data->blocking = false; - data->fh = NULL; - list_del(&data->xfer_list); - } - mutex_unlock(&adap->lock); - while (!list_empty(&fh->msgs)) { - struct cec_msg_entry *entry = - list_first_entry(&fh->msgs, struct cec_msg_entry, list); - - list_del(&entry->list); - kfree(entry); - } - kfree(fh); - - cec_put_device(devnode); - filp->private_data = NULL; - return 0; -} - -const struct file_operations cec_devnode_fops = { - .owner = THIS_MODULE, - .open = cec_open, - .unlocked_ioctl = cec_ioctl, - .release = cec_release, - .poll = cec_poll, - .llseek = no_llseek, -}; diff --git a/drivers/staging/media/cec/cec-core.c b/drivers/staging/media/cec/cec-core.c deleted file mode 100644 index b0137e247dc9..000000000000 --- a/drivers/staging/media/cec/cec-core.c +++ /dev/null @@ -1,411 +0,0 @@ -/* - * cec-core.c - HDMI Consumer Electronics Control framework - Core - * - * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - * - * This program is free software; you may redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cec-priv.h" - -#define CEC_NUM_DEVICES 256 -#define CEC_NAME "cec" - -int cec_debug; -module_param_named(debug, cec_debug, int, 0644); -MODULE_PARM_DESC(debug, "debug level (0-2)"); - -static dev_t cec_dev_t; - -/* Active devices */ -static DEFINE_MUTEX(cec_devnode_lock); -static DECLARE_BITMAP(cec_devnode_nums, CEC_NUM_DEVICES); - -static struct dentry *top_cec_dir; - -/* dev to cec_devnode */ -#define to_cec_devnode(cd) container_of(cd, struct cec_devnode, dev) - -int cec_get_device(struct cec_devnode *devnode) -{ - /* - * Check if the cec device is available. This needs to be done with - * the devnode->lock held to prevent an open/unregister race: - * without the lock, the device could be unregistered and freed between - * the devnode->registered check and get_device() calls, leading to - * a crash. - */ - mutex_lock(&devnode->lock); - /* - * return ENXIO if the cec device has been removed - * already or if it is not registered anymore. - */ - if (!devnode->registered) { - mutex_unlock(&devnode->lock); - return -ENXIO; - } - /* and increase the device refcount */ - get_device(&devnode->dev); - mutex_unlock(&devnode->lock); - return 0; -} - -void cec_put_device(struct cec_devnode *devnode) -{ - put_device(&devnode->dev); -} - -/* Called when the last user of the cec device exits. */ -static void cec_devnode_release(struct device *cd) -{ - struct cec_devnode *devnode = to_cec_devnode(cd); - - mutex_lock(&cec_devnode_lock); - /* Mark device node number as free */ - clear_bit(devnode->minor, cec_devnode_nums); - mutex_unlock(&cec_devnode_lock); - - cec_delete_adapter(to_cec_adapter(devnode)); -} - -static struct bus_type cec_bus_type = { - .name = CEC_NAME, -}; - -/* - * Register a cec device node - * - * The registration code assigns minor numbers and registers the new device node - * with the kernel. An error is returned if no free minor number can be found, - * or if the registration of the device node fails. - * - * Zero is returned on success. - * - * Note that if the cec_devnode_register call fails, the release() callback of - * the cec_devnode structure is *not* called, so the caller is responsible for - * freeing any data. - */ -static int __must_check cec_devnode_register(struct cec_devnode *devnode, - struct module *owner) -{ - int minor; - int ret; - - /* Initialization */ - INIT_LIST_HEAD(&devnode->fhs); - mutex_init(&devnode->lock); - - /* Part 1: Find a free minor number */ - mutex_lock(&cec_devnode_lock); - minor = find_next_zero_bit(cec_devnode_nums, CEC_NUM_DEVICES, 0); - if (minor == CEC_NUM_DEVICES) { - mutex_unlock(&cec_devnode_lock); - pr_err("could not get a free minor\n"); - return -ENFILE; - } - - set_bit(minor, cec_devnode_nums); - mutex_unlock(&cec_devnode_lock); - - devnode->minor = minor; - devnode->dev.bus = &cec_bus_type; - devnode->dev.devt = MKDEV(MAJOR(cec_dev_t), minor); - devnode->dev.release = cec_devnode_release; - devnode->dev.parent = devnode->parent; - dev_set_name(&devnode->dev, "cec%d", devnode->minor); - device_initialize(&devnode->dev); - - /* Part 2: Initialize and register the character device */ - cdev_init(&devnode->cdev, &cec_devnode_fops); - devnode->cdev.kobj.parent = &devnode->dev.kobj; - devnode->cdev.owner = owner; - - ret = cdev_add(&devnode->cdev, devnode->dev.devt, 1); - if (ret < 0) { - pr_err("%s: cdev_add failed\n", __func__); - goto clr_bit; - } - - ret = device_add(&devnode->dev); - if (ret) - goto cdev_del; - - devnode->registered = true; - return 0; - -cdev_del: - cdev_del(&devnode->cdev); -clr_bit: - mutex_lock(&cec_devnode_lock); - clear_bit(devnode->minor, cec_devnode_nums); - mutex_unlock(&cec_devnode_lock); - return ret; -} - -/* - * Unregister a cec device node - * - * This unregisters the passed device. Future open calls will be met with - * errors. - * - * This function can safely be called if the device node has never been - * registered or has already been unregistered. - */ -static void cec_devnode_unregister(struct cec_devnode *devnode) -{ - struct cec_fh *fh; - - mutex_lock(&devnode->lock); - - /* Check if devnode was never registered or already unregistered */ - if (!devnode->registered || devnode->unregistered) { - mutex_unlock(&devnode->lock); - return; - } - - list_for_each_entry(fh, &devnode->fhs, list) - wake_up_interruptible(&fh->wait); - - devnode->registered = false; - devnode->unregistered = true; - mutex_unlock(&devnode->lock); - - device_del(&devnode->dev); - cdev_del(&devnode->cdev); - put_device(&devnode->dev); -} - -struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops, - void *priv, const char *name, u32 caps, - u8 available_las, struct device *parent) -{ - struct cec_adapter *adap; - int res; - - if (WARN_ON(!parent)) - return ERR_PTR(-EINVAL); - if (WARN_ON(!caps)) - return ERR_PTR(-EINVAL); - if (WARN_ON(!ops)) - return ERR_PTR(-EINVAL); - if (WARN_ON(!available_las || available_las > CEC_MAX_LOG_ADDRS)) - return ERR_PTR(-EINVAL); - adap = kzalloc(sizeof(*adap), GFP_KERNEL); - if (!adap) - return ERR_PTR(-ENOMEM); - adap->owner = parent->driver->owner; - adap->devnode.parent = parent; - strlcpy(adap->name, name, sizeof(adap->name)); - adap->phys_addr = CEC_PHYS_ADDR_INVALID; - adap->log_addrs.cec_version = CEC_OP_CEC_VERSION_2_0; - adap->log_addrs.vendor_id = CEC_VENDOR_ID_NONE; - adap->capabilities = caps; - adap->available_log_addrs = available_las; - adap->sequence = 0; - adap->ops = ops; - adap->priv = priv; - memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs)); - mutex_init(&adap->lock); - INIT_LIST_HEAD(&adap->transmit_queue); - INIT_LIST_HEAD(&adap->wait_queue); - init_waitqueue_head(&adap->kthread_waitq); - - adap->kthread = kthread_run(cec_thread_func, adap, "cec-%s", name); - if (IS_ERR(adap->kthread)) { - pr_err("cec-%s: kernel_thread() failed\n", name); - res = PTR_ERR(adap->kthread); - kfree(adap); - return ERR_PTR(res); - } - - if (!(caps & CEC_CAP_RC)) - return adap; - -#if IS_REACHABLE(CONFIG_RC_CORE) - /* Prepare the RC input device */ - adap->rc = rc_allocate_device(); - if (!adap->rc) { - pr_err("cec-%s: failed to allocate memory for rc_dev\n", - name); - kthread_stop(adap->kthread); - kfree(adap); - return ERR_PTR(-ENOMEM); - } - - snprintf(adap->input_name, sizeof(adap->input_name), - "RC for %s", name); - snprintf(adap->input_phys, sizeof(adap->input_phys), - "%s/input0", name); - - adap->rc->input_name = adap->input_name; - adap->rc->input_phys = adap->input_phys; - adap->rc->input_id.bustype = BUS_CEC; - adap->rc->input_id.vendor = 0; - adap->rc->input_id.product = 0; - adap->rc->input_id.version = 1; - adap->rc->dev.parent = parent; - adap->rc->driver_type = RC_DRIVER_SCANCODE; - adap->rc->driver_name = CEC_NAME; - adap->rc->allowed_protocols = RC_BIT_CEC; - adap->rc->priv = adap; - adap->rc->map_name = RC_MAP_CEC; - adap->rc->timeout = MS_TO_NS(100); -#else - adap->capabilities &= ~CEC_CAP_RC; -#endif - return adap; -} -EXPORT_SYMBOL_GPL(cec_allocate_adapter); - -int cec_register_adapter(struct cec_adapter *adap) -{ - int res; - - if (IS_ERR_OR_NULL(adap)) - return 0; - -#if IS_REACHABLE(CONFIG_RC_CORE) - if (adap->capabilities & CEC_CAP_RC) { - res = rc_register_device(adap->rc); - - if (res) { - pr_err("cec-%s: failed to prepare input device\n", - adap->name); - rc_free_device(adap->rc); - adap->rc = NULL; - return res; - } - } -#endif - - res = cec_devnode_register(&adap->devnode, adap->owner); - if (res) { -#if IS_REACHABLE(CONFIG_RC_CORE) - /* Note: rc_unregister also calls rc_free */ - rc_unregister_device(adap->rc); - adap->rc = NULL; -#endif - return res; - } - - dev_set_drvdata(&adap->devnode.dev, adap); -#ifdef CONFIG_MEDIA_CEC_DEBUG - if (!top_cec_dir) - return 0; - - adap->cec_dir = debugfs_create_dir(dev_name(&adap->devnode.dev), top_cec_dir); - if (IS_ERR_OR_NULL(adap->cec_dir)) { - pr_warn("cec-%s: Failed to create debugfs dir\n", adap->name); - return 0; - } - adap->status_file = debugfs_create_devm_seqfile(&adap->devnode.dev, - "status", adap->cec_dir, cec_adap_status); - if (IS_ERR_OR_NULL(adap->status_file)) { - pr_warn("cec-%s: Failed to create status file\n", adap->name); - debugfs_remove_recursive(adap->cec_dir); - adap->cec_dir = NULL; - } -#endif - return 0; -} -EXPORT_SYMBOL_GPL(cec_register_adapter); - -void cec_unregister_adapter(struct cec_adapter *adap) -{ - if (IS_ERR_OR_NULL(adap)) - return; - -#if IS_REACHABLE(CONFIG_RC_CORE) - /* Note: rc_unregister also calls rc_free */ - rc_unregister_device(adap->rc); - adap->rc = NULL; -#endif - debugfs_remove_recursive(adap->cec_dir); - cec_devnode_unregister(&adap->devnode); -} -EXPORT_SYMBOL_GPL(cec_unregister_adapter); - -void cec_delete_adapter(struct cec_adapter *adap) -{ - if (IS_ERR_OR_NULL(adap)) - return; - mutex_lock(&adap->lock); - __cec_s_phys_addr(adap, CEC_PHYS_ADDR_INVALID, false); - mutex_unlock(&adap->lock); - kthread_stop(adap->kthread); - if (adap->kthread_config) - kthread_stop(adap->kthread_config); -#if IS_REACHABLE(CONFIG_RC_CORE) - rc_free_device(adap->rc); -#endif - kfree(adap); -} -EXPORT_SYMBOL_GPL(cec_delete_adapter); - -/* - * Initialise cec for linux - */ -static int __init cec_devnode_init(void) -{ - int ret; - - pr_info("Linux cec interface: v0.10\n"); - ret = alloc_chrdev_region(&cec_dev_t, 0, CEC_NUM_DEVICES, - CEC_NAME); - if (ret < 0) { - pr_warn("cec: unable to allocate major\n"); - return ret; - } - -#ifdef CONFIG_MEDIA_CEC_DEBUG - top_cec_dir = debugfs_create_dir("cec", NULL); - if (IS_ERR_OR_NULL(top_cec_dir)) { - pr_warn("cec: Failed to create debugfs cec dir\n"); - top_cec_dir = NULL; - } -#endif - - ret = bus_register(&cec_bus_type); - if (ret < 0) { - unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES); - pr_warn("cec: bus_register failed\n"); - return -EIO; - } - - return 0; -} - -static void __exit cec_devnode_exit(void) -{ - debugfs_remove_recursive(top_cec_dir); - bus_unregister(&cec_bus_type); - unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES); -} - -subsys_initcall(cec_devnode_init); -module_exit(cec_devnode_exit) - -MODULE_AUTHOR("Hans Verkuil "); -MODULE_DESCRIPTION("Device node registration for cec drivers"); -MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/cec/cec-priv.h b/drivers/staging/media/cec/cec-priv.h deleted file mode 100644 index 70767a7900f2..000000000000 --- a/drivers/staging/media/cec/cec-priv.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * cec-priv.h - HDMI Consumer Electronics Control internal header - * - * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - * - * This program is free software; you may redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _CEC_PRIV_H -#define _CEC_PRIV_H - -#include -#include - -#define dprintk(lvl, fmt, arg...) \ - do { \ - if (lvl <= cec_debug) \ - pr_info("cec-%s: " fmt, adap->name, ## arg); \ - } while (0) - -/* devnode to cec_adapter */ -#define to_cec_adapter(node) container_of(node, struct cec_adapter, devnode) - -/* cec-core.c */ -extern int cec_debug; -int cec_get_device(struct cec_devnode *devnode); -void cec_put_device(struct cec_devnode *devnode); - -/* cec-adap.c */ -int cec_monitor_all_cnt_inc(struct cec_adapter *adap); -void cec_monitor_all_cnt_dec(struct cec_adapter *adap); -int cec_adap_status(struct seq_file *file, void *priv); -int cec_thread_func(void *_adap); -void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block); -int __cec_s_log_addrs(struct cec_adapter *adap, - struct cec_log_addrs *log_addrs, bool block); -int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, - struct cec_fh *fh, bool block); -void cec_queue_event_fh(struct cec_fh *fh, - const struct cec_event *new_ev, u64 ts); - -/* cec-api.c */ -extern const struct file_operations cec_devnode_fops; - -#endif diff --git a/drivers/staging/media/pulse8-cec/Kconfig b/drivers/staging/media/pulse8-cec/Kconfig index c6aa2d1c9df0..6ffc407de62f 100644 --- a/drivers/staging/media/pulse8-cec/Kconfig +++ b/drivers/staging/media/pulse8-cec/Kconfig @@ -1,6 +1,6 @@ config USB_PULSE8_CEC tristate "Pulse Eight HDMI CEC" - depends on USB_ACM && MEDIA_CEC + depends on USB_ACM && MEDIA_CEC_SUPPORT select SERIO select SERIO_SERPORT ---help--- diff --git a/drivers/staging/media/s5p-cec/Kconfig b/drivers/staging/media/s5p-cec/Kconfig index 0315fd7ad0f1..ddfd955da0d4 100644 --- a/drivers/staging/media/s5p-cec/Kconfig +++ b/drivers/staging/media/s5p-cec/Kconfig @@ -1,6 +1,6 @@ config VIDEO_SAMSUNG_S5P_CEC tristate "Samsung S5P CEC driver" - depends on VIDEO_DEV && MEDIA_CEC && (PLAT_S5P || ARCH_EXYNOS || COMPILE_TEST) + depends on VIDEO_DEV && MEDIA_CEC_SUPPORT && (PLAT_S5P || ARCH_EXYNOS || COMPILE_TEST) ---help--- This is a driver for Samsung S5P HDMI CEC interface. It uses the generic CEC framework interface. diff --git a/drivers/staging/media/st-cec/Kconfig b/drivers/staging/media/st-cec/Kconfig index 784d2c600aca..c04283db58d6 100644 --- a/drivers/staging/media/st-cec/Kconfig +++ b/drivers/staging/media/st-cec/Kconfig @@ -1,6 +1,6 @@ config VIDEO_STI_HDMI_CEC tristate "STMicroelectronics STiH4xx HDMI CEC driver" - depends on VIDEO_DEV && MEDIA_CEC && (ARCH_STI || COMPILE_TEST) + depends on VIDEO_DEV && MEDIA_CEC_SUPPORT && (ARCH_STI || COMPILE_TEST) ---help--- This is a driver for STIH4xx HDMI CEC interface. It uses the generic CEC framework interface. diff --git a/include/linux/cec-funcs.h b/include/linux/cec-funcs.h deleted file mode 100644 index 138bbf721e70..000000000000 --- a/include/linux/cec-funcs.h +++ /dev/null @@ -1,1971 +0,0 @@ -/* - * cec - HDMI Consumer Electronics Control message functions - * - * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - * - * This program is free software; you may redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * Alternatively you can redistribute this file under the terms of the - * BSD license as stated below: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * 3. The names of its contributors may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* - * Note: this framework is still in staging and it is likely the API - * will change before it goes out of staging. - * - * Once it is moved out of staging this header will move to uapi. - */ -#ifndef _CEC_UAPI_FUNCS_H -#define _CEC_UAPI_FUNCS_H - -#include - -/* One Touch Play Feature */ -static inline void cec_msg_active_source(struct cec_msg *msg, __u16 phys_addr) -{ - msg->len = 4; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_ACTIVE_SOURCE; - msg->msg[2] = phys_addr >> 8; - msg->msg[3] = phys_addr & 0xff; -} - -static inline void cec_ops_active_source(const struct cec_msg *msg, - __u16 *phys_addr) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; -} - -static inline void cec_msg_image_view_on(struct cec_msg *msg) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_IMAGE_VIEW_ON; -} - -static inline void cec_msg_text_view_on(struct cec_msg *msg) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_TEXT_VIEW_ON; -} - - -/* Routing Control Feature */ -static inline void cec_msg_inactive_source(struct cec_msg *msg, - __u16 phys_addr) -{ - msg->len = 4; - msg->msg[1] = CEC_MSG_INACTIVE_SOURCE; - msg->msg[2] = phys_addr >> 8; - msg->msg[3] = phys_addr & 0xff; -} - -static inline void cec_ops_inactive_source(const struct cec_msg *msg, - __u16 *phys_addr) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; -} - -static inline void cec_msg_request_active_source(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_REQUEST_ACTIVE_SOURCE; - msg->reply = reply ? CEC_MSG_ACTIVE_SOURCE : 0; -} - -static inline void cec_msg_routing_information(struct cec_msg *msg, - __u16 phys_addr) -{ - msg->len = 4; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_ROUTING_INFORMATION; - msg->msg[2] = phys_addr >> 8; - msg->msg[3] = phys_addr & 0xff; -} - -static inline void cec_ops_routing_information(const struct cec_msg *msg, - __u16 *phys_addr) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; -} - -static inline void cec_msg_routing_change(struct cec_msg *msg, - bool reply, - __u16 orig_phys_addr, - __u16 new_phys_addr) -{ - msg->len = 6; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_ROUTING_CHANGE; - msg->msg[2] = orig_phys_addr >> 8; - msg->msg[3] = orig_phys_addr & 0xff; - msg->msg[4] = new_phys_addr >> 8; - msg->msg[5] = new_phys_addr & 0xff; - msg->reply = reply ? CEC_MSG_ROUTING_INFORMATION : 0; -} - -static inline void cec_ops_routing_change(const struct cec_msg *msg, - __u16 *orig_phys_addr, - __u16 *new_phys_addr) -{ - *orig_phys_addr = (msg->msg[2] << 8) | msg->msg[3]; - *new_phys_addr = (msg->msg[4] << 8) | msg->msg[5]; -} - -static inline void cec_msg_set_stream_path(struct cec_msg *msg, __u16 phys_addr) -{ - msg->len = 4; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_SET_STREAM_PATH; - msg->msg[2] = phys_addr >> 8; - msg->msg[3] = phys_addr & 0xff; -} - -static inline void cec_ops_set_stream_path(const struct cec_msg *msg, - __u16 *phys_addr) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; -} - - -/* Standby Feature */ -static inline void cec_msg_standby(struct cec_msg *msg) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_STANDBY; -} - - -/* One Touch Record Feature */ -static inline void cec_msg_record_off(struct cec_msg *msg, bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_RECORD_OFF; - msg->reply = reply ? CEC_MSG_RECORD_STATUS : 0; -} - -struct cec_op_arib_data { - __u16 transport_id; - __u16 service_id; - __u16 orig_network_id; -}; - -struct cec_op_atsc_data { - __u16 transport_id; - __u16 program_number; -}; - -struct cec_op_dvb_data { - __u16 transport_id; - __u16 service_id; - __u16 orig_network_id; -}; - -struct cec_op_channel_data { - __u8 channel_number_fmt; - __u16 major; - __u16 minor; -}; - -struct cec_op_digital_service_id { - __u8 service_id_method; - __u8 dig_bcast_system; - union { - struct cec_op_arib_data arib; - struct cec_op_atsc_data atsc; - struct cec_op_dvb_data dvb; - struct cec_op_channel_data channel; - }; -}; - -struct cec_op_record_src { - __u8 type; - union { - struct cec_op_digital_service_id digital; - struct { - __u8 ana_bcast_type; - __u16 ana_freq; - __u8 bcast_system; - } analog; - struct { - __u8 plug; - } ext_plug; - struct { - __u16 phys_addr; - } ext_phys_addr; - }; -}; - -static inline void cec_set_digital_service_id(__u8 *msg, - const struct cec_op_digital_service_id *digital) -{ - *msg++ = (digital->service_id_method << 7) | digital->dig_bcast_system; - if (digital->service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) { - *msg++ = (digital->channel.channel_number_fmt << 2) | - (digital->channel.major >> 8); - *msg++ = digital->channel.major & 0xff; - *msg++ = digital->channel.minor >> 8; - *msg++ = digital->channel.minor & 0xff; - *msg++ = 0; - *msg++ = 0; - return; - } - switch (digital->dig_bcast_system) { - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: - *msg++ = digital->atsc.transport_id >> 8; - *msg++ = digital->atsc.transport_id & 0xff; - *msg++ = digital->atsc.program_number >> 8; - *msg++ = digital->atsc.program_number & 0xff; - *msg++ = 0; - *msg++ = 0; - break; - default: - *msg++ = digital->dvb.transport_id >> 8; - *msg++ = digital->dvb.transport_id & 0xff; - *msg++ = digital->dvb.service_id >> 8; - *msg++ = digital->dvb.service_id & 0xff; - *msg++ = digital->dvb.orig_network_id >> 8; - *msg++ = digital->dvb.orig_network_id & 0xff; - break; - } -} - -static inline void cec_get_digital_service_id(const __u8 *msg, - struct cec_op_digital_service_id *digital) -{ - digital->service_id_method = msg[0] >> 7; - digital->dig_bcast_system = msg[0] & 0x7f; - if (digital->service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) { - digital->channel.channel_number_fmt = msg[1] >> 2; - digital->channel.major = ((msg[1] & 3) << 6) | msg[2]; - digital->channel.minor = (msg[3] << 8) | msg[4]; - return; - } - digital->dvb.transport_id = (msg[1] << 8) | msg[2]; - digital->dvb.service_id = (msg[3] << 8) | msg[4]; - digital->dvb.orig_network_id = (msg[5] << 8) | msg[6]; -} - -static inline void cec_msg_record_on_own(struct cec_msg *msg) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_RECORD_ON; - msg->msg[2] = CEC_OP_RECORD_SRC_OWN; -} - -static inline void cec_msg_record_on_digital(struct cec_msg *msg, - const struct cec_op_digital_service_id *digital) -{ - msg->len = 10; - msg->msg[1] = CEC_MSG_RECORD_ON; - msg->msg[2] = CEC_OP_RECORD_SRC_DIGITAL; - cec_set_digital_service_id(msg->msg + 3, digital); -} - -static inline void cec_msg_record_on_analog(struct cec_msg *msg, - __u8 ana_bcast_type, - __u16 ana_freq, - __u8 bcast_system) -{ - msg->len = 7; - msg->msg[1] = CEC_MSG_RECORD_ON; - msg->msg[2] = CEC_OP_RECORD_SRC_ANALOG; - msg->msg[3] = ana_bcast_type; - msg->msg[4] = ana_freq >> 8; - msg->msg[5] = ana_freq & 0xff; - msg->msg[6] = bcast_system; -} - -static inline void cec_msg_record_on_plug(struct cec_msg *msg, - __u8 plug) -{ - msg->len = 4; - msg->msg[1] = CEC_MSG_RECORD_ON; - msg->msg[2] = CEC_OP_RECORD_SRC_EXT_PLUG; - msg->msg[3] = plug; -} - -static inline void cec_msg_record_on_phys_addr(struct cec_msg *msg, - __u16 phys_addr) -{ - msg->len = 5; - msg->msg[1] = CEC_MSG_RECORD_ON; - msg->msg[2] = CEC_OP_RECORD_SRC_EXT_PHYS_ADDR; - msg->msg[3] = phys_addr >> 8; - msg->msg[4] = phys_addr & 0xff; -} - -static inline void cec_msg_record_on(struct cec_msg *msg, - bool reply, - const struct cec_op_record_src *rec_src) -{ - switch (rec_src->type) { - case CEC_OP_RECORD_SRC_OWN: - cec_msg_record_on_own(msg); - break; - case CEC_OP_RECORD_SRC_DIGITAL: - cec_msg_record_on_digital(msg, &rec_src->digital); - break; - case CEC_OP_RECORD_SRC_ANALOG: - cec_msg_record_on_analog(msg, - rec_src->analog.ana_bcast_type, - rec_src->analog.ana_freq, - rec_src->analog.bcast_system); - break; - case CEC_OP_RECORD_SRC_EXT_PLUG: - cec_msg_record_on_plug(msg, rec_src->ext_plug.plug); - break; - case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR: - cec_msg_record_on_phys_addr(msg, - rec_src->ext_phys_addr.phys_addr); - break; - } - msg->reply = reply ? CEC_MSG_RECORD_STATUS : 0; -} - -static inline void cec_ops_record_on(const struct cec_msg *msg, - struct cec_op_record_src *rec_src) -{ - rec_src->type = msg->msg[2]; - switch (rec_src->type) { - case CEC_OP_RECORD_SRC_OWN: - break; - case CEC_OP_RECORD_SRC_DIGITAL: - cec_get_digital_service_id(msg->msg + 3, &rec_src->digital); - break; - case CEC_OP_RECORD_SRC_ANALOG: - rec_src->analog.ana_bcast_type = msg->msg[3]; - rec_src->analog.ana_freq = - (msg->msg[4] << 8) | msg->msg[5]; - rec_src->analog.bcast_system = msg->msg[6]; - break; - case CEC_OP_RECORD_SRC_EXT_PLUG: - rec_src->ext_plug.plug = msg->msg[3]; - break; - case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR: - rec_src->ext_phys_addr.phys_addr = - (msg->msg[3] << 8) | msg->msg[4]; - break; - } -} - -static inline void cec_msg_record_status(struct cec_msg *msg, __u8 rec_status) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_RECORD_STATUS; - msg->msg[2] = rec_status; -} - -static inline void cec_ops_record_status(const struct cec_msg *msg, - __u8 *rec_status) -{ - *rec_status = msg->msg[2]; -} - -static inline void cec_msg_record_tv_screen(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_RECORD_TV_SCREEN; - msg->reply = reply ? CEC_MSG_RECORD_ON : 0; -} - - -/* Timer Programming Feature */ -static inline void cec_msg_timer_status(struct cec_msg *msg, - __u8 timer_overlap_warning, - __u8 media_info, - __u8 prog_info, - __u8 prog_error, - __u8 duration_hr, - __u8 duration_min) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_TIMER_STATUS; - msg->msg[2] = (timer_overlap_warning << 7) | - (media_info << 5) | - (prog_info ? 0x10 : 0) | - (prog_info ? prog_info : prog_error); - if (prog_info == CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE || - prog_info == CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE || - prog_error == CEC_OP_PROG_ERROR_DUPLICATE) { - msg->len += 2; - msg->msg[3] = ((duration_hr / 10) << 4) | (duration_hr % 10); - msg->msg[4] = ((duration_min / 10) << 4) | (duration_min % 10); - } -} - -static inline void cec_ops_timer_status(const struct cec_msg *msg, - __u8 *timer_overlap_warning, - __u8 *media_info, - __u8 *prog_info, - __u8 *prog_error, - __u8 *duration_hr, - __u8 *duration_min) -{ - *timer_overlap_warning = msg->msg[2] >> 7; - *media_info = (msg->msg[2] >> 5) & 3; - if (msg->msg[2] & 0x10) { - *prog_info = msg->msg[2] & 0xf; - *prog_error = 0; - } else { - *prog_info = 0; - *prog_error = msg->msg[2] & 0xf; - } - if (*prog_info == CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE || - *prog_info == CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE || - *prog_error == CEC_OP_PROG_ERROR_DUPLICATE) { - *duration_hr = (msg->msg[3] >> 4) * 10 + (msg->msg[3] & 0xf); - *duration_min = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); - } else { - *duration_hr = *duration_min = 0; - } -} - -static inline void cec_msg_timer_cleared_status(struct cec_msg *msg, - __u8 timer_cleared_status) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_TIMER_CLEARED_STATUS; - msg->msg[2] = timer_cleared_status; -} - -static inline void cec_ops_timer_cleared_status(const struct cec_msg *msg, - __u8 *timer_cleared_status) -{ - *timer_cleared_status = msg->msg[2]; -} - -static inline void cec_msg_clear_analogue_timer(struct cec_msg *msg, - bool reply, - __u8 day, - __u8 month, - __u8 start_hr, - __u8 start_min, - __u8 duration_hr, - __u8 duration_min, - __u8 recording_seq, - __u8 ana_bcast_type, - __u16 ana_freq, - __u8 bcast_system) -{ - msg->len = 13; - msg->msg[1] = CEC_MSG_CLEAR_ANALOGUE_TIMER; - msg->msg[2] = day; - msg->msg[3] = month; - /* Hours and minutes are in BCD format */ - msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); - msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); - msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); - msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); - msg->msg[8] = recording_seq; - msg->msg[9] = ana_bcast_type; - msg->msg[10] = ana_freq >> 8; - msg->msg[11] = ana_freq & 0xff; - msg->msg[12] = bcast_system; - msg->reply = reply ? CEC_MSG_TIMER_CLEARED_STATUS : 0; -} - -static inline void cec_ops_clear_analogue_timer(const struct cec_msg *msg, - __u8 *day, - __u8 *month, - __u8 *start_hr, - __u8 *start_min, - __u8 *duration_hr, - __u8 *duration_min, - __u8 *recording_seq, - __u8 *ana_bcast_type, - __u16 *ana_freq, - __u8 *bcast_system) -{ - *day = msg->msg[2]; - *month = msg->msg[3]; - /* Hours and minutes are in BCD format */ - *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); - *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); - *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); - *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); - *recording_seq = msg->msg[8]; - *ana_bcast_type = msg->msg[9]; - *ana_freq = (msg->msg[10] << 8) | msg->msg[11]; - *bcast_system = msg->msg[12]; -} - -static inline void cec_msg_clear_digital_timer(struct cec_msg *msg, - bool reply, - __u8 day, - __u8 month, - __u8 start_hr, - __u8 start_min, - __u8 duration_hr, - __u8 duration_min, - __u8 recording_seq, - const struct cec_op_digital_service_id *digital) -{ - msg->len = 16; - msg->reply = reply ? CEC_MSG_TIMER_CLEARED_STATUS : 0; - msg->msg[1] = CEC_MSG_CLEAR_DIGITAL_TIMER; - msg->msg[2] = day; - msg->msg[3] = month; - /* Hours and minutes are in BCD format */ - msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); - msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); - msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); - msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); - msg->msg[8] = recording_seq; - cec_set_digital_service_id(msg->msg + 9, digital); -} - -static inline void cec_ops_clear_digital_timer(const struct cec_msg *msg, - __u8 *day, - __u8 *month, - __u8 *start_hr, - __u8 *start_min, - __u8 *duration_hr, - __u8 *duration_min, - __u8 *recording_seq, - struct cec_op_digital_service_id *digital) -{ - *day = msg->msg[2]; - *month = msg->msg[3]; - /* Hours and minutes are in BCD format */ - *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); - *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); - *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); - *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); - *recording_seq = msg->msg[8]; - cec_get_digital_service_id(msg->msg + 9, digital); -} - -static inline void cec_msg_clear_ext_timer(struct cec_msg *msg, - bool reply, - __u8 day, - __u8 month, - __u8 start_hr, - __u8 start_min, - __u8 duration_hr, - __u8 duration_min, - __u8 recording_seq, - __u8 ext_src_spec, - __u8 plug, - __u16 phys_addr) -{ - msg->len = 13; - msg->msg[1] = CEC_MSG_CLEAR_EXT_TIMER; - msg->msg[2] = day; - msg->msg[3] = month; - /* Hours and minutes are in BCD format */ - msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); - msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); - msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); - msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); - msg->msg[8] = recording_seq; - msg->msg[9] = ext_src_spec; - msg->msg[10] = plug; - msg->msg[11] = phys_addr >> 8; - msg->msg[12] = phys_addr & 0xff; - msg->reply = reply ? CEC_MSG_TIMER_CLEARED_STATUS : 0; -} - -static inline void cec_ops_clear_ext_timer(const struct cec_msg *msg, - __u8 *day, - __u8 *month, - __u8 *start_hr, - __u8 *start_min, - __u8 *duration_hr, - __u8 *duration_min, - __u8 *recording_seq, - __u8 *ext_src_spec, - __u8 *plug, - __u16 *phys_addr) -{ - *day = msg->msg[2]; - *month = msg->msg[3]; - /* Hours and minutes are in BCD format */ - *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); - *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); - *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); - *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); - *recording_seq = msg->msg[8]; - *ext_src_spec = msg->msg[9]; - *plug = msg->msg[10]; - *phys_addr = (msg->msg[11] << 8) | msg->msg[12]; -} - -static inline void cec_msg_set_analogue_timer(struct cec_msg *msg, - bool reply, - __u8 day, - __u8 month, - __u8 start_hr, - __u8 start_min, - __u8 duration_hr, - __u8 duration_min, - __u8 recording_seq, - __u8 ana_bcast_type, - __u16 ana_freq, - __u8 bcast_system) -{ - msg->len = 13; - msg->msg[1] = CEC_MSG_SET_ANALOGUE_TIMER; - msg->msg[2] = day; - msg->msg[3] = month; - /* Hours and minutes are in BCD format */ - msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); - msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); - msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); - msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); - msg->msg[8] = recording_seq; - msg->msg[9] = ana_bcast_type; - msg->msg[10] = ana_freq >> 8; - msg->msg[11] = ana_freq & 0xff; - msg->msg[12] = bcast_system; - msg->reply = reply ? CEC_MSG_TIMER_STATUS : 0; -} - -static inline void cec_ops_set_analogue_timer(const struct cec_msg *msg, - __u8 *day, - __u8 *month, - __u8 *start_hr, - __u8 *start_min, - __u8 *duration_hr, - __u8 *duration_min, - __u8 *recording_seq, - __u8 *ana_bcast_type, - __u16 *ana_freq, - __u8 *bcast_system) -{ - *day = msg->msg[2]; - *month = msg->msg[3]; - /* Hours and minutes are in BCD format */ - *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); - *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); - *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); - *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); - *recording_seq = msg->msg[8]; - *ana_bcast_type = msg->msg[9]; - *ana_freq = (msg->msg[10] << 8) | msg->msg[11]; - *bcast_system = msg->msg[12]; -} - -static inline void cec_msg_set_digital_timer(struct cec_msg *msg, - bool reply, - __u8 day, - __u8 month, - __u8 start_hr, - __u8 start_min, - __u8 duration_hr, - __u8 duration_min, - __u8 recording_seq, - const struct cec_op_digital_service_id *digital) -{ - msg->len = 16; - msg->reply = reply ? CEC_MSG_TIMER_STATUS : 0; - msg->msg[1] = CEC_MSG_SET_DIGITAL_TIMER; - msg->msg[2] = day; - msg->msg[3] = month; - /* Hours and minutes are in BCD format */ - msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); - msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); - msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); - msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); - msg->msg[8] = recording_seq; - cec_set_digital_service_id(msg->msg + 9, digital); -} - -static inline void cec_ops_set_digital_timer(const struct cec_msg *msg, - __u8 *day, - __u8 *month, - __u8 *start_hr, - __u8 *start_min, - __u8 *duration_hr, - __u8 *duration_min, - __u8 *recording_seq, - struct cec_op_digital_service_id *digital) -{ - *day = msg->msg[2]; - *month = msg->msg[3]; - /* Hours and minutes are in BCD format */ - *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); - *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); - *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); - *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); - *recording_seq = msg->msg[8]; - cec_get_digital_service_id(msg->msg + 9, digital); -} - -static inline void cec_msg_set_ext_timer(struct cec_msg *msg, - bool reply, - __u8 day, - __u8 month, - __u8 start_hr, - __u8 start_min, - __u8 duration_hr, - __u8 duration_min, - __u8 recording_seq, - __u8 ext_src_spec, - __u8 plug, - __u16 phys_addr) -{ - msg->len = 13; - msg->msg[1] = CEC_MSG_SET_EXT_TIMER; - msg->msg[2] = day; - msg->msg[3] = month; - /* Hours and minutes are in BCD format */ - msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); - msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); - msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); - msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); - msg->msg[8] = recording_seq; - msg->msg[9] = ext_src_spec; - msg->msg[10] = plug; - msg->msg[11] = phys_addr >> 8; - msg->msg[12] = phys_addr & 0xff; - msg->reply = reply ? CEC_MSG_TIMER_STATUS : 0; -} - -static inline void cec_ops_set_ext_timer(const struct cec_msg *msg, - __u8 *day, - __u8 *month, - __u8 *start_hr, - __u8 *start_min, - __u8 *duration_hr, - __u8 *duration_min, - __u8 *recording_seq, - __u8 *ext_src_spec, - __u8 *plug, - __u16 *phys_addr) -{ - *day = msg->msg[2]; - *month = msg->msg[3]; - /* Hours and minutes are in BCD format */ - *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); - *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); - *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); - *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); - *recording_seq = msg->msg[8]; - *ext_src_spec = msg->msg[9]; - *plug = msg->msg[10]; - *phys_addr = (msg->msg[11] << 8) | msg->msg[12]; -} - -static inline void cec_msg_set_timer_program_title(struct cec_msg *msg, - const char *prog_title) -{ - unsigned int len = strlen(prog_title); - - if (len > 14) - len = 14; - msg->len = 2 + len; - msg->msg[1] = CEC_MSG_SET_TIMER_PROGRAM_TITLE; - memcpy(msg->msg + 2, prog_title, len); -} - -static inline void cec_ops_set_timer_program_title(const struct cec_msg *msg, - char *prog_title) -{ - unsigned int len = msg->len > 2 ? msg->len - 2 : 0; - - if (len > 14) - len = 14; - memcpy(prog_title, msg->msg + 2, len); - prog_title[len] = '\0'; -} - -/* System Information Feature */ -static inline void cec_msg_cec_version(struct cec_msg *msg, __u8 cec_version) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_CEC_VERSION; - msg->msg[2] = cec_version; -} - -static inline void cec_ops_cec_version(const struct cec_msg *msg, - __u8 *cec_version) -{ - *cec_version = msg->msg[2]; -} - -static inline void cec_msg_get_cec_version(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_GET_CEC_VERSION; - msg->reply = reply ? CEC_MSG_CEC_VERSION : 0; -} - -static inline void cec_msg_report_physical_addr(struct cec_msg *msg, - __u16 phys_addr, __u8 prim_devtype) -{ - msg->len = 5; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_REPORT_PHYSICAL_ADDR; - msg->msg[2] = phys_addr >> 8; - msg->msg[3] = phys_addr & 0xff; - msg->msg[4] = prim_devtype; -} - -static inline void cec_ops_report_physical_addr(const struct cec_msg *msg, - __u16 *phys_addr, __u8 *prim_devtype) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; - *prim_devtype = msg->msg[4]; -} - -static inline void cec_msg_give_physical_addr(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_GIVE_PHYSICAL_ADDR; - msg->reply = reply ? CEC_MSG_REPORT_PHYSICAL_ADDR : 0; -} - -static inline void cec_msg_set_menu_language(struct cec_msg *msg, - const char *language) -{ - msg->len = 5; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_SET_MENU_LANGUAGE; - memcpy(msg->msg + 2, language, 3); -} - -static inline void cec_ops_set_menu_language(const struct cec_msg *msg, - char *language) -{ - memcpy(language, msg->msg + 2, 3); - language[3] = '\0'; -} - -static inline void cec_msg_get_menu_language(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_GET_MENU_LANGUAGE; - msg->reply = reply ? CEC_MSG_SET_MENU_LANGUAGE : 0; -} - -/* - * Assumes a single RC Profile byte and a single Device Features byte, - * i.e. no extended features are supported by this helper function. - * - * As of CEC 2.0 no extended features are defined, should those be added - * in the future, then this function needs to be adapted or a new function - * should be added. - */ -static inline void cec_msg_report_features(struct cec_msg *msg, - __u8 cec_version, __u8 all_device_types, - __u8 rc_profile, __u8 dev_features) -{ - msg->len = 6; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_REPORT_FEATURES; - msg->msg[2] = cec_version; - msg->msg[3] = all_device_types; - msg->msg[4] = rc_profile; - msg->msg[5] = dev_features; -} - -static inline void cec_ops_report_features(const struct cec_msg *msg, - __u8 *cec_version, __u8 *all_device_types, - const __u8 **rc_profile, const __u8 **dev_features) -{ - const __u8 *p = &msg->msg[4]; - - *cec_version = msg->msg[2]; - *all_device_types = msg->msg[3]; - *rc_profile = p; - while (p < &msg->msg[14] && (*p & CEC_OP_FEAT_EXT)) - p++; - if (!(*p & CEC_OP_FEAT_EXT)) { - *dev_features = p + 1; - while (p < &msg->msg[15] && (*p & CEC_OP_FEAT_EXT)) - p++; - } - if (*p & CEC_OP_FEAT_EXT) - *rc_profile = *dev_features = NULL; -} - -static inline void cec_msg_give_features(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_GIVE_FEATURES; - msg->reply = reply ? CEC_MSG_REPORT_FEATURES : 0; -} - -/* Deck Control Feature */ -static inline void cec_msg_deck_control(struct cec_msg *msg, - __u8 deck_control_mode) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_DECK_CONTROL; - msg->msg[2] = deck_control_mode; -} - -static inline void cec_ops_deck_control(const struct cec_msg *msg, - __u8 *deck_control_mode) -{ - *deck_control_mode = msg->msg[2]; -} - -static inline void cec_msg_deck_status(struct cec_msg *msg, - __u8 deck_info) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_DECK_STATUS; - msg->msg[2] = deck_info; -} - -static inline void cec_ops_deck_status(const struct cec_msg *msg, - __u8 *deck_info) -{ - *deck_info = msg->msg[2]; -} - -static inline void cec_msg_give_deck_status(struct cec_msg *msg, - bool reply, - __u8 status_req) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_GIVE_DECK_STATUS; - msg->msg[2] = status_req; - msg->reply = reply ? CEC_MSG_DECK_STATUS : 0; -} - -static inline void cec_ops_give_deck_status(const struct cec_msg *msg, - __u8 *status_req) -{ - *status_req = msg->msg[2]; -} - -static inline void cec_msg_play(struct cec_msg *msg, - __u8 play_mode) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_PLAY; - msg->msg[2] = play_mode; -} - -static inline void cec_ops_play(const struct cec_msg *msg, - __u8 *play_mode) -{ - *play_mode = msg->msg[2]; -} - - -/* Tuner Control Feature */ -struct cec_op_tuner_device_info { - __u8 rec_flag; - __u8 tuner_display_info; - bool is_analog; - union { - struct cec_op_digital_service_id digital; - struct { - __u8 ana_bcast_type; - __u16 ana_freq; - __u8 bcast_system; - } analog; - }; -}; - -static inline void cec_msg_tuner_device_status_analog(struct cec_msg *msg, - __u8 rec_flag, - __u8 tuner_display_info, - __u8 ana_bcast_type, - __u16 ana_freq, - __u8 bcast_system) -{ - msg->len = 7; - msg->msg[1] = CEC_MSG_TUNER_DEVICE_STATUS; - msg->msg[2] = (rec_flag << 7) | tuner_display_info; - msg->msg[3] = ana_bcast_type; - msg->msg[4] = ana_freq >> 8; - msg->msg[5] = ana_freq & 0xff; - msg->msg[6] = bcast_system; -} - -static inline void cec_msg_tuner_device_status_digital(struct cec_msg *msg, - __u8 rec_flag, __u8 tuner_display_info, - const struct cec_op_digital_service_id *digital) -{ - msg->len = 10; - msg->msg[1] = CEC_MSG_TUNER_DEVICE_STATUS; - msg->msg[2] = (rec_flag << 7) | tuner_display_info; - cec_set_digital_service_id(msg->msg + 3, digital); -} - -static inline void cec_msg_tuner_device_status(struct cec_msg *msg, - const struct cec_op_tuner_device_info *tuner_dev_info) -{ - if (tuner_dev_info->is_analog) - cec_msg_tuner_device_status_analog(msg, - tuner_dev_info->rec_flag, - tuner_dev_info->tuner_display_info, - tuner_dev_info->analog.ana_bcast_type, - tuner_dev_info->analog.ana_freq, - tuner_dev_info->analog.bcast_system); - else - cec_msg_tuner_device_status_digital(msg, - tuner_dev_info->rec_flag, - tuner_dev_info->tuner_display_info, - &tuner_dev_info->digital); -} - -static inline void cec_ops_tuner_device_status(const struct cec_msg *msg, - struct cec_op_tuner_device_info *tuner_dev_info) -{ - tuner_dev_info->is_analog = msg->len < 10; - tuner_dev_info->rec_flag = msg->msg[2] >> 7; - tuner_dev_info->tuner_display_info = msg->msg[2] & 0x7f; - if (tuner_dev_info->is_analog) { - tuner_dev_info->analog.ana_bcast_type = msg->msg[3]; - tuner_dev_info->analog.ana_freq = (msg->msg[4] << 8) | msg->msg[5]; - tuner_dev_info->analog.bcast_system = msg->msg[6]; - return; - } - cec_get_digital_service_id(msg->msg + 3, &tuner_dev_info->digital); -} - -static inline void cec_msg_give_tuner_device_status(struct cec_msg *msg, - bool reply, - __u8 status_req) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_GIVE_TUNER_DEVICE_STATUS; - msg->msg[2] = status_req; - msg->reply = reply ? CEC_MSG_TUNER_DEVICE_STATUS : 0; -} - -static inline void cec_ops_give_tuner_device_status(const struct cec_msg *msg, - __u8 *status_req) -{ - *status_req = msg->msg[2]; -} - -static inline void cec_msg_select_analogue_service(struct cec_msg *msg, - __u8 ana_bcast_type, - __u16 ana_freq, - __u8 bcast_system) -{ - msg->len = 6; - msg->msg[1] = CEC_MSG_SELECT_ANALOGUE_SERVICE; - msg->msg[2] = ana_bcast_type; - msg->msg[3] = ana_freq >> 8; - msg->msg[4] = ana_freq & 0xff; - msg->msg[5] = bcast_system; -} - -static inline void cec_ops_select_analogue_service(const struct cec_msg *msg, - __u8 *ana_bcast_type, - __u16 *ana_freq, - __u8 *bcast_system) -{ - *ana_bcast_type = msg->msg[2]; - *ana_freq = (msg->msg[3] << 8) | msg->msg[4]; - *bcast_system = msg->msg[5]; -} - -static inline void cec_msg_select_digital_service(struct cec_msg *msg, - const struct cec_op_digital_service_id *digital) -{ - msg->len = 9; - msg->msg[1] = CEC_MSG_SELECT_DIGITAL_SERVICE; - cec_set_digital_service_id(msg->msg + 2, digital); -} - -static inline void cec_ops_select_digital_service(const struct cec_msg *msg, - struct cec_op_digital_service_id *digital) -{ - cec_get_digital_service_id(msg->msg + 2, digital); -} - -static inline void cec_msg_tuner_step_decrement(struct cec_msg *msg) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_TUNER_STEP_DECREMENT; -} - -static inline void cec_msg_tuner_step_increment(struct cec_msg *msg) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_TUNER_STEP_INCREMENT; -} - - -/* Vendor Specific Commands Feature */ -static inline void cec_msg_device_vendor_id(struct cec_msg *msg, __u32 vendor_id) -{ - msg->len = 5; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_DEVICE_VENDOR_ID; - msg->msg[2] = vendor_id >> 16; - msg->msg[3] = (vendor_id >> 8) & 0xff; - msg->msg[4] = vendor_id & 0xff; -} - -static inline void cec_ops_device_vendor_id(const struct cec_msg *msg, - __u32 *vendor_id) -{ - *vendor_id = (msg->msg[2] << 16) | (msg->msg[3] << 8) | msg->msg[4]; -} - -static inline void cec_msg_give_device_vendor_id(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_GIVE_DEVICE_VENDOR_ID; - msg->reply = reply ? CEC_MSG_DEVICE_VENDOR_ID : 0; -} - -static inline void cec_msg_vendor_command(struct cec_msg *msg, - __u8 size, const __u8 *vendor_cmd) -{ - if (size > 14) - size = 14; - msg->len = 2 + size; - msg->msg[1] = CEC_MSG_VENDOR_COMMAND; - memcpy(msg->msg + 2, vendor_cmd, size); -} - -static inline void cec_ops_vendor_command(const struct cec_msg *msg, - __u8 *size, - const __u8 **vendor_cmd) -{ - *size = msg->len - 2; - - if (*size > 14) - *size = 14; - *vendor_cmd = msg->msg + 2; -} - -static inline void cec_msg_vendor_command_with_id(struct cec_msg *msg, - __u32 vendor_id, __u8 size, - const __u8 *vendor_cmd) -{ - if (size > 11) - size = 11; - msg->len = 5 + size; - msg->msg[1] = CEC_MSG_VENDOR_COMMAND_WITH_ID; - msg->msg[2] = vendor_id >> 16; - msg->msg[3] = (vendor_id >> 8) & 0xff; - msg->msg[4] = vendor_id & 0xff; - memcpy(msg->msg + 5, vendor_cmd, size); -} - -static inline void cec_ops_vendor_command_with_id(const struct cec_msg *msg, - __u32 *vendor_id, __u8 *size, - const __u8 **vendor_cmd) -{ - *size = msg->len - 5; - - if (*size > 11) - *size = 11; - *vendor_id = (msg->msg[2] << 16) | (msg->msg[3] << 8) | msg->msg[4]; - *vendor_cmd = msg->msg + 5; -} - -static inline void cec_msg_vendor_remote_button_down(struct cec_msg *msg, - __u8 size, - const __u8 *rc_code) -{ - if (size > 14) - size = 14; - msg->len = 2 + size; - msg->msg[1] = CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN; - memcpy(msg->msg + 2, rc_code, size); -} - -static inline void cec_ops_vendor_remote_button_down(const struct cec_msg *msg, - __u8 *size, - const __u8 **rc_code) -{ - *size = msg->len - 2; - - if (*size > 14) - *size = 14; - *rc_code = msg->msg + 2; -} - -static inline void cec_msg_vendor_remote_button_up(struct cec_msg *msg) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_VENDOR_REMOTE_BUTTON_UP; -} - - -/* OSD Display Feature */ -static inline void cec_msg_set_osd_string(struct cec_msg *msg, - __u8 disp_ctl, - const char *osd) -{ - unsigned int len = strlen(osd); - - if (len > 13) - len = 13; - msg->len = 3 + len; - msg->msg[1] = CEC_MSG_SET_OSD_STRING; - msg->msg[2] = disp_ctl; - memcpy(msg->msg + 3, osd, len); -} - -static inline void cec_ops_set_osd_string(const struct cec_msg *msg, - __u8 *disp_ctl, - char *osd) -{ - unsigned int len = msg->len > 3 ? msg->len - 3 : 0; - - *disp_ctl = msg->msg[2]; - if (len > 13) - len = 13; - memcpy(osd, msg->msg + 3, len); - osd[len] = '\0'; -} - - -/* Device OSD Transfer Feature */ -static inline void cec_msg_set_osd_name(struct cec_msg *msg, const char *name) -{ - unsigned int len = strlen(name); - - if (len > 14) - len = 14; - msg->len = 2 + len; - msg->msg[1] = CEC_MSG_SET_OSD_NAME; - memcpy(msg->msg + 2, name, len); -} - -static inline void cec_ops_set_osd_name(const struct cec_msg *msg, - char *name) -{ - unsigned int len = msg->len > 2 ? msg->len - 2 : 0; - - if (len > 14) - len = 14; - memcpy(name, msg->msg + 2, len); - name[len] = '\0'; -} - -static inline void cec_msg_give_osd_name(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_GIVE_OSD_NAME; - msg->reply = reply ? CEC_MSG_SET_OSD_NAME : 0; -} - - -/* Device Menu Control Feature */ -static inline void cec_msg_menu_status(struct cec_msg *msg, - __u8 menu_state) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_MENU_STATUS; - msg->msg[2] = menu_state; -} - -static inline void cec_ops_menu_status(const struct cec_msg *msg, - __u8 *menu_state) -{ - *menu_state = msg->msg[2]; -} - -static inline void cec_msg_menu_request(struct cec_msg *msg, - bool reply, - __u8 menu_req) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_MENU_REQUEST; - msg->msg[2] = menu_req; - msg->reply = reply ? CEC_MSG_MENU_STATUS : 0; -} - -static inline void cec_ops_menu_request(const struct cec_msg *msg, - __u8 *menu_req) -{ - *menu_req = msg->msg[2]; -} - -struct cec_op_ui_command { - __u8 ui_cmd; - bool has_opt_arg; - union { - struct cec_op_channel_data channel_identifier; - __u8 ui_broadcast_type; - __u8 ui_sound_presentation_control; - __u8 play_mode; - __u8 ui_function_media; - __u8 ui_function_select_av_input; - __u8 ui_function_select_audio_input; - }; -}; - -static inline void cec_msg_user_control_pressed(struct cec_msg *msg, - const struct cec_op_ui_command *ui_cmd) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_USER_CONTROL_PRESSED; - msg->msg[2] = ui_cmd->ui_cmd; - if (!ui_cmd->has_opt_arg) - return; - switch (ui_cmd->ui_cmd) { - case 0x56: - case 0x57: - case 0x60: - case 0x68: - case 0x69: - case 0x6a: - /* The optional operand is one byte for all these ui commands */ - msg->len++; - msg->msg[3] = ui_cmd->play_mode; - break; - case 0x67: - msg->len += 4; - msg->msg[3] = (ui_cmd->channel_identifier.channel_number_fmt << 2) | - (ui_cmd->channel_identifier.major >> 8); - msg->msg[4] = ui_cmd->channel_identifier.major & 0xff; - msg->msg[5] = ui_cmd->channel_identifier.minor >> 8; - msg->msg[6] = ui_cmd->channel_identifier.minor & 0xff; - break; - } -} - -static inline void cec_ops_user_control_pressed(const struct cec_msg *msg, - struct cec_op_ui_command *ui_cmd) -{ - ui_cmd->ui_cmd = msg->msg[2]; - ui_cmd->has_opt_arg = false; - if (msg->len == 3) - return; - switch (ui_cmd->ui_cmd) { - case 0x56: - case 0x57: - case 0x60: - case 0x68: - case 0x69: - case 0x6a: - /* The optional operand is one byte for all these ui commands */ - ui_cmd->play_mode = msg->msg[3]; - ui_cmd->has_opt_arg = true; - break; - case 0x67: - if (msg->len < 7) - break; - ui_cmd->has_opt_arg = true; - ui_cmd->channel_identifier.channel_number_fmt = msg->msg[3] >> 2; - ui_cmd->channel_identifier.major = ((msg->msg[3] & 3) << 6) | msg->msg[4]; - ui_cmd->channel_identifier.minor = (msg->msg[5] << 8) | msg->msg[6]; - break; - } -} - -static inline void cec_msg_user_control_released(struct cec_msg *msg) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_USER_CONTROL_RELEASED; -} - -/* Remote Control Passthrough Feature */ - -/* Power Status Feature */ -static inline void cec_msg_report_power_status(struct cec_msg *msg, - __u8 pwr_state) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_REPORT_POWER_STATUS; - msg->msg[2] = pwr_state; -} - -static inline void cec_ops_report_power_status(const struct cec_msg *msg, - __u8 *pwr_state) -{ - *pwr_state = msg->msg[2]; -} - -static inline void cec_msg_give_device_power_status(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_GIVE_DEVICE_POWER_STATUS; - msg->reply = reply ? CEC_MSG_REPORT_POWER_STATUS : 0; -} - -/* General Protocol Messages */ -static inline void cec_msg_feature_abort(struct cec_msg *msg, - __u8 abort_msg, __u8 reason) -{ - msg->len = 4; - msg->msg[1] = CEC_MSG_FEATURE_ABORT; - msg->msg[2] = abort_msg; - msg->msg[3] = reason; -} - -static inline void cec_ops_feature_abort(const struct cec_msg *msg, - __u8 *abort_msg, __u8 *reason) -{ - *abort_msg = msg->msg[2]; - *reason = msg->msg[3]; -} - -/* This changes the current message into a feature abort message */ -static inline void cec_msg_reply_feature_abort(struct cec_msg *msg, __u8 reason) -{ - cec_msg_set_reply_to(msg, msg); - msg->len = 4; - msg->msg[2] = msg->msg[1]; - msg->msg[3] = reason; - msg->msg[1] = CEC_MSG_FEATURE_ABORT; -} - -static inline void cec_msg_abort(struct cec_msg *msg) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_ABORT; -} - - -/* System Audio Control Feature */ -static inline void cec_msg_report_audio_status(struct cec_msg *msg, - __u8 aud_mute_status, - __u8 aud_vol_status) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_REPORT_AUDIO_STATUS; - msg->msg[2] = (aud_mute_status << 7) | (aud_vol_status & 0x7f); -} - -static inline void cec_ops_report_audio_status(const struct cec_msg *msg, - __u8 *aud_mute_status, - __u8 *aud_vol_status) -{ - *aud_mute_status = msg->msg[2] >> 7; - *aud_vol_status = msg->msg[2] & 0x7f; -} - -static inline void cec_msg_give_audio_status(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_GIVE_AUDIO_STATUS; - msg->reply = reply ? CEC_MSG_REPORT_AUDIO_STATUS : 0; -} - -static inline void cec_msg_set_system_audio_mode(struct cec_msg *msg, - __u8 sys_aud_status) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_SET_SYSTEM_AUDIO_MODE; - msg->msg[2] = sys_aud_status; -} - -static inline void cec_ops_set_system_audio_mode(const struct cec_msg *msg, - __u8 *sys_aud_status) -{ - *sys_aud_status = msg->msg[2]; -} - -static inline void cec_msg_system_audio_mode_request(struct cec_msg *msg, - bool reply, - __u16 phys_addr) -{ - msg->len = phys_addr == 0xffff ? 2 : 4; - msg->msg[1] = CEC_MSG_SYSTEM_AUDIO_MODE_REQUEST; - msg->msg[2] = phys_addr >> 8; - msg->msg[3] = phys_addr & 0xff; - msg->reply = reply ? CEC_MSG_SET_SYSTEM_AUDIO_MODE : 0; - -} - -static inline void cec_ops_system_audio_mode_request(const struct cec_msg *msg, - __u16 *phys_addr) -{ - if (msg->len < 4) - *phys_addr = 0xffff; - else - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; -} - -static inline void cec_msg_system_audio_mode_status(struct cec_msg *msg, - __u8 sys_aud_status) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_SYSTEM_AUDIO_MODE_STATUS; - msg->msg[2] = sys_aud_status; -} - -static inline void cec_ops_system_audio_mode_status(const struct cec_msg *msg, - __u8 *sys_aud_status) -{ - *sys_aud_status = msg->msg[2]; -} - -static inline void cec_msg_give_system_audio_mode_status(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS; - msg->reply = reply ? CEC_MSG_SYSTEM_AUDIO_MODE_STATUS : 0; -} - -static inline void cec_msg_report_short_audio_descriptor(struct cec_msg *msg, - __u8 num_descriptors, - const __u32 *descriptors) -{ - unsigned int i; - - if (num_descriptors > 4) - num_descriptors = 4; - msg->len = 2 + num_descriptors * 3; - msg->msg[1] = CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR; - for (i = 0; i < num_descriptors; i++) { - msg->msg[2 + i * 3] = (descriptors[i] >> 16) & 0xff; - msg->msg[3 + i * 3] = (descriptors[i] >> 8) & 0xff; - msg->msg[4 + i * 3] = descriptors[i] & 0xff; - } -} - -static inline void cec_ops_report_short_audio_descriptor(const struct cec_msg *msg, - __u8 *num_descriptors, - __u32 *descriptors) -{ - unsigned int i; - - *num_descriptors = (msg->len - 2) / 3; - if (*num_descriptors > 4) - *num_descriptors = 4; - for (i = 0; i < *num_descriptors; i++) - descriptors[i] = (msg->msg[2 + i * 3] << 16) | - (msg->msg[3 + i * 3] << 8) | - msg->msg[4 + i * 3]; -} - -static inline void cec_msg_request_short_audio_descriptor(struct cec_msg *msg, - bool reply, - __u8 num_descriptors, - const __u8 *audio_format_id, - const __u8 *audio_format_code) -{ - unsigned int i; - - if (num_descriptors > 4) - num_descriptors = 4; - msg->len = 2 + num_descriptors; - msg->msg[1] = CEC_MSG_REQUEST_SHORT_AUDIO_DESCRIPTOR; - msg->reply = reply ? CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR : 0; - for (i = 0; i < num_descriptors; i++) - msg->msg[2 + i] = (audio_format_id[i] << 6) | - (audio_format_code[i] & 0x3f); -} - -static inline void cec_ops_request_short_audio_descriptor(const struct cec_msg *msg, - __u8 *num_descriptors, - __u8 *audio_format_id, - __u8 *audio_format_code) -{ - unsigned int i; - - *num_descriptors = msg->len - 2; - if (*num_descriptors > 4) - *num_descriptors = 4; - for (i = 0; i < *num_descriptors; i++) { - audio_format_id[i] = msg->msg[2 + i] >> 6; - audio_format_code[i] = msg->msg[2 + i] & 0x3f; - } -} - - -/* Audio Rate Control Feature */ -static inline void cec_msg_set_audio_rate(struct cec_msg *msg, - __u8 audio_rate) -{ - msg->len = 3; - msg->msg[1] = CEC_MSG_SET_AUDIO_RATE; - msg->msg[2] = audio_rate; -} - -static inline void cec_ops_set_audio_rate(const struct cec_msg *msg, - __u8 *audio_rate) -{ - *audio_rate = msg->msg[2]; -} - - -/* Audio Return Channel Control Feature */ -static inline void cec_msg_report_arc_initiated(struct cec_msg *msg) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_REPORT_ARC_INITIATED; -} - -static inline void cec_msg_initiate_arc(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_INITIATE_ARC; - msg->reply = reply ? CEC_MSG_REPORT_ARC_INITIATED : 0; -} - -static inline void cec_msg_request_arc_initiation(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_REQUEST_ARC_INITIATION; - msg->reply = reply ? CEC_MSG_INITIATE_ARC : 0; -} - -static inline void cec_msg_report_arc_terminated(struct cec_msg *msg) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_REPORT_ARC_TERMINATED; -} - -static inline void cec_msg_terminate_arc(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_TERMINATE_ARC; - msg->reply = reply ? CEC_MSG_REPORT_ARC_TERMINATED : 0; -} - -static inline void cec_msg_request_arc_termination(struct cec_msg *msg, - bool reply) -{ - msg->len = 2; - msg->msg[1] = CEC_MSG_REQUEST_ARC_TERMINATION; - msg->reply = reply ? CEC_MSG_TERMINATE_ARC : 0; -} - - -/* Dynamic Audio Lipsync Feature */ -/* Only for CEC 2.0 and up */ -static inline void cec_msg_report_current_latency(struct cec_msg *msg, - __u16 phys_addr, - __u8 video_latency, - __u8 low_latency_mode, - __u8 audio_out_compensated, - __u8 audio_out_delay) -{ - msg->len = 7; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_REPORT_CURRENT_LATENCY; - msg->msg[2] = phys_addr >> 8; - msg->msg[3] = phys_addr & 0xff; - msg->msg[4] = video_latency; - msg->msg[5] = (low_latency_mode << 2) | audio_out_compensated; - msg->msg[6] = audio_out_delay; -} - -static inline void cec_ops_report_current_latency(const struct cec_msg *msg, - __u16 *phys_addr, - __u8 *video_latency, - __u8 *low_latency_mode, - __u8 *audio_out_compensated, - __u8 *audio_out_delay) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; - *video_latency = msg->msg[4]; - *low_latency_mode = (msg->msg[5] >> 2) & 1; - *audio_out_compensated = msg->msg[5] & 3; - *audio_out_delay = msg->msg[6]; -} - -static inline void cec_msg_request_current_latency(struct cec_msg *msg, - bool reply, - __u16 phys_addr) -{ - msg->len = 4; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_REQUEST_CURRENT_LATENCY; - msg->msg[2] = phys_addr >> 8; - msg->msg[3] = phys_addr & 0xff; - msg->reply = reply ? CEC_MSG_REPORT_CURRENT_LATENCY : 0; -} - -static inline void cec_ops_request_current_latency(const struct cec_msg *msg, - __u16 *phys_addr) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; -} - - -/* Capability Discovery and Control Feature */ -static inline void cec_msg_cdc_hec_inquire_state(struct cec_msg *msg, - __u16 phys_addr1, - __u16 phys_addr2) -{ - msg->len = 9; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_CDC_MESSAGE; - /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ - msg->msg[4] = CEC_MSG_CDC_HEC_INQUIRE_STATE; - msg->msg[5] = phys_addr1 >> 8; - msg->msg[6] = phys_addr1 & 0xff; - msg->msg[7] = phys_addr2 >> 8; - msg->msg[8] = phys_addr2 & 0xff; -} - -static inline void cec_ops_cdc_hec_inquire_state(const struct cec_msg *msg, - __u16 *phys_addr, - __u16 *phys_addr1, - __u16 *phys_addr2) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; - *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; - *phys_addr2 = (msg->msg[7] << 8) | msg->msg[8]; -} - -static inline void cec_msg_cdc_hec_report_state(struct cec_msg *msg, - __u16 target_phys_addr, - __u8 hec_func_state, - __u8 host_func_state, - __u8 enc_func_state, - __u8 cdc_errcode, - __u8 has_field, - __u16 hec_field) -{ - msg->len = has_field ? 10 : 8; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_CDC_MESSAGE; - /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ - msg->msg[4] = CEC_MSG_CDC_HEC_REPORT_STATE; - msg->msg[5] = target_phys_addr >> 8; - msg->msg[6] = target_phys_addr & 0xff; - msg->msg[7] = (hec_func_state << 6) | - (host_func_state << 4) | - (enc_func_state << 2) | - cdc_errcode; - if (has_field) { - msg->msg[8] = hec_field >> 8; - msg->msg[9] = hec_field & 0xff; - } -} - -static inline void cec_ops_cdc_hec_report_state(const struct cec_msg *msg, - __u16 *phys_addr, - __u16 *target_phys_addr, - __u8 *hec_func_state, - __u8 *host_func_state, - __u8 *enc_func_state, - __u8 *cdc_errcode, - __u8 *has_field, - __u16 *hec_field) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; - *target_phys_addr = (msg->msg[5] << 8) | msg->msg[6]; - *hec_func_state = msg->msg[7] >> 6; - *host_func_state = (msg->msg[7] >> 4) & 3; - *enc_func_state = (msg->msg[7] >> 4) & 3; - *cdc_errcode = msg->msg[7] & 3; - *has_field = msg->len >= 10; - *hec_field = *has_field ? ((msg->msg[8] << 8) | msg->msg[9]) : 0; -} - -static inline void cec_msg_cdc_hec_set_state(struct cec_msg *msg, - __u16 phys_addr1, - __u16 phys_addr2, - __u8 hec_set_state, - __u16 phys_addr3, - __u16 phys_addr4, - __u16 phys_addr5) -{ - msg->len = 10; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_CDC_MESSAGE; - /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ - msg->msg[4] = CEC_MSG_CDC_HEC_INQUIRE_STATE; - msg->msg[5] = phys_addr1 >> 8; - msg->msg[6] = phys_addr1 & 0xff; - msg->msg[7] = phys_addr2 >> 8; - msg->msg[8] = phys_addr2 & 0xff; - msg->msg[9] = hec_set_state; - if (phys_addr3 != CEC_PHYS_ADDR_INVALID) { - msg->msg[msg->len++] = phys_addr3 >> 8; - msg->msg[msg->len++] = phys_addr3 & 0xff; - if (phys_addr4 != CEC_PHYS_ADDR_INVALID) { - msg->msg[msg->len++] = phys_addr4 >> 8; - msg->msg[msg->len++] = phys_addr4 & 0xff; - if (phys_addr5 != CEC_PHYS_ADDR_INVALID) { - msg->msg[msg->len++] = phys_addr5 >> 8; - msg->msg[msg->len++] = phys_addr5 & 0xff; - } - } - } -} - -static inline void cec_ops_cdc_hec_set_state(const struct cec_msg *msg, - __u16 *phys_addr, - __u16 *phys_addr1, - __u16 *phys_addr2, - __u8 *hec_set_state, - __u16 *phys_addr3, - __u16 *phys_addr4, - __u16 *phys_addr5) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; - *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; - *phys_addr2 = (msg->msg[7] << 8) | msg->msg[8]; - *hec_set_state = msg->msg[9]; - *phys_addr3 = *phys_addr4 = *phys_addr5 = CEC_PHYS_ADDR_INVALID; - if (msg->len >= 12) - *phys_addr3 = (msg->msg[10] << 8) | msg->msg[11]; - if (msg->len >= 14) - *phys_addr4 = (msg->msg[12] << 8) | msg->msg[13]; - if (msg->len >= 16) - *phys_addr5 = (msg->msg[14] << 8) | msg->msg[15]; -} - -static inline void cec_msg_cdc_hec_set_state_adjacent(struct cec_msg *msg, - __u16 phys_addr1, - __u8 hec_set_state) -{ - msg->len = 8; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_CDC_MESSAGE; - /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ - msg->msg[4] = CEC_MSG_CDC_HEC_SET_STATE_ADJACENT; - msg->msg[5] = phys_addr1 >> 8; - msg->msg[6] = phys_addr1 & 0xff; - msg->msg[7] = hec_set_state; -} - -static inline void cec_ops_cdc_hec_set_state_adjacent(const struct cec_msg *msg, - __u16 *phys_addr, - __u16 *phys_addr1, - __u8 *hec_set_state) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; - *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; - *hec_set_state = msg->msg[7]; -} - -static inline void cec_msg_cdc_hec_request_deactivation(struct cec_msg *msg, - __u16 phys_addr1, - __u16 phys_addr2, - __u16 phys_addr3) -{ - msg->len = 11; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_CDC_MESSAGE; - /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ - msg->msg[4] = CEC_MSG_CDC_HEC_REQUEST_DEACTIVATION; - msg->msg[5] = phys_addr1 >> 8; - msg->msg[6] = phys_addr1 & 0xff; - msg->msg[7] = phys_addr2 >> 8; - msg->msg[8] = phys_addr2 & 0xff; - msg->msg[9] = phys_addr3 >> 8; - msg->msg[10] = phys_addr3 & 0xff; -} - -static inline void cec_ops_cdc_hec_request_deactivation(const struct cec_msg *msg, - __u16 *phys_addr, - __u16 *phys_addr1, - __u16 *phys_addr2, - __u16 *phys_addr3) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; - *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; - *phys_addr2 = (msg->msg[7] << 8) | msg->msg[8]; - *phys_addr3 = (msg->msg[9] << 8) | msg->msg[10]; -} - -static inline void cec_msg_cdc_hec_notify_alive(struct cec_msg *msg) -{ - msg->len = 5; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_CDC_MESSAGE; - /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ - msg->msg[4] = CEC_MSG_CDC_HEC_NOTIFY_ALIVE; -} - -static inline void cec_ops_cdc_hec_notify_alive(const struct cec_msg *msg, - __u16 *phys_addr) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; -} - -static inline void cec_msg_cdc_hec_discover(struct cec_msg *msg) -{ - msg->len = 5; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_CDC_MESSAGE; - /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ - msg->msg[4] = CEC_MSG_CDC_HEC_DISCOVER; -} - -static inline void cec_ops_cdc_hec_discover(const struct cec_msg *msg, - __u16 *phys_addr) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; -} - -static inline void cec_msg_cdc_hpd_set_state(struct cec_msg *msg, - __u8 input_port, - __u8 hpd_state) -{ - msg->len = 6; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_CDC_MESSAGE; - /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ - msg->msg[4] = CEC_MSG_CDC_HPD_SET_STATE; - msg->msg[5] = (input_port << 4) | hpd_state; -} - -static inline void cec_ops_cdc_hpd_set_state(const struct cec_msg *msg, - __u16 *phys_addr, - __u8 *input_port, - __u8 *hpd_state) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; - *input_port = msg->msg[5] >> 4; - *hpd_state = msg->msg[5] & 0xf; -} - -static inline void cec_msg_cdc_hpd_report_state(struct cec_msg *msg, - __u8 hpd_state, - __u8 hpd_error) -{ - msg->len = 6; - msg->msg[0] |= 0xf; /* broadcast */ - msg->msg[1] = CEC_MSG_CDC_MESSAGE; - /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ - msg->msg[4] = CEC_MSG_CDC_HPD_REPORT_STATE; - msg->msg[5] = (hpd_state << 4) | hpd_error; -} - -static inline void cec_ops_cdc_hpd_report_state(const struct cec_msg *msg, - __u16 *phys_addr, - __u8 *hpd_state, - __u8 *hpd_error) -{ - *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; - *hpd_state = msg->msg[5] >> 4; - *hpd_error = msg->msg[5] & 0xf; -} - -#endif diff --git a/include/linux/cec.h b/include/linux/cec.h deleted file mode 100644 index 9c87711c0e1c..000000000000 --- a/include/linux/cec.h +++ /dev/null @@ -1,1071 +0,0 @@ -/* - * cec - HDMI Consumer Electronics Control public header - * - * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - * - * This program is free software; you may redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * Alternatively you can redistribute this file under the terms of the - * BSD license as stated below: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * 3. The names of its contributors may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* - * Note: this framework is still in staging and it is likely the API - * will change before it goes out of staging. - * - * Once it is moved out of staging this header will move to uapi. - */ -#ifndef _CEC_UAPI_H -#define _CEC_UAPI_H - -#include - -#define CEC_MAX_MSG_SIZE 16 - -/** - * struct cec_msg - CEC message structure. - * @tx_ts: Timestamp in nanoseconds using CLOCK_MONOTONIC. Set by the - * driver when the message transmission has finished. - * @rx_ts: Timestamp in nanoseconds using CLOCK_MONOTONIC. Set by the - * driver when the message was received. - * @len: Length in bytes of the message. - * @timeout: The timeout (in ms) that is used to timeout CEC_RECEIVE. - * Set to 0 if you want to wait forever. This timeout can also be - * used with CEC_TRANSMIT as the timeout for waiting for a reply. - * If 0, then it will use a 1 second timeout instead of waiting - * forever as is done with CEC_RECEIVE. - * @sequence: The framework assigns a sequence number to messages that are - * sent. This can be used to track replies to previously sent - * messages. - * @flags: Set to 0. - * @msg: The message payload. - * @reply: This field is ignored with CEC_RECEIVE and is only used by - * CEC_TRANSMIT. If non-zero, then wait for a reply with this - * opcode. Set to CEC_MSG_FEATURE_ABORT if you want to wait for - * a possible ABORT reply. If there was an error when sending the - * msg or FeatureAbort was returned, then reply is set to 0. - * If reply is non-zero upon return, then len/msg are set to - * the received message. - * If reply is zero upon return and status has the - * CEC_TX_STATUS_FEATURE_ABORT bit set, then len/msg are set to - * the received feature abort message. - * If reply is zero upon return and status has the - * CEC_TX_STATUS_MAX_RETRIES bit set, then no reply was seen at - * all. If reply is non-zero for CEC_TRANSMIT and the message is a - * broadcast, then -EINVAL is returned. - * if reply is non-zero, then timeout is set to 1000 (the required - * maximum response time). - * @rx_status: The message receive status bits. Set by the driver. - * @tx_status: The message transmit status bits. Set by the driver. - * @tx_arb_lost_cnt: The number of 'Arbitration Lost' events. Set by the driver. - * @tx_nack_cnt: The number of 'Not Acknowledged' events. Set by the driver. - * @tx_low_drive_cnt: The number of 'Low Drive Detected' events. Set by the - * driver. - * @tx_error_cnt: The number of 'Error' events. Set by the driver. - */ -struct cec_msg { - __u64 tx_ts; - __u64 rx_ts; - __u32 len; - __u32 timeout; - __u32 sequence; - __u32 flags; - __u8 msg[CEC_MAX_MSG_SIZE]; - __u8 reply; - __u8 rx_status; - __u8 tx_status; - __u8 tx_arb_lost_cnt; - __u8 tx_nack_cnt; - __u8 tx_low_drive_cnt; - __u8 tx_error_cnt; -}; - -/** - * cec_msg_initiator - return the initiator's logical address. - * @msg: the message structure - */ -static inline __u8 cec_msg_initiator(const struct cec_msg *msg) -{ - return msg->msg[0] >> 4; -} - -/** - * cec_msg_destination - return the destination's logical address. - * @msg: the message structure - */ -static inline __u8 cec_msg_destination(const struct cec_msg *msg) -{ - return msg->msg[0] & 0xf; -} - -/** - * cec_msg_opcode - return the opcode of the message, -1 for poll - * @msg: the message structure - */ -static inline int cec_msg_opcode(const struct cec_msg *msg) -{ - return msg->len > 1 ? msg->msg[1] : -1; -} - -/** - * cec_msg_is_broadcast - return true if this is a broadcast message. - * @msg: the message structure - */ -static inline bool cec_msg_is_broadcast(const struct cec_msg *msg) -{ - return (msg->msg[0] & 0xf) == 0xf; -} - -/** - * cec_msg_init - initialize the message structure. - * @msg: the message structure - * @initiator: the logical address of the initiator - * @destination:the logical address of the destination (0xf for broadcast) - * - * The whole structure is zeroed, the len field is set to 1 (i.e. a poll - * message) and the initiator and destination are filled in. - */ -static inline void cec_msg_init(struct cec_msg *msg, - __u8 initiator, __u8 destination) -{ - memset(msg, 0, sizeof(*msg)); - msg->msg[0] = (initiator << 4) | destination; - msg->len = 1; -} - -/** - * cec_msg_set_reply_to - fill in destination/initiator in a reply message. - * @msg: the message structure for the reply - * @orig: the original message structure - * - * Set the msg destination to the orig initiator and the msg initiator to the - * orig destination. Note that msg and orig may be the same pointer, in which - * case the change is done in place. - */ -static inline void cec_msg_set_reply_to(struct cec_msg *msg, - struct cec_msg *orig) -{ - /* The destination becomes the initiator and vice versa */ - msg->msg[0] = (cec_msg_destination(orig) << 4) | - cec_msg_initiator(orig); - msg->reply = msg->timeout = 0; -} - -/* cec_msg flags field */ -#define CEC_MSG_FL_REPLY_TO_FOLLOWERS (1 << 0) - -/* cec_msg tx/rx_status field */ -#define CEC_TX_STATUS_OK (1 << 0) -#define CEC_TX_STATUS_ARB_LOST (1 << 1) -#define CEC_TX_STATUS_NACK (1 << 2) -#define CEC_TX_STATUS_LOW_DRIVE (1 << 3) -#define CEC_TX_STATUS_ERROR (1 << 4) -#define CEC_TX_STATUS_MAX_RETRIES (1 << 5) - -#define CEC_RX_STATUS_OK (1 << 0) -#define CEC_RX_STATUS_TIMEOUT (1 << 1) -#define CEC_RX_STATUS_FEATURE_ABORT (1 << 2) - -static inline bool cec_msg_status_is_ok(const struct cec_msg *msg) -{ - if (msg->tx_status && !(msg->tx_status & CEC_TX_STATUS_OK)) - return false; - if (msg->rx_status && !(msg->rx_status & CEC_RX_STATUS_OK)) - return false; - if (!msg->tx_status && !msg->rx_status) - return false; - return !(msg->rx_status & CEC_RX_STATUS_FEATURE_ABORT); -} - -#define CEC_LOG_ADDR_INVALID 0xff -#define CEC_PHYS_ADDR_INVALID 0xffff - -/* - * The maximum number of logical addresses one device can be assigned to. - * The CEC 2.0 spec allows for only 2 logical addresses at the moment. The - * Analog Devices CEC hardware supports 3. So let's go wild and go for 4. - */ -#define CEC_MAX_LOG_ADDRS 4 - -/* The logical addresses defined by CEC 2.0 */ -#define CEC_LOG_ADDR_TV 0 -#define CEC_LOG_ADDR_RECORD_1 1 -#define CEC_LOG_ADDR_RECORD_2 2 -#define CEC_LOG_ADDR_TUNER_1 3 -#define CEC_LOG_ADDR_PLAYBACK_1 4 -#define CEC_LOG_ADDR_AUDIOSYSTEM 5 -#define CEC_LOG_ADDR_TUNER_2 6 -#define CEC_LOG_ADDR_TUNER_3 7 -#define CEC_LOG_ADDR_PLAYBACK_2 8 -#define CEC_LOG_ADDR_RECORD_3 9 -#define CEC_LOG_ADDR_TUNER_4 10 -#define CEC_LOG_ADDR_PLAYBACK_3 11 -#define CEC_LOG_ADDR_BACKUP_1 12 -#define CEC_LOG_ADDR_BACKUP_2 13 -#define CEC_LOG_ADDR_SPECIFIC 14 -#define CEC_LOG_ADDR_UNREGISTERED 15 /* as initiator address */ -#define CEC_LOG_ADDR_BROADCAST 15 /* ad destination address */ - -/* The logical address types that the CEC device wants to claim */ -#define CEC_LOG_ADDR_TYPE_TV 0 -#define CEC_LOG_ADDR_TYPE_RECORD 1 -#define CEC_LOG_ADDR_TYPE_TUNER 2 -#define CEC_LOG_ADDR_TYPE_PLAYBACK 3 -#define CEC_LOG_ADDR_TYPE_AUDIOSYSTEM 4 -#define CEC_LOG_ADDR_TYPE_SPECIFIC 5 -#define CEC_LOG_ADDR_TYPE_UNREGISTERED 6 -/* - * Switches should use UNREGISTERED. - * Processors should use SPECIFIC. - */ - -#define CEC_LOG_ADDR_MASK_TV (1 << CEC_LOG_ADDR_TV) -#define CEC_LOG_ADDR_MASK_RECORD ((1 << CEC_LOG_ADDR_RECORD_1) | \ - (1 << CEC_LOG_ADDR_RECORD_2) | \ - (1 << CEC_LOG_ADDR_RECORD_3)) -#define CEC_LOG_ADDR_MASK_TUNER ((1 << CEC_LOG_ADDR_TUNER_1) | \ - (1 << CEC_LOG_ADDR_TUNER_2) | \ - (1 << CEC_LOG_ADDR_TUNER_3) | \ - (1 << CEC_LOG_ADDR_TUNER_4)) -#define CEC_LOG_ADDR_MASK_PLAYBACK ((1 << CEC_LOG_ADDR_PLAYBACK_1) | \ - (1 << CEC_LOG_ADDR_PLAYBACK_2) | \ - (1 << CEC_LOG_ADDR_PLAYBACK_3)) -#define CEC_LOG_ADDR_MASK_AUDIOSYSTEM (1 << CEC_LOG_ADDR_AUDIOSYSTEM) -#define CEC_LOG_ADDR_MASK_BACKUP ((1 << CEC_LOG_ADDR_BACKUP_1) | \ - (1 << CEC_LOG_ADDR_BACKUP_2)) -#define CEC_LOG_ADDR_MASK_SPECIFIC (1 << CEC_LOG_ADDR_SPECIFIC) -#define CEC_LOG_ADDR_MASK_UNREGISTERED (1 << CEC_LOG_ADDR_UNREGISTERED) - -static inline bool cec_has_tv(__u16 log_addr_mask) -{ - return log_addr_mask & CEC_LOG_ADDR_MASK_TV; -} - -static inline bool cec_has_record(__u16 log_addr_mask) -{ - return log_addr_mask & CEC_LOG_ADDR_MASK_RECORD; -} - -static inline bool cec_has_tuner(__u16 log_addr_mask) -{ - return log_addr_mask & CEC_LOG_ADDR_MASK_TUNER; -} - -static inline bool cec_has_playback(__u16 log_addr_mask) -{ - return log_addr_mask & CEC_LOG_ADDR_MASK_PLAYBACK; -} - -static inline bool cec_has_audiosystem(__u16 log_addr_mask) -{ - return log_addr_mask & CEC_LOG_ADDR_MASK_AUDIOSYSTEM; -} - -static inline bool cec_has_backup(__u16 log_addr_mask) -{ - return log_addr_mask & CEC_LOG_ADDR_MASK_BACKUP; -} - -static inline bool cec_has_specific(__u16 log_addr_mask) -{ - return log_addr_mask & CEC_LOG_ADDR_MASK_SPECIFIC; -} - -static inline bool cec_is_unregistered(__u16 log_addr_mask) -{ - return log_addr_mask & CEC_LOG_ADDR_MASK_UNREGISTERED; -} - -static inline bool cec_is_unconfigured(__u16 log_addr_mask) -{ - return log_addr_mask == 0; -} - -/* - * Use this if there is no vendor ID (CEC_G_VENDOR_ID) or if the vendor ID - * should be disabled (CEC_S_VENDOR_ID) - */ -#define CEC_VENDOR_ID_NONE 0xffffffff - -/* The message handling modes */ -/* Modes for initiator */ -#define CEC_MODE_NO_INITIATOR (0x0 << 0) -#define CEC_MODE_INITIATOR (0x1 << 0) -#define CEC_MODE_EXCL_INITIATOR (0x2 << 0) -#define CEC_MODE_INITIATOR_MSK 0x0f - -/* Modes for follower */ -#define CEC_MODE_NO_FOLLOWER (0x0 << 4) -#define CEC_MODE_FOLLOWER (0x1 << 4) -#define CEC_MODE_EXCL_FOLLOWER (0x2 << 4) -#define CEC_MODE_EXCL_FOLLOWER_PASSTHRU (0x3 << 4) -#define CEC_MODE_MONITOR (0xe << 4) -#define CEC_MODE_MONITOR_ALL (0xf << 4) -#define CEC_MODE_FOLLOWER_MSK 0xf0 - -/* Userspace has to configure the physical address */ -#define CEC_CAP_PHYS_ADDR (1 << 0) -/* Userspace has to configure the logical addresses */ -#define CEC_CAP_LOG_ADDRS (1 << 1) -/* Userspace can transmit messages (and thus become follower as well) */ -#define CEC_CAP_TRANSMIT (1 << 2) -/* - * Passthrough all messages instead of processing them. - */ -#define CEC_CAP_PASSTHROUGH (1 << 3) -/* Supports remote control */ -#define CEC_CAP_RC (1 << 4) -/* Hardware can monitor all messages, not just directed and broadcast. */ -#define CEC_CAP_MONITOR_ALL (1 << 5) - -/** - * struct cec_caps - CEC capabilities structure. - * @driver: name of the CEC device driver. - * @name: name of the CEC device. @driver + @name must be unique. - * @available_log_addrs: number of available logical addresses. - * @capabilities: capabilities of the CEC adapter. - * @version: version of the CEC adapter framework. - */ -struct cec_caps { - char driver[32]; - char name[32]; - __u32 available_log_addrs; - __u32 capabilities; - __u32 version; -}; - -/** - * struct cec_log_addrs - CEC logical addresses structure. - * @log_addr: the claimed logical addresses. Set by the driver. - * @log_addr_mask: current logical address mask. Set by the driver. - * @cec_version: the CEC version that the adapter should implement. Set by the - * caller. - * @num_log_addrs: how many logical addresses should be claimed. Set by the - * caller. - * @vendor_id: the vendor ID of the device. Set by the caller. - * @flags: flags. - * @osd_name: the OSD name of the device. Set by the caller. - * @primary_device_type: the primary device type for each logical address. - * Set by the caller. - * @log_addr_type: the logical address types. Set by the caller. - * @all_device_types: CEC 2.0: all device types represented by the logical - * address. Set by the caller. - * @features: CEC 2.0: The logical address features. Set by the caller. - */ -struct cec_log_addrs { - __u8 log_addr[CEC_MAX_LOG_ADDRS]; - __u16 log_addr_mask; - __u8 cec_version; - __u8 num_log_addrs; - __u32 vendor_id; - __u32 flags; - char osd_name[15]; - __u8 primary_device_type[CEC_MAX_LOG_ADDRS]; - __u8 log_addr_type[CEC_MAX_LOG_ADDRS]; - - /* CEC 2.0 */ - __u8 all_device_types[CEC_MAX_LOG_ADDRS]; - __u8 features[CEC_MAX_LOG_ADDRS][12]; -}; - -/* Allow a fallback to unregistered */ -#define CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK (1 << 0) -/* Passthrough RC messages to the input subsystem */ -#define CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU (1 << 1) -/* CDC-Only device: supports only CDC messages */ -#define CEC_LOG_ADDRS_FL_CDC_ONLY (1 << 2) - -/* Events */ - -/* Event that occurs when the adapter state changes */ -#define CEC_EVENT_STATE_CHANGE 1 -/* - * This event is sent when messages are lost because the application - * didn't empty the message queue in time - */ -#define CEC_EVENT_LOST_MSGS 2 - -#define CEC_EVENT_FL_INITIAL_STATE (1 << 0) - -/** - * struct cec_event_state_change - used when the CEC adapter changes state. - * @phys_addr: the current physical address - * @log_addr_mask: the current logical address mask - */ -struct cec_event_state_change { - __u16 phys_addr; - __u16 log_addr_mask; -}; - -/** - * struct cec_event_lost_msgs - tells you how many messages were lost due. - * @lost_msgs: how many messages were lost. - */ -struct cec_event_lost_msgs { - __u32 lost_msgs; -}; - -/** - * struct cec_event - CEC event structure - * @ts: the timestamp of when the event was sent. - * @event: the event. - * array. - * @state_change: the event payload for CEC_EVENT_STATE_CHANGE. - * @lost_msgs: the event payload for CEC_EVENT_LOST_MSGS. - * @raw: array to pad the union. - */ -struct cec_event { - __u64 ts; - __u32 event; - __u32 flags; - union { - struct cec_event_state_change state_change; - struct cec_event_lost_msgs lost_msgs; - __u32 raw[16]; - }; -}; - -/* ioctls */ - -/* Adapter capabilities */ -#define CEC_ADAP_G_CAPS _IOWR('a', 0, struct cec_caps) - -/* - * phys_addr is either 0 (if this is the CEC root device) - * or a valid physical address obtained from the sink's EDID - * as read by this CEC device (if this is a source device) - * or a physical address obtained and modified from a sink - * EDID and used for a sink CEC device. - * If nothing is connected, then phys_addr is 0xffff. - * See HDMI 1.4b, section 8.7 (Physical Address). - * - * The CEC_ADAP_S_PHYS_ADDR ioctl may not be available if that is handled - * internally. - */ -#define CEC_ADAP_G_PHYS_ADDR _IOR('a', 1, __u16) -#define CEC_ADAP_S_PHYS_ADDR _IOW('a', 2, __u16) - -/* - * Configure the CEC adapter. It sets the device type and which - * logical types it will try to claim. It will return which - * logical addresses it could actually claim. - * An error is returned if the adapter is disabled or if there - * is no physical address assigned. - */ - -#define CEC_ADAP_G_LOG_ADDRS _IOR('a', 3, struct cec_log_addrs) -#define CEC_ADAP_S_LOG_ADDRS _IOWR('a', 4, struct cec_log_addrs) - -/* Transmit/receive a CEC command */ -#define CEC_TRANSMIT _IOWR('a', 5, struct cec_msg) -#define CEC_RECEIVE _IOWR('a', 6, struct cec_msg) - -/* Dequeue CEC events */ -#define CEC_DQEVENT _IOWR('a', 7, struct cec_event) - -/* - * Get and set the message handling mode for this filehandle. - */ -#define CEC_G_MODE _IOR('a', 8, __u32) -#define CEC_S_MODE _IOW('a', 9, __u32) - -/* - * The remainder of this header defines all CEC messages and operands. - * The format matters since it the cec-ctl utility parses it to generate - * code for implementing all these messages. - * - * Comments ending with 'Feature' group messages for each feature. - * If messages are part of multiple features, then the "Has also" - * comment is used to list the previously defined messages that are - * supported by the feature. - * - * Before operands are defined a comment is added that gives the - * name of the operand and in brackets the variable name of the - * corresponding argument in the cec-funcs.h function. - */ - -/* Messages */ - -/* One Touch Play Feature */ -#define CEC_MSG_ACTIVE_SOURCE 0x82 -#define CEC_MSG_IMAGE_VIEW_ON 0x04 -#define CEC_MSG_TEXT_VIEW_ON 0x0d - - -/* Routing Control Feature */ - -/* - * Has also: - * CEC_MSG_ACTIVE_SOURCE - */ - -#define CEC_MSG_INACTIVE_SOURCE 0x9d -#define CEC_MSG_REQUEST_ACTIVE_SOURCE 0x85 -#define CEC_MSG_ROUTING_CHANGE 0x80 -#define CEC_MSG_ROUTING_INFORMATION 0x81 -#define CEC_MSG_SET_STREAM_PATH 0x86 - - -/* Standby Feature */ -#define CEC_MSG_STANDBY 0x36 - - -/* One Touch Record Feature */ -#define CEC_MSG_RECORD_OFF 0x0b -#define CEC_MSG_RECORD_ON 0x09 -/* Record Source Type Operand (rec_src_type) */ -#define CEC_OP_RECORD_SRC_OWN 1 -#define CEC_OP_RECORD_SRC_DIGITAL 2 -#define CEC_OP_RECORD_SRC_ANALOG 3 -#define CEC_OP_RECORD_SRC_EXT_PLUG 4 -#define CEC_OP_RECORD_SRC_EXT_PHYS_ADDR 5 -/* Service Identification Method Operand (service_id_method) */ -#define CEC_OP_SERVICE_ID_METHOD_BY_DIG_ID 0 -#define CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL 1 -/* Digital Service Broadcast System Operand (dig_bcast_system) */ -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN 0x00 -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN 0x01 -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN 0x02 -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS 0x08 -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS 0x09 -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T 0x0a -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE 0x10 -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT 0x11 -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T 0x12 -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C 0x18 -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S 0x19 -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2 0x1a -#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T 0x1b -/* Analogue Broadcast Type Operand (ana_bcast_type) */ -#define CEC_OP_ANA_BCAST_TYPE_CABLE 0 -#define CEC_OP_ANA_BCAST_TYPE_SATELLITE 1 -#define CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL 2 -/* Broadcast System Operand (bcast_system) */ -#define CEC_OP_BCAST_SYSTEM_PAL_BG 0x00 -#define CEC_OP_BCAST_SYSTEM_SECAM_LQ 0x01 /* SECAM L' */ -#define CEC_OP_BCAST_SYSTEM_PAL_M 0x02 -#define CEC_OP_BCAST_SYSTEM_NTSC_M 0x03 -#define CEC_OP_BCAST_SYSTEM_PAL_I 0x04 -#define CEC_OP_BCAST_SYSTEM_SECAM_DK 0x05 -#define CEC_OP_BCAST_SYSTEM_SECAM_BG 0x06 -#define CEC_OP_BCAST_SYSTEM_SECAM_L 0x07 -#define CEC_OP_BCAST_SYSTEM_PAL_DK 0x08 -#define CEC_OP_BCAST_SYSTEM_OTHER 0x1f -/* Channel Number Format Operand (channel_number_fmt) */ -#define CEC_OP_CHANNEL_NUMBER_FMT_1_PART 0x01 -#define CEC_OP_CHANNEL_NUMBER_FMT_2_PART 0x02 - -#define CEC_MSG_RECORD_STATUS 0x0a -/* Record Status Operand (rec_status) */ -#define CEC_OP_RECORD_STATUS_CUR_SRC 0x01 -#define CEC_OP_RECORD_STATUS_DIG_SERVICE 0x02 -#define CEC_OP_RECORD_STATUS_ANA_SERVICE 0x03 -#define CEC_OP_RECORD_STATUS_EXT_INPUT 0x04 -#define CEC_OP_RECORD_STATUS_NO_DIG_SERVICE 0x05 -#define CEC_OP_RECORD_STATUS_NO_ANA_SERVICE 0x06 -#define CEC_OP_RECORD_STATUS_NO_SERVICE 0x07 -#define CEC_OP_RECORD_STATUS_INVALID_EXT_PLUG 0x09 -#define CEC_OP_RECORD_STATUS_INVALID_EXT_PHYS_ADDR 0x0a -#define CEC_OP_RECORD_STATUS_UNSUP_CA 0x0b -#define CEC_OP_RECORD_STATUS_NO_CA_ENTITLEMENTS 0x0c -#define CEC_OP_RECORD_STATUS_CANT_COPY_SRC 0x0d -#define CEC_OP_RECORD_STATUS_NO_MORE_COPIES 0x0e -#define CEC_OP_RECORD_STATUS_NO_MEDIA 0x10 -#define CEC_OP_RECORD_STATUS_PLAYING 0x11 -#define CEC_OP_RECORD_STATUS_ALREADY_RECORDING 0x12 -#define CEC_OP_RECORD_STATUS_MEDIA_PROT 0x13 -#define CEC_OP_RECORD_STATUS_NO_SIGNAL 0x14 -#define CEC_OP_RECORD_STATUS_MEDIA_PROBLEM 0x15 -#define CEC_OP_RECORD_STATUS_NO_SPACE 0x16 -#define CEC_OP_RECORD_STATUS_PARENTAL_LOCK 0x17 -#define CEC_OP_RECORD_STATUS_TERMINATED_OK 0x1a -#define CEC_OP_RECORD_STATUS_ALREADY_TERM 0x1b -#define CEC_OP_RECORD_STATUS_OTHER 0x1f - -#define CEC_MSG_RECORD_TV_SCREEN 0x0f - - -/* Timer Programming Feature */ -#define CEC_MSG_CLEAR_ANALOGUE_TIMER 0x33 -/* Recording Sequence Operand (recording_seq) */ -#define CEC_OP_REC_SEQ_SUNDAY 0x01 -#define CEC_OP_REC_SEQ_MONDAY 0x02 -#define CEC_OP_REC_SEQ_TUESDAY 0x04 -#define CEC_OP_REC_SEQ_WEDNESDAY 0x08 -#define CEC_OP_REC_SEQ_THURSDAY 0x10 -#define CEC_OP_REC_SEQ_FRIDAY 0x20 -#define CEC_OP_REC_SEQ_SATERDAY 0x40 -#define CEC_OP_REC_SEQ_ONCE_ONLY 0x00 - -#define CEC_MSG_CLEAR_DIGITAL_TIMER 0x99 - -#define CEC_MSG_CLEAR_EXT_TIMER 0xa1 -/* External Source Specifier Operand (ext_src_spec) */ -#define CEC_OP_EXT_SRC_PLUG 0x04 -#define CEC_OP_EXT_SRC_PHYS_ADDR 0x05 - -#define CEC_MSG_SET_ANALOGUE_TIMER 0x34 -#define CEC_MSG_SET_DIGITAL_TIMER 0x97 -#define CEC_MSG_SET_EXT_TIMER 0xa2 - -#define CEC_MSG_SET_TIMER_PROGRAM_TITLE 0x67 -#define CEC_MSG_TIMER_CLEARED_STATUS 0x43 -/* Timer Cleared Status Data Operand (timer_cleared_status) */ -#define CEC_OP_TIMER_CLR_STAT_RECORDING 0x00 -#define CEC_OP_TIMER_CLR_STAT_NO_MATCHING 0x01 -#define CEC_OP_TIMER_CLR_STAT_NO_INFO 0x02 -#define CEC_OP_TIMER_CLR_STAT_CLEARED 0x80 - -#define CEC_MSG_TIMER_STATUS 0x35 -/* Timer Overlap Warning Operand (timer_overlap_warning) */ -#define CEC_OP_TIMER_OVERLAP_WARNING_NO_OVERLAP 0 -#define CEC_OP_TIMER_OVERLAP_WARNING_OVERLAP 1 -/* Media Info Operand (media_info) */ -#define CEC_OP_MEDIA_INFO_UNPROT_MEDIA 0 -#define CEC_OP_MEDIA_INFO_PROT_MEDIA 1 -#define CEC_OP_MEDIA_INFO_NO_MEDIA 2 -/* Programmed Indicator Operand (prog_indicator) */ -#define CEC_OP_PROG_IND_NOT_PROGRAMMED 0 -#define CEC_OP_PROG_IND_PROGRAMMED 1 -/* Programmed Info Operand (prog_info) */ -#define CEC_OP_PROG_INFO_ENOUGH_SPACE 0x08 -#define CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE 0x09 -#define CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE 0x0b -#define CEC_OP_PROG_INFO_NONE_AVAILABLE 0x0a -/* Not Programmed Error Info Operand (prog_error) */ -#define CEC_OP_PROG_ERROR_NO_FREE_TIMER 0x01 -#define CEC_OP_PROG_ERROR_DATE_OUT_OF_RANGE 0x02 -#define CEC_OP_PROG_ERROR_REC_SEQ_ERROR 0x03 -#define CEC_OP_PROG_ERROR_INV_EXT_PLUG 0x04 -#define CEC_OP_PROG_ERROR_INV_EXT_PHYS_ADDR 0x05 -#define CEC_OP_PROG_ERROR_CA_UNSUPP 0x06 -#define CEC_OP_PROG_ERROR_INSUF_CA_ENTITLEMENTS 0x07 -#define CEC_OP_PROG_ERROR_RESOLUTION_UNSUPP 0x08 -#define CEC_OP_PROG_ERROR_PARENTAL_LOCK 0x09 -#define CEC_OP_PROG_ERROR_CLOCK_FAILURE 0x0a -#define CEC_OP_PROG_ERROR_DUPLICATE 0x0e - - -/* System Information Feature */ -#define CEC_MSG_CEC_VERSION 0x9e -/* CEC Version Operand (cec_version) */ -#define CEC_OP_CEC_VERSION_1_3A 4 -#define CEC_OP_CEC_VERSION_1_4 5 -#define CEC_OP_CEC_VERSION_2_0 6 - -#define CEC_MSG_GET_CEC_VERSION 0x9f -#define CEC_MSG_GIVE_PHYSICAL_ADDR 0x83 -#define CEC_MSG_GET_MENU_LANGUAGE 0x91 -#define CEC_MSG_REPORT_PHYSICAL_ADDR 0x84 -/* Primary Device Type Operand (prim_devtype) */ -#define CEC_OP_PRIM_DEVTYPE_TV 0 -#define CEC_OP_PRIM_DEVTYPE_RECORD 1 -#define CEC_OP_PRIM_DEVTYPE_TUNER 3 -#define CEC_OP_PRIM_DEVTYPE_PLAYBACK 4 -#define CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM 5 -#define CEC_OP_PRIM_DEVTYPE_SWITCH 6 -#define CEC_OP_PRIM_DEVTYPE_PROCESSOR 7 - -#define CEC_MSG_SET_MENU_LANGUAGE 0x32 -#define CEC_MSG_REPORT_FEATURES 0xa6 /* HDMI 2.0 */ -/* All Device Types Operand (all_device_types) */ -#define CEC_OP_ALL_DEVTYPE_TV 0x80 -#define CEC_OP_ALL_DEVTYPE_RECORD 0x40 -#define CEC_OP_ALL_DEVTYPE_TUNER 0x20 -#define CEC_OP_ALL_DEVTYPE_PLAYBACK 0x10 -#define CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM 0x08 -#define CEC_OP_ALL_DEVTYPE_SWITCH 0x04 -/* - * And if you wondering what happened to PROCESSOR devices: those should - * be mapped to a SWITCH. - */ - -/* Valid for RC Profile and Device Feature operands */ -#define CEC_OP_FEAT_EXT 0x80 /* Extension bit */ -/* RC Profile Operand (rc_profile) */ -#define CEC_OP_FEAT_RC_TV_PROFILE_NONE 0x00 -#define CEC_OP_FEAT_RC_TV_PROFILE_1 0x02 -#define CEC_OP_FEAT_RC_TV_PROFILE_2 0x06 -#define CEC_OP_FEAT_RC_TV_PROFILE_3 0x0a -#define CEC_OP_FEAT_RC_TV_PROFILE_4 0x0e -#define CEC_OP_FEAT_RC_SRC_HAS_DEV_ROOT_MENU 0x50 -#define CEC_OP_FEAT_RC_SRC_HAS_DEV_SETUP_MENU 0x48 -#define CEC_OP_FEAT_RC_SRC_HAS_CONTENTS_MENU 0x44 -#define CEC_OP_FEAT_RC_SRC_HAS_MEDIA_TOP_MENU 0x42 -#define CEC_OP_FEAT_RC_SRC_HAS_MEDIA_CONTEXT_MENU 0x41 -/* Device Feature Operand (dev_features) */ -#define CEC_OP_FEAT_DEV_HAS_RECORD_TV_SCREEN 0x40 -#define CEC_OP_FEAT_DEV_HAS_SET_OSD_STRING 0x20 -#define CEC_OP_FEAT_DEV_HAS_DECK_CONTROL 0x10 -#define CEC_OP_FEAT_DEV_HAS_SET_AUDIO_RATE 0x08 -#define CEC_OP_FEAT_DEV_SINK_HAS_ARC_TX 0x04 -#define CEC_OP_FEAT_DEV_SOURCE_HAS_ARC_RX 0x02 - -#define CEC_MSG_GIVE_FEATURES 0xa5 /* HDMI 2.0 */ - - -/* Deck Control Feature */ -#define CEC_MSG_DECK_CONTROL 0x42 -/* Deck Control Mode Operand (deck_control_mode) */ -#define CEC_OP_DECK_CTL_MODE_SKIP_FWD 1 -#define CEC_OP_DECK_CTL_MODE_SKIP_REV 2 -#define CEC_OP_DECK_CTL_MODE_STOP 3 -#define CEC_OP_DECK_CTL_MODE_EJECT 4 - -#define CEC_MSG_DECK_STATUS 0x1b -/* Deck Info Operand (deck_info) */ -#define CEC_OP_DECK_INFO_PLAY 0x11 -#define CEC_OP_DECK_INFO_RECORD 0x12 -#define CEC_OP_DECK_INFO_PLAY_REV 0x13 -#define CEC_OP_DECK_INFO_STILL 0x14 -#define CEC_OP_DECK_INFO_SLOW 0x15 -#define CEC_OP_DECK_INFO_SLOW_REV 0x16 -#define CEC_OP_DECK_INFO_FAST_FWD 0x17 -#define CEC_OP_DECK_INFO_FAST_REV 0x18 -#define CEC_OP_DECK_INFO_NO_MEDIA 0x19 -#define CEC_OP_DECK_INFO_STOP 0x1a -#define CEC_OP_DECK_INFO_SKIP_FWD 0x1b -#define CEC_OP_DECK_INFO_SKIP_REV 0x1c -#define CEC_OP_DECK_INFO_INDEX_SEARCH_FWD 0x1d -#define CEC_OP_DECK_INFO_INDEX_SEARCH_REV 0x1e -#define CEC_OP_DECK_INFO_OTHER 0x1f - -#define CEC_MSG_GIVE_DECK_STATUS 0x1a -/* Status Request Operand (status_req) */ -#define CEC_OP_STATUS_REQ_ON 1 -#define CEC_OP_STATUS_REQ_OFF 2 -#define CEC_OP_STATUS_REQ_ONCE 3 - -#define CEC_MSG_PLAY 0x41 -/* Play Mode Operand (play_mode) */ -#define CEC_OP_PLAY_MODE_PLAY_FWD 0x24 -#define CEC_OP_PLAY_MODE_PLAY_REV 0x20 -#define CEC_OP_PLAY_MODE_PLAY_STILL 0x25 -#define CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MIN 0x05 -#define CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MED 0x06 -#define CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MAX 0x07 -#define CEC_OP_PLAY_MODE_PLAY_FAST_REV_MIN 0x09 -#define CEC_OP_PLAY_MODE_PLAY_FAST_REV_MED 0x0a -#define CEC_OP_PLAY_MODE_PLAY_FAST_REV_MAX 0x0b -#define CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MIN 0x15 -#define CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MED 0x16 -#define CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MAX 0x17 -#define CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MIN 0x19 -#define CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MED 0x1a -#define CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MAX 0x1b - - -/* Tuner Control Feature */ -#define CEC_MSG_GIVE_TUNER_DEVICE_STATUS 0x08 -#define CEC_MSG_SELECT_ANALOGUE_SERVICE 0x92 -#define CEC_MSG_SELECT_DIGITAL_SERVICE 0x93 -#define CEC_MSG_TUNER_DEVICE_STATUS 0x07 -/* Recording Flag Operand (rec_flag) */ -#define CEC_OP_REC_FLAG_USED 0 -#define CEC_OP_REC_FLAG_NOT_USED 1 -/* Tuner Display Info Operand (tuner_display_info) */ -#define CEC_OP_TUNER_DISPLAY_INFO_DIGITAL 0 -#define CEC_OP_TUNER_DISPLAY_INFO_NONE 1 -#define CEC_OP_TUNER_DISPLAY_INFO_ANALOGUE 2 - -#define CEC_MSG_TUNER_STEP_DECREMENT 0x06 -#define CEC_MSG_TUNER_STEP_INCREMENT 0x05 - - -/* Vendor Specific Commands Feature */ - -/* - * Has also: - * CEC_MSG_CEC_VERSION - * CEC_MSG_GET_CEC_VERSION - */ -#define CEC_MSG_DEVICE_VENDOR_ID 0x87 -#define CEC_MSG_GIVE_DEVICE_VENDOR_ID 0x8c -#define CEC_MSG_VENDOR_COMMAND 0x89 -#define CEC_MSG_VENDOR_COMMAND_WITH_ID 0xa0 -#define CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN 0x8a -#define CEC_MSG_VENDOR_REMOTE_BUTTON_UP 0x8b - - -/* OSD Display Feature */ -#define CEC_MSG_SET_OSD_STRING 0x64 -/* Display Control Operand (disp_ctl) */ -#define CEC_OP_DISP_CTL_DEFAULT 0x00 -#define CEC_OP_DISP_CTL_UNTIL_CLEARED 0x40 -#define CEC_OP_DISP_CTL_CLEAR 0x80 - - -/* Device OSD Transfer Feature */ -#define CEC_MSG_GIVE_OSD_NAME 0x46 -#define CEC_MSG_SET_OSD_NAME 0x47 - - -/* Device Menu Control Feature */ -#define CEC_MSG_MENU_REQUEST 0x8d -/* Menu Request Type Operand (menu_req) */ -#define CEC_OP_MENU_REQUEST_ACTIVATE 0x00 -#define CEC_OP_MENU_REQUEST_DEACTIVATE 0x01 -#define CEC_OP_MENU_REQUEST_QUERY 0x02 - -#define CEC_MSG_MENU_STATUS 0x8e -/* Menu State Operand (menu_state) */ -#define CEC_OP_MENU_STATE_ACTIVATED 0x00 -#define CEC_OP_MENU_STATE_DEACTIVATED 0x01 - -#define CEC_MSG_USER_CONTROL_PRESSED 0x44 -/* UI Broadcast Type Operand (ui_bcast_type) */ -#define CEC_OP_UI_BCAST_TYPE_TOGGLE_ALL 0x00 -#define CEC_OP_UI_BCAST_TYPE_TOGGLE_DIG_ANA 0x01 -#define CEC_OP_UI_BCAST_TYPE_ANALOGUE 0x10 -#define CEC_OP_UI_BCAST_TYPE_ANALOGUE_T 0x20 -#define CEC_OP_UI_BCAST_TYPE_ANALOGUE_CABLE 0x30 -#define CEC_OP_UI_BCAST_TYPE_ANALOGUE_SAT 0x40 -#define CEC_OP_UI_BCAST_TYPE_DIGITAL 0x50 -#define CEC_OP_UI_BCAST_TYPE_DIGITAL_T 0x60 -#define CEC_OP_UI_BCAST_TYPE_DIGITAL_CABLE 0x70 -#define CEC_OP_UI_BCAST_TYPE_DIGITAL_SAT 0x80 -#define CEC_OP_UI_BCAST_TYPE_DIGITAL_COM_SAT 0x90 -#define CEC_OP_UI_BCAST_TYPE_DIGITAL_COM_SAT2 0x91 -#define CEC_OP_UI_BCAST_TYPE_IP 0xa0 -/* UI Sound Presentation Control Operand (ui_snd_pres_ctl) */ -#define CEC_OP_UI_SND_PRES_CTL_DUAL_MONO 0x10 -#define CEC_OP_UI_SND_PRES_CTL_KARAOKE 0x20 -#define CEC_OP_UI_SND_PRES_CTL_DOWNMIX 0x80 -#define CEC_OP_UI_SND_PRES_CTL_REVERB 0x90 -#define CEC_OP_UI_SND_PRES_CTL_EQUALIZER 0xa0 -#define CEC_OP_UI_SND_PRES_CTL_BASS_UP 0xb1 -#define CEC_OP_UI_SND_PRES_CTL_BASS_NEUTRAL 0xb2 -#define CEC_OP_UI_SND_PRES_CTL_BASS_DOWN 0xb3 -#define CEC_OP_UI_SND_PRES_CTL_TREBLE_UP 0xc1 -#define CEC_OP_UI_SND_PRES_CTL_TREBLE_NEUTRAL 0xc2 -#define CEC_OP_UI_SND_PRES_CTL_TREBLE_DOWN 0xc3 - -#define CEC_MSG_USER_CONTROL_RELEASED 0x45 - - -/* Remote Control Passthrough Feature */ - -/* - * Has also: - * CEC_MSG_USER_CONTROL_PRESSED - * CEC_MSG_USER_CONTROL_RELEASED - */ - - -/* Power Status Feature */ -#define CEC_MSG_GIVE_DEVICE_POWER_STATUS 0x8f -#define CEC_MSG_REPORT_POWER_STATUS 0x90 -/* Power Status Operand (pwr_state) */ -#define CEC_OP_POWER_STATUS_ON 0 -#define CEC_OP_POWER_STATUS_STANDBY 1 -#define CEC_OP_POWER_STATUS_TO_ON 2 -#define CEC_OP_POWER_STATUS_TO_STANDBY 3 - - -/* General Protocol Messages */ -#define CEC_MSG_FEATURE_ABORT 0x00 -/* Abort Reason Operand (reason) */ -#define CEC_OP_ABORT_UNRECOGNIZED_OP 0 -#define CEC_OP_ABORT_INCORRECT_MODE 1 -#define CEC_OP_ABORT_NO_SOURCE 2 -#define CEC_OP_ABORT_INVALID_OP 3 -#define CEC_OP_ABORT_REFUSED 4 -#define CEC_OP_ABORT_UNDETERMINED 5 - -#define CEC_MSG_ABORT 0xff - - -/* System Audio Control Feature */ - -/* - * Has also: - * CEC_MSG_USER_CONTROL_PRESSED - * CEC_MSG_USER_CONTROL_RELEASED - */ -#define CEC_MSG_GIVE_AUDIO_STATUS 0x71 -#define CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS 0x7d -#define CEC_MSG_REPORT_AUDIO_STATUS 0x7a -/* Audio Mute Status Operand (aud_mute_status) */ -#define CEC_OP_AUD_MUTE_STATUS_OFF 0 -#define CEC_OP_AUD_MUTE_STATUS_ON 1 - -#define CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR 0xa3 -#define CEC_MSG_REQUEST_SHORT_AUDIO_DESCRIPTOR 0xa4 -#define CEC_MSG_SET_SYSTEM_AUDIO_MODE 0x72 -/* System Audio Status Operand (sys_aud_status) */ -#define CEC_OP_SYS_AUD_STATUS_OFF 0 -#define CEC_OP_SYS_AUD_STATUS_ON 1 - -#define CEC_MSG_SYSTEM_AUDIO_MODE_REQUEST 0x70 -#define CEC_MSG_SYSTEM_AUDIO_MODE_STATUS 0x7e -/* Audio Format ID Operand (audio_format_id) */ -#define CEC_OP_AUD_FMT_ID_CEA861 0 -#define CEC_OP_AUD_FMT_ID_CEA861_CXT 1 - - -/* Audio Rate Control Feature */ -#define CEC_MSG_SET_AUDIO_RATE 0x9a -/* Audio Rate Operand (audio_rate) */ -#define CEC_OP_AUD_RATE_OFF 0 -#define CEC_OP_AUD_RATE_WIDE_STD 1 -#define CEC_OP_AUD_RATE_WIDE_FAST 2 -#define CEC_OP_AUD_RATE_WIDE_SLOW 3 -#define CEC_OP_AUD_RATE_NARROW_STD 4 -#define CEC_OP_AUD_RATE_NARROW_FAST 5 -#define CEC_OP_AUD_RATE_NARROW_SLOW 6 - - -/* Audio Return Channel Control Feature */ -#define CEC_MSG_INITIATE_ARC 0xc0 -#define CEC_MSG_REPORT_ARC_INITIATED 0xc1 -#define CEC_MSG_REPORT_ARC_TERMINATED 0xc2 -#define CEC_MSG_REQUEST_ARC_INITIATION 0xc3 -#define CEC_MSG_REQUEST_ARC_TERMINATION 0xc4 -#define CEC_MSG_TERMINATE_ARC 0xc5 - - -/* Dynamic Audio Lipsync Feature */ -/* Only for CEC 2.0 and up */ -#define CEC_MSG_REQUEST_CURRENT_LATENCY 0xa7 -#define CEC_MSG_REPORT_CURRENT_LATENCY 0xa8 -/* Low Latency Mode Operand (low_latency_mode) */ -#define CEC_OP_LOW_LATENCY_MODE_OFF 0 -#define CEC_OP_LOW_LATENCY_MODE_ON 1 -/* Audio Output Compensated Operand (audio_out_compensated) */ -#define CEC_OP_AUD_OUT_COMPENSATED_NA 0 -#define CEC_OP_AUD_OUT_COMPENSATED_DELAY 1 -#define CEC_OP_AUD_OUT_COMPENSATED_NO_DELAY 2 -#define CEC_OP_AUD_OUT_COMPENSATED_PARTIAL_DELAY 3 - - -/* Capability Discovery and Control Feature */ -#define CEC_MSG_CDC_MESSAGE 0xf8 -/* Ethernet-over-HDMI: nobody ever does this... */ -#define CEC_MSG_CDC_HEC_INQUIRE_STATE 0x00 -#define CEC_MSG_CDC_HEC_REPORT_STATE 0x01 -/* HEC Functionality State Operand (hec_func_state) */ -#define CEC_OP_HEC_FUNC_STATE_NOT_SUPPORTED 0 -#define CEC_OP_HEC_FUNC_STATE_INACTIVE 1 -#define CEC_OP_HEC_FUNC_STATE_ACTIVE 2 -#define CEC_OP_HEC_FUNC_STATE_ACTIVATION_FIELD 3 -/* Host Functionality State Operand (host_func_state) */ -#define CEC_OP_HOST_FUNC_STATE_NOT_SUPPORTED 0 -#define CEC_OP_HOST_FUNC_STATE_INACTIVE 1 -#define CEC_OP_HOST_FUNC_STATE_ACTIVE 2 -/* ENC Functionality State Operand (enc_func_state) */ -#define CEC_OP_ENC_FUNC_STATE_EXT_CON_NOT_SUPPORTED 0 -#define CEC_OP_ENC_FUNC_STATE_EXT_CON_INACTIVE 1 -#define CEC_OP_ENC_FUNC_STATE_EXT_CON_ACTIVE 2 -/* CDC Error Code Operand (cdc_errcode) */ -#define CEC_OP_CDC_ERROR_CODE_NONE 0 -#define CEC_OP_CDC_ERROR_CODE_CAP_UNSUPPORTED 1 -#define CEC_OP_CDC_ERROR_CODE_WRONG_STATE 2 -#define CEC_OP_CDC_ERROR_CODE_OTHER 3 -/* HEC Support Operand (hec_support) */ -#define CEC_OP_HEC_SUPPORT_NO 0 -#define CEC_OP_HEC_SUPPORT_YES 1 -/* HEC Activation Operand (hec_activation) */ -#define CEC_OP_HEC_ACTIVATION_ON 0 -#define CEC_OP_HEC_ACTIVATION_OFF 1 - -#define CEC_MSG_CDC_HEC_SET_STATE_ADJACENT 0x02 -#define CEC_MSG_CDC_HEC_SET_STATE 0x03 -/* HEC Set State Operand (hec_set_state) */ -#define CEC_OP_HEC_SET_STATE_DEACTIVATE 0 -#define CEC_OP_HEC_SET_STATE_ACTIVATE 1 - -#define CEC_MSG_CDC_HEC_REQUEST_DEACTIVATION 0x04 -#define CEC_MSG_CDC_HEC_NOTIFY_ALIVE 0x05 -#define CEC_MSG_CDC_HEC_DISCOVER 0x06 -/* Hotplug Detect messages */ -#define CEC_MSG_CDC_HPD_SET_STATE 0x10 -/* HPD State Operand (hpd_state) */ -#define CEC_OP_HPD_STATE_CP_EDID_DISABLE 0 -#define CEC_OP_HPD_STATE_CP_EDID_ENABLE 1 -#define CEC_OP_HPD_STATE_CP_EDID_DISABLE_ENABLE 2 -#define CEC_OP_HPD_STATE_EDID_DISABLE 3 -#define CEC_OP_HPD_STATE_EDID_ENABLE 4 -#define CEC_OP_HPD_STATE_EDID_DISABLE_ENABLE 5 -#define CEC_MSG_CDC_HPD_REPORT_STATE 0x11 -/* HPD Error Code Operand (hpd_error) */ -#define CEC_OP_HPD_ERROR_NONE 0 -#define CEC_OP_HPD_ERROR_INITIATOR_NOT_CAPABLE 1 -#define CEC_OP_HPD_ERROR_INITIATOR_WRONG_STATE 2 -#define CEC_OP_HPD_ERROR_OTHER 3 -#define CEC_OP_HPD_ERROR_NONE_NO_VIDEO 4 - -/* End of Messages */ - -/* Helper functions to identify the 'special' CEC devices */ - -static inline bool cec_is_2nd_tv(const struct cec_log_addrs *las) -{ - /* - * It is a second TV if the logical address is 14 or 15 and the - * primary device type is a TV. - */ - return las->num_log_addrs && - las->log_addr[0] >= CEC_LOG_ADDR_SPECIFIC && - las->primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_TV; -} - -static inline bool cec_is_processor(const struct cec_log_addrs *las) -{ - /* - * It is a processor if the logical address is 12-15 and the - * primary device type is a Processor. - */ - return las->num_log_addrs && - las->log_addr[0] >= CEC_LOG_ADDR_BACKUP_1 && - las->primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_PROCESSOR; -} - -static inline bool cec_is_switch(const struct cec_log_addrs *las) -{ - /* - * It is a switch if the logical address is 15 and the - * primary device type is a Switch and the CDC-Only flag is not set. - */ - return las->num_log_addrs == 1 && - las->log_addr[0] == CEC_LOG_ADDR_UNREGISTERED && - las->primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_SWITCH && - !(las->flags & CEC_LOG_ADDRS_FL_CDC_ONLY); -} - -static inline bool cec_is_cdc_only(const struct cec_log_addrs *las) -{ - /* - * It is a CDC-only device if the logical address is 15 and the - * primary device type is a Switch and the CDC-Only flag is set. - */ - return las->num_log_addrs == 1 && - las->log_addr[0] == CEC_LOG_ADDR_UNREGISTERED && - las->primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_SWITCH && - (las->flags & CEC_LOG_ADDRS_FL_CDC_ONLY); -} - -#endif diff --git a/include/media/cec.h b/include/media/cec.h index fdb5d600e4bb..717eaf552f3d 100644 --- a/include/media/cec.h +++ b/include/media/cec.h @@ -196,7 +196,7 @@ static inline bool cec_is_sink(const struct cec_adapter *adap) return adap->phys_addr == 0; } -#if IS_ENABLED(CONFIG_MEDIA_CEC) +#if IS_ENABLED(CONFIG_MEDIA_CEC_SUPPORT) struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops, void *priv, const char *name, u32 caps, u8 available_las, struct device *parent); diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index 6965d0909554..c49c448cff92 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -82,6 +82,8 @@ header-y += capi.h header-y += cciss_defs.h header-y += cciss_ioctl.h header-y += cdrom.h +header-y += cec.h +header-y += cec-funcs.h header-y += cgroupstats.h header-y += chio.h header-y += cm4000_cs.h diff --git a/include/uapi/linux/cec-funcs.h b/include/uapi/linux/cec-funcs.h new file mode 100644 index 000000000000..1a1de2169f48 --- /dev/null +++ b/include/uapi/linux/cec-funcs.h @@ -0,0 +1,1965 @@ +/* + * cec - HDMI Consumer Electronics Control message functions + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * Alternatively you can redistribute this file under the terms of the + * BSD license as stated below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. The names of its contributors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _CEC_UAPI_FUNCS_H +#define _CEC_UAPI_FUNCS_H + +#include + +/* One Touch Play Feature */ +static inline void cec_msg_active_source(struct cec_msg *msg, __u16 phys_addr) +{ + msg->len = 4; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_ACTIVE_SOURCE; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; +} + +static inline void cec_ops_active_source(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_image_view_on(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_IMAGE_VIEW_ON; +} + +static inline void cec_msg_text_view_on(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_TEXT_VIEW_ON; +} + + +/* Routing Control Feature */ +static inline void cec_msg_inactive_source(struct cec_msg *msg, + __u16 phys_addr) +{ + msg->len = 4; + msg->msg[1] = CEC_MSG_INACTIVE_SOURCE; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; +} + +static inline void cec_ops_inactive_source(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_request_active_source(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_REQUEST_ACTIVE_SOURCE; + msg->reply = reply ? CEC_MSG_ACTIVE_SOURCE : 0; +} + +static inline void cec_msg_routing_information(struct cec_msg *msg, + __u16 phys_addr) +{ + msg->len = 4; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_ROUTING_INFORMATION; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; +} + +static inline void cec_ops_routing_information(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_routing_change(struct cec_msg *msg, + bool reply, + __u16 orig_phys_addr, + __u16 new_phys_addr) +{ + msg->len = 6; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_ROUTING_CHANGE; + msg->msg[2] = orig_phys_addr >> 8; + msg->msg[3] = orig_phys_addr & 0xff; + msg->msg[4] = new_phys_addr >> 8; + msg->msg[5] = new_phys_addr & 0xff; + msg->reply = reply ? CEC_MSG_ROUTING_INFORMATION : 0; +} + +static inline void cec_ops_routing_change(const struct cec_msg *msg, + __u16 *orig_phys_addr, + __u16 *new_phys_addr) +{ + *orig_phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *new_phys_addr = (msg->msg[4] << 8) | msg->msg[5]; +} + +static inline void cec_msg_set_stream_path(struct cec_msg *msg, __u16 phys_addr) +{ + msg->len = 4; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_SET_STREAM_PATH; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; +} + +static inline void cec_ops_set_stream_path(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + + +/* Standby Feature */ +static inline void cec_msg_standby(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_STANDBY; +} + + +/* One Touch Record Feature */ +static inline void cec_msg_record_off(struct cec_msg *msg, bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_RECORD_OFF; + msg->reply = reply ? CEC_MSG_RECORD_STATUS : 0; +} + +struct cec_op_arib_data { + __u16 transport_id; + __u16 service_id; + __u16 orig_network_id; +}; + +struct cec_op_atsc_data { + __u16 transport_id; + __u16 program_number; +}; + +struct cec_op_dvb_data { + __u16 transport_id; + __u16 service_id; + __u16 orig_network_id; +}; + +struct cec_op_channel_data { + __u8 channel_number_fmt; + __u16 major; + __u16 minor; +}; + +struct cec_op_digital_service_id { + __u8 service_id_method; + __u8 dig_bcast_system; + union { + struct cec_op_arib_data arib; + struct cec_op_atsc_data atsc; + struct cec_op_dvb_data dvb; + struct cec_op_channel_data channel; + }; +}; + +struct cec_op_record_src { + __u8 type; + union { + struct cec_op_digital_service_id digital; + struct { + __u8 ana_bcast_type; + __u16 ana_freq; + __u8 bcast_system; + } analog; + struct { + __u8 plug; + } ext_plug; + struct { + __u16 phys_addr; + } ext_phys_addr; + }; +}; + +static inline void cec_set_digital_service_id(__u8 *msg, + const struct cec_op_digital_service_id *digital) +{ + *msg++ = (digital->service_id_method << 7) | digital->dig_bcast_system; + if (digital->service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) { + *msg++ = (digital->channel.channel_number_fmt << 2) | + (digital->channel.major >> 8); + *msg++ = digital->channel.major & 0xff; + *msg++ = digital->channel.minor >> 8; + *msg++ = digital->channel.minor & 0xff; + *msg++ = 0; + *msg++ = 0; + return; + } + switch (digital->dig_bcast_system) { + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: + *msg++ = digital->atsc.transport_id >> 8; + *msg++ = digital->atsc.transport_id & 0xff; + *msg++ = digital->atsc.program_number >> 8; + *msg++ = digital->atsc.program_number & 0xff; + *msg++ = 0; + *msg++ = 0; + break; + default: + *msg++ = digital->dvb.transport_id >> 8; + *msg++ = digital->dvb.transport_id & 0xff; + *msg++ = digital->dvb.service_id >> 8; + *msg++ = digital->dvb.service_id & 0xff; + *msg++ = digital->dvb.orig_network_id >> 8; + *msg++ = digital->dvb.orig_network_id & 0xff; + break; + } +} + +static inline void cec_get_digital_service_id(const __u8 *msg, + struct cec_op_digital_service_id *digital) +{ + digital->service_id_method = msg[0] >> 7; + digital->dig_bcast_system = msg[0] & 0x7f; + if (digital->service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) { + digital->channel.channel_number_fmt = msg[1] >> 2; + digital->channel.major = ((msg[1] & 3) << 6) | msg[2]; + digital->channel.minor = (msg[3] << 8) | msg[4]; + return; + } + digital->dvb.transport_id = (msg[1] << 8) | msg[2]; + digital->dvb.service_id = (msg[3] << 8) | msg[4]; + digital->dvb.orig_network_id = (msg[5] << 8) | msg[6]; +} + +static inline void cec_msg_record_on_own(struct cec_msg *msg) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_RECORD_ON; + msg->msg[2] = CEC_OP_RECORD_SRC_OWN; +} + +static inline void cec_msg_record_on_digital(struct cec_msg *msg, + const struct cec_op_digital_service_id *digital) +{ + msg->len = 10; + msg->msg[1] = CEC_MSG_RECORD_ON; + msg->msg[2] = CEC_OP_RECORD_SRC_DIGITAL; + cec_set_digital_service_id(msg->msg + 3, digital); +} + +static inline void cec_msg_record_on_analog(struct cec_msg *msg, + __u8 ana_bcast_type, + __u16 ana_freq, + __u8 bcast_system) +{ + msg->len = 7; + msg->msg[1] = CEC_MSG_RECORD_ON; + msg->msg[2] = CEC_OP_RECORD_SRC_ANALOG; + msg->msg[3] = ana_bcast_type; + msg->msg[4] = ana_freq >> 8; + msg->msg[5] = ana_freq & 0xff; + msg->msg[6] = bcast_system; +} + +static inline void cec_msg_record_on_plug(struct cec_msg *msg, + __u8 plug) +{ + msg->len = 4; + msg->msg[1] = CEC_MSG_RECORD_ON; + msg->msg[2] = CEC_OP_RECORD_SRC_EXT_PLUG; + msg->msg[3] = plug; +} + +static inline void cec_msg_record_on_phys_addr(struct cec_msg *msg, + __u16 phys_addr) +{ + msg->len = 5; + msg->msg[1] = CEC_MSG_RECORD_ON; + msg->msg[2] = CEC_OP_RECORD_SRC_EXT_PHYS_ADDR; + msg->msg[3] = phys_addr >> 8; + msg->msg[4] = phys_addr & 0xff; +} + +static inline void cec_msg_record_on(struct cec_msg *msg, + bool reply, + const struct cec_op_record_src *rec_src) +{ + switch (rec_src->type) { + case CEC_OP_RECORD_SRC_OWN: + cec_msg_record_on_own(msg); + break; + case CEC_OP_RECORD_SRC_DIGITAL: + cec_msg_record_on_digital(msg, &rec_src->digital); + break; + case CEC_OP_RECORD_SRC_ANALOG: + cec_msg_record_on_analog(msg, + rec_src->analog.ana_bcast_type, + rec_src->analog.ana_freq, + rec_src->analog.bcast_system); + break; + case CEC_OP_RECORD_SRC_EXT_PLUG: + cec_msg_record_on_plug(msg, rec_src->ext_plug.plug); + break; + case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR: + cec_msg_record_on_phys_addr(msg, + rec_src->ext_phys_addr.phys_addr); + break; + } + msg->reply = reply ? CEC_MSG_RECORD_STATUS : 0; +} + +static inline void cec_ops_record_on(const struct cec_msg *msg, + struct cec_op_record_src *rec_src) +{ + rec_src->type = msg->msg[2]; + switch (rec_src->type) { + case CEC_OP_RECORD_SRC_OWN: + break; + case CEC_OP_RECORD_SRC_DIGITAL: + cec_get_digital_service_id(msg->msg + 3, &rec_src->digital); + break; + case CEC_OP_RECORD_SRC_ANALOG: + rec_src->analog.ana_bcast_type = msg->msg[3]; + rec_src->analog.ana_freq = + (msg->msg[4] << 8) | msg->msg[5]; + rec_src->analog.bcast_system = msg->msg[6]; + break; + case CEC_OP_RECORD_SRC_EXT_PLUG: + rec_src->ext_plug.plug = msg->msg[3]; + break; + case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR: + rec_src->ext_phys_addr.phys_addr = + (msg->msg[3] << 8) | msg->msg[4]; + break; + } +} + +static inline void cec_msg_record_status(struct cec_msg *msg, __u8 rec_status) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_RECORD_STATUS; + msg->msg[2] = rec_status; +} + +static inline void cec_ops_record_status(const struct cec_msg *msg, + __u8 *rec_status) +{ + *rec_status = msg->msg[2]; +} + +static inline void cec_msg_record_tv_screen(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_RECORD_TV_SCREEN; + msg->reply = reply ? CEC_MSG_RECORD_ON : 0; +} + + +/* Timer Programming Feature */ +static inline void cec_msg_timer_status(struct cec_msg *msg, + __u8 timer_overlap_warning, + __u8 media_info, + __u8 prog_info, + __u8 prog_error, + __u8 duration_hr, + __u8 duration_min) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_TIMER_STATUS; + msg->msg[2] = (timer_overlap_warning << 7) | + (media_info << 5) | + (prog_info ? 0x10 : 0) | + (prog_info ? prog_info : prog_error); + if (prog_info == CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE || + prog_info == CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE || + prog_error == CEC_OP_PROG_ERROR_DUPLICATE) { + msg->len += 2; + msg->msg[3] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[4] = ((duration_min / 10) << 4) | (duration_min % 10); + } +} + +static inline void cec_ops_timer_status(const struct cec_msg *msg, + __u8 *timer_overlap_warning, + __u8 *media_info, + __u8 *prog_info, + __u8 *prog_error, + __u8 *duration_hr, + __u8 *duration_min) +{ + *timer_overlap_warning = msg->msg[2] >> 7; + *media_info = (msg->msg[2] >> 5) & 3; + if (msg->msg[2] & 0x10) { + *prog_info = msg->msg[2] & 0xf; + *prog_error = 0; + } else { + *prog_info = 0; + *prog_error = msg->msg[2] & 0xf; + } + if (*prog_info == CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE || + *prog_info == CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE || + *prog_error == CEC_OP_PROG_ERROR_DUPLICATE) { + *duration_hr = (msg->msg[3] >> 4) * 10 + (msg->msg[3] & 0xf); + *duration_min = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + } else { + *duration_hr = *duration_min = 0; + } +} + +static inline void cec_msg_timer_cleared_status(struct cec_msg *msg, + __u8 timer_cleared_status) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_TIMER_CLEARED_STATUS; + msg->msg[2] = timer_cleared_status; +} + +static inline void cec_ops_timer_cleared_status(const struct cec_msg *msg, + __u8 *timer_cleared_status) +{ + *timer_cleared_status = msg->msg[2]; +} + +static inline void cec_msg_clear_analogue_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + __u8 ana_bcast_type, + __u16 ana_freq, + __u8 bcast_system) +{ + msg->len = 13; + msg->msg[1] = CEC_MSG_CLEAR_ANALOGUE_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + msg->msg[9] = ana_bcast_type; + msg->msg[10] = ana_freq >> 8; + msg->msg[11] = ana_freq & 0xff; + msg->msg[12] = bcast_system; + msg->reply = reply ? CEC_MSG_TIMER_CLEARED_STATUS : 0; +} + +static inline void cec_ops_clear_analogue_timer(const struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + __u8 *ana_bcast_type, + __u16 *ana_freq, + __u8 *bcast_system) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + *ana_bcast_type = msg->msg[9]; + *ana_freq = (msg->msg[10] << 8) | msg->msg[11]; + *bcast_system = msg->msg[12]; +} + +static inline void cec_msg_clear_digital_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + const struct cec_op_digital_service_id *digital) +{ + msg->len = 16; + msg->reply = reply ? CEC_MSG_TIMER_CLEARED_STATUS : 0; + msg->msg[1] = CEC_MSG_CLEAR_DIGITAL_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + cec_set_digital_service_id(msg->msg + 9, digital); +} + +static inline void cec_ops_clear_digital_timer(const struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + struct cec_op_digital_service_id *digital) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + cec_get_digital_service_id(msg->msg + 9, digital); +} + +static inline void cec_msg_clear_ext_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + __u8 ext_src_spec, + __u8 plug, + __u16 phys_addr) +{ + msg->len = 13; + msg->msg[1] = CEC_MSG_CLEAR_EXT_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + msg->msg[9] = ext_src_spec; + msg->msg[10] = plug; + msg->msg[11] = phys_addr >> 8; + msg->msg[12] = phys_addr & 0xff; + msg->reply = reply ? CEC_MSG_TIMER_CLEARED_STATUS : 0; +} + +static inline void cec_ops_clear_ext_timer(const struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + __u8 *ext_src_spec, + __u8 *plug, + __u16 *phys_addr) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + *ext_src_spec = msg->msg[9]; + *plug = msg->msg[10]; + *phys_addr = (msg->msg[11] << 8) | msg->msg[12]; +} + +static inline void cec_msg_set_analogue_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + __u8 ana_bcast_type, + __u16 ana_freq, + __u8 bcast_system) +{ + msg->len = 13; + msg->msg[1] = CEC_MSG_SET_ANALOGUE_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + msg->msg[9] = ana_bcast_type; + msg->msg[10] = ana_freq >> 8; + msg->msg[11] = ana_freq & 0xff; + msg->msg[12] = bcast_system; + msg->reply = reply ? CEC_MSG_TIMER_STATUS : 0; +} + +static inline void cec_ops_set_analogue_timer(const struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + __u8 *ana_bcast_type, + __u16 *ana_freq, + __u8 *bcast_system) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + *ana_bcast_type = msg->msg[9]; + *ana_freq = (msg->msg[10] << 8) | msg->msg[11]; + *bcast_system = msg->msg[12]; +} + +static inline void cec_msg_set_digital_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + const struct cec_op_digital_service_id *digital) +{ + msg->len = 16; + msg->reply = reply ? CEC_MSG_TIMER_STATUS : 0; + msg->msg[1] = CEC_MSG_SET_DIGITAL_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + cec_set_digital_service_id(msg->msg + 9, digital); +} + +static inline void cec_ops_set_digital_timer(const struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + struct cec_op_digital_service_id *digital) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + cec_get_digital_service_id(msg->msg + 9, digital); +} + +static inline void cec_msg_set_ext_timer(struct cec_msg *msg, + bool reply, + __u8 day, + __u8 month, + __u8 start_hr, + __u8 start_min, + __u8 duration_hr, + __u8 duration_min, + __u8 recording_seq, + __u8 ext_src_spec, + __u8 plug, + __u16 phys_addr) +{ + msg->len = 13; + msg->msg[1] = CEC_MSG_SET_EXT_TIMER; + msg->msg[2] = day; + msg->msg[3] = month; + /* Hours and minutes are in BCD format */ + msg->msg[4] = ((start_hr / 10) << 4) | (start_hr % 10); + msg->msg[5] = ((start_min / 10) << 4) | (start_min % 10); + msg->msg[6] = ((duration_hr / 10) << 4) | (duration_hr % 10); + msg->msg[7] = ((duration_min / 10) << 4) | (duration_min % 10); + msg->msg[8] = recording_seq; + msg->msg[9] = ext_src_spec; + msg->msg[10] = plug; + msg->msg[11] = phys_addr >> 8; + msg->msg[12] = phys_addr & 0xff; + msg->reply = reply ? CEC_MSG_TIMER_STATUS : 0; +} + +static inline void cec_ops_set_ext_timer(const struct cec_msg *msg, + __u8 *day, + __u8 *month, + __u8 *start_hr, + __u8 *start_min, + __u8 *duration_hr, + __u8 *duration_min, + __u8 *recording_seq, + __u8 *ext_src_spec, + __u8 *plug, + __u16 *phys_addr) +{ + *day = msg->msg[2]; + *month = msg->msg[3]; + /* Hours and minutes are in BCD format */ + *start_hr = (msg->msg[4] >> 4) * 10 + (msg->msg[4] & 0xf); + *start_min = (msg->msg[5] >> 4) * 10 + (msg->msg[5] & 0xf); + *duration_hr = (msg->msg[6] >> 4) * 10 + (msg->msg[6] & 0xf); + *duration_min = (msg->msg[7] >> 4) * 10 + (msg->msg[7] & 0xf); + *recording_seq = msg->msg[8]; + *ext_src_spec = msg->msg[9]; + *plug = msg->msg[10]; + *phys_addr = (msg->msg[11] << 8) | msg->msg[12]; +} + +static inline void cec_msg_set_timer_program_title(struct cec_msg *msg, + const char *prog_title) +{ + unsigned int len = strlen(prog_title); + + if (len > 14) + len = 14; + msg->len = 2 + len; + msg->msg[1] = CEC_MSG_SET_TIMER_PROGRAM_TITLE; + memcpy(msg->msg + 2, prog_title, len); +} + +static inline void cec_ops_set_timer_program_title(const struct cec_msg *msg, + char *prog_title) +{ + unsigned int len = msg->len > 2 ? msg->len - 2 : 0; + + if (len > 14) + len = 14; + memcpy(prog_title, msg->msg + 2, len); + prog_title[len] = '\0'; +} + +/* System Information Feature */ +static inline void cec_msg_cec_version(struct cec_msg *msg, __u8 cec_version) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_CEC_VERSION; + msg->msg[2] = cec_version; +} + +static inline void cec_ops_cec_version(const struct cec_msg *msg, + __u8 *cec_version) +{ + *cec_version = msg->msg[2]; +} + +static inline void cec_msg_get_cec_version(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GET_CEC_VERSION; + msg->reply = reply ? CEC_MSG_CEC_VERSION : 0; +} + +static inline void cec_msg_report_physical_addr(struct cec_msg *msg, + __u16 phys_addr, __u8 prim_devtype) +{ + msg->len = 5; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_REPORT_PHYSICAL_ADDR; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; + msg->msg[4] = prim_devtype; +} + +static inline void cec_ops_report_physical_addr(const struct cec_msg *msg, + __u16 *phys_addr, __u8 *prim_devtype) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *prim_devtype = msg->msg[4]; +} + +static inline void cec_msg_give_physical_addr(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_PHYSICAL_ADDR; + msg->reply = reply ? CEC_MSG_REPORT_PHYSICAL_ADDR : 0; +} + +static inline void cec_msg_set_menu_language(struct cec_msg *msg, + const char *language) +{ + msg->len = 5; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_SET_MENU_LANGUAGE; + memcpy(msg->msg + 2, language, 3); +} + +static inline void cec_ops_set_menu_language(const struct cec_msg *msg, + char *language) +{ + memcpy(language, msg->msg + 2, 3); + language[3] = '\0'; +} + +static inline void cec_msg_get_menu_language(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GET_MENU_LANGUAGE; + msg->reply = reply ? CEC_MSG_SET_MENU_LANGUAGE : 0; +} + +/* + * Assumes a single RC Profile byte and a single Device Features byte, + * i.e. no extended features are supported by this helper function. + * + * As of CEC 2.0 no extended features are defined, should those be added + * in the future, then this function needs to be adapted or a new function + * should be added. + */ +static inline void cec_msg_report_features(struct cec_msg *msg, + __u8 cec_version, __u8 all_device_types, + __u8 rc_profile, __u8 dev_features) +{ + msg->len = 6; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_REPORT_FEATURES; + msg->msg[2] = cec_version; + msg->msg[3] = all_device_types; + msg->msg[4] = rc_profile; + msg->msg[5] = dev_features; +} + +static inline void cec_ops_report_features(const struct cec_msg *msg, + __u8 *cec_version, __u8 *all_device_types, + const __u8 **rc_profile, const __u8 **dev_features) +{ + const __u8 *p = &msg->msg[4]; + + *cec_version = msg->msg[2]; + *all_device_types = msg->msg[3]; + *rc_profile = p; + while (p < &msg->msg[14] && (*p & CEC_OP_FEAT_EXT)) + p++; + if (!(*p & CEC_OP_FEAT_EXT)) { + *dev_features = p + 1; + while (p < &msg->msg[15] && (*p & CEC_OP_FEAT_EXT)) + p++; + } + if (*p & CEC_OP_FEAT_EXT) + *rc_profile = *dev_features = NULL; +} + +static inline void cec_msg_give_features(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_FEATURES; + msg->reply = reply ? CEC_MSG_REPORT_FEATURES : 0; +} + +/* Deck Control Feature */ +static inline void cec_msg_deck_control(struct cec_msg *msg, + __u8 deck_control_mode) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_DECK_CONTROL; + msg->msg[2] = deck_control_mode; +} + +static inline void cec_ops_deck_control(const struct cec_msg *msg, + __u8 *deck_control_mode) +{ + *deck_control_mode = msg->msg[2]; +} + +static inline void cec_msg_deck_status(struct cec_msg *msg, + __u8 deck_info) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_DECK_STATUS; + msg->msg[2] = deck_info; +} + +static inline void cec_ops_deck_status(const struct cec_msg *msg, + __u8 *deck_info) +{ + *deck_info = msg->msg[2]; +} + +static inline void cec_msg_give_deck_status(struct cec_msg *msg, + bool reply, + __u8 status_req) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_GIVE_DECK_STATUS; + msg->msg[2] = status_req; + msg->reply = reply ? CEC_MSG_DECK_STATUS : 0; +} + +static inline void cec_ops_give_deck_status(const struct cec_msg *msg, + __u8 *status_req) +{ + *status_req = msg->msg[2]; +} + +static inline void cec_msg_play(struct cec_msg *msg, + __u8 play_mode) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_PLAY; + msg->msg[2] = play_mode; +} + +static inline void cec_ops_play(const struct cec_msg *msg, + __u8 *play_mode) +{ + *play_mode = msg->msg[2]; +} + + +/* Tuner Control Feature */ +struct cec_op_tuner_device_info { + __u8 rec_flag; + __u8 tuner_display_info; + bool is_analog; + union { + struct cec_op_digital_service_id digital; + struct { + __u8 ana_bcast_type; + __u16 ana_freq; + __u8 bcast_system; + } analog; + }; +}; + +static inline void cec_msg_tuner_device_status_analog(struct cec_msg *msg, + __u8 rec_flag, + __u8 tuner_display_info, + __u8 ana_bcast_type, + __u16 ana_freq, + __u8 bcast_system) +{ + msg->len = 7; + msg->msg[1] = CEC_MSG_TUNER_DEVICE_STATUS; + msg->msg[2] = (rec_flag << 7) | tuner_display_info; + msg->msg[3] = ana_bcast_type; + msg->msg[4] = ana_freq >> 8; + msg->msg[5] = ana_freq & 0xff; + msg->msg[6] = bcast_system; +} + +static inline void cec_msg_tuner_device_status_digital(struct cec_msg *msg, + __u8 rec_flag, __u8 tuner_display_info, + const struct cec_op_digital_service_id *digital) +{ + msg->len = 10; + msg->msg[1] = CEC_MSG_TUNER_DEVICE_STATUS; + msg->msg[2] = (rec_flag << 7) | tuner_display_info; + cec_set_digital_service_id(msg->msg + 3, digital); +} + +static inline void cec_msg_tuner_device_status(struct cec_msg *msg, + const struct cec_op_tuner_device_info *tuner_dev_info) +{ + if (tuner_dev_info->is_analog) + cec_msg_tuner_device_status_analog(msg, + tuner_dev_info->rec_flag, + tuner_dev_info->tuner_display_info, + tuner_dev_info->analog.ana_bcast_type, + tuner_dev_info->analog.ana_freq, + tuner_dev_info->analog.bcast_system); + else + cec_msg_tuner_device_status_digital(msg, + tuner_dev_info->rec_flag, + tuner_dev_info->tuner_display_info, + &tuner_dev_info->digital); +} + +static inline void cec_ops_tuner_device_status(const struct cec_msg *msg, + struct cec_op_tuner_device_info *tuner_dev_info) +{ + tuner_dev_info->is_analog = msg->len < 10; + tuner_dev_info->rec_flag = msg->msg[2] >> 7; + tuner_dev_info->tuner_display_info = msg->msg[2] & 0x7f; + if (tuner_dev_info->is_analog) { + tuner_dev_info->analog.ana_bcast_type = msg->msg[3]; + tuner_dev_info->analog.ana_freq = (msg->msg[4] << 8) | msg->msg[5]; + tuner_dev_info->analog.bcast_system = msg->msg[6]; + return; + } + cec_get_digital_service_id(msg->msg + 3, &tuner_dev_info->digital); +} + +static inline void cec_msg_give_tuner_device_status(struct cec_msg *msg, + bool reply, + __u8 status_req) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_GIVE_TUNER_DEVICE_STATUS; + msg->msg[2] = status_req; + msg->reply = reply ? CEC_MSG_TUNER_DEVICE_STATUS : 0; +} + +static inline void cec_ops_give_tuner_device_status(const struct cec_msg *msg, + __u8 *status_req) +{ + *status_req = msg->msg[2]; +} + +static inline void cec_msg_select_analogue_service(struct cec_msg *msg, + __u8 ana_bcast_type, + __u16 ana_freq, + __u8 bcast_system) +{ + msg->len = 6; + msg->msg[1] = CEC_MSG_SELECT_ANALOGUE_SERVICE; + msg->msg[2] = ana_bcast_type; + msg->msg[3] = ana_freq >> 8; + msg->msg[4] = ana_freq & 0xff; + msg->msg[5] = bcast_system; +} + +static inline void cec_ops_select_analogue_service(const struct cec_msg *msg, + __u8 *ana_bcast_type, + __u16 *ana_freq, + __u8 *bcast_system) +{ + *ana_bcast_type = msg->msg[2]; + *ana_freq = (msg->msg[3] << 8) | msg->msg[4]; + *bcast_system = msg->msg[5]; +} + +static inline void cec_msg_select_digital_service(struct cec_msg *msg, + const struct cec_op_digital_service_id *digital) +{ + msg->len = 9; + msg->msg[1] = CEC_MSG_SELECT_DIGITAL_SERVICE; + cec_set_digital_service_id(msg->msg + 2, digital); +} + +static inline void cec_ops_select_digital_service(const struct cec_msg *msg, + struct cec_op_digital_service_id *digital) +{ + cec_get_digital_service_id(msg->msg + 2, digital); +} + +static inline void cec_msg_tuner_step_decrement(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_TUNER_STEP_DECREMENT; +} + +static inline void cec_msg_tuner_step_increment(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_TUNER_STEP_INCREMENT; +} + + +/* Vendor Specific Commands Feature */ +static inline void cec_msg_device_vendor_id(struct cec_msg *msg, __u32 vendor_id) +{ + msg->len = 5; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_DEVICE_VENDOR_ID; + msg->msg[2] = vendor_id >> 16; + msg->msg[3] = (vendor_id >> 8) & 0xff; + msg->msg[4] = vendor_id & 0xff; +} + +static inline void cec_ops_device_vendor_id(const struct cec_msg *msg, + __u32 *vendor_id) +{ + *vendor_id = (msg->msg[2] << 16) | (msg->msg[3] << 8) | msg->msg[4]; +} + +static inline void cec_msg_give_device_vendor_id(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_DEVICE_VENDOR_ID; + msg->reply = reply ? CEC_MSG_DEVICE_VENDOR_ID : 0; +} + +static inline void cec_msg_vendor_command(struct cec_msg *msg, + __u8 size, const __u8 *vendor_cmd) +{ + if (size > 14) + size = 14; + msg->len = 2 + size; + msg->msg[1] = CEC_MSG_VENDOR_COMMAND; + memcpy(msg->msg + 2, vendor_cmd, size); +} + +static inline void cec_ops_vendor_command(const struct cec_msg *msg, + __u8 *size, + const __u8 **vendor_cmd) +{ + *size = msg->len - 2; + + if (*size > 14) + *size = 14; + *vendor_cmd = msg->msg + 2; +} + +static inline void cec_msg_vendor_command_with_id(struct cec_msg *msg, + __u32 vendor_id, __u8 size, + const __u8 *vendor_cmd) +{ + if (size > 11) + size = 11; + msg->len = 5 + size; + msg->msg[1] = CEC_MSG_VENDOR_COMMAND_WITH_ID; + msg->msg[2] = vendor_id >> 16; + msg->msg[3] = (vendor_id >> 8) & 0xff; + msg->msg[4] = vendor_id & 0xff; + memcpy(msg->msg + 5, vendor_cmd, size); +} + +static inline void cec_ops_vendor_command_with_id(const struct cec_msg *msg, + __u32 *vendor_id, __u8 *size, + const __u8 **vendor_cmd) +{ + *size = msg->len - 5; + + if (*size > 11) + *size = 11; + *vendor_id = (msg->msg[2] << 16) | (msg->msg[3] << 8) | msg->msg[4]; + *vendor_cmd = msg->msg + 5; +} + +static inline void cec_msg_vendor_remote_button_down(struct cec_msg *msg, + __u8 size, + const __u8 *rc_code) +{ + if (size > 14) + size = 14; + msg->len = 2 + size; + msg->msg[1] = CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN; + memcpy(msg->msg + 2, rc_code, size); +} + +static inline void cec_ops_vendor_remote_button_down(const struct cec_msg *msg, + __u8 *size, + const __u8 **rc_code) +{ + *size = msg->len - 2; + + if (*size > 14) + *size = 14; + *rc_code = msg->msg + 2; +} + +static inline void cec_msg_vendor_remote_button_up(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_VENDOR_REMOTE_BUTTON_UP; +} + + +/* OSD Display Feature */ +static inline void cec_msg_set_osd_string(struct cec_msg *msg, + __u8 disp_ctl, + const char *osd) +{ + unsigned int len = strlen(osd); + + if (len > 13) + len = 13; + msg->len = 3 + len; + msg->msg[1] = CEC_MSG_SET_OSD_STRING; + msg->msg[2] = disp_ctl; + memcpy(msg->msg + 3, osd, len); +} + +static inline void cec_ops_set_osd_string(const struct cec_msg *msg, + __u8 *disp_ctl, + char *osd) +{ + unsigned int len = msg->len > 3 ? msg->len - 3 : 0; + + *disp_ctl = msg->msg[2]; + if (len > 13) + len = 13; + memcpy(osd, msg->msg + 3, len); + osd[len] = '\0'; +} + + +/* Device OSD Transfer Feature */ +static inline void cec_msg_set_osd_name(struct cec_msg *msg, const char *name) +{ + unsigned int len = strlen(name); + + if (len > 14) + len = 14; + msg->len = 2 + len; + msg->msg[1] = CEC_MSG_SET_OSD_NAME; + memcpy(msg->msg + 2, name, len); +} + +static inline void cec_ops_set_osd_name(const struct cec_msg *msg, + char *name) +{ + unsigned int len = msg->len > 2 ? msg->len - 2 : 0; + + if (len > 14) + len = 14; + memcpy(name, msg->msg + 2, len); + name[len] = '\0'; +} + +static inline void cec_msg_give_osd_name(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_OSD_NAME; + msg->reply = reply ? CEC_MSG_SET_OSD_NAME : 0; +} + + +/* Device Menu Control Feature */ +static inline void cec_msg_menu_status(struct cec_msg *msg, + __u8 menu_state) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_MENU_STATUS; + msg->msg[2] = menu_state; +} + +static inline void cec_ops_menu_status(const struct cec_msg *msg, + __u8 *menu_state) +{ + *menu_state = msg->msg[2]; +} + +static inline void cec_msg_menu_request(struct cec_msg *msg, + bool reply, + __u8 menu_req) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_MENU_REQUEST; + msg->msg[2] = menu_req; + msg->reply = reply ? CEC_MSG_MENU_STATUS : 0; +} + +static inline void cec_ops_menu_request(const struct cec_msg *msg, + __u8 *menu_req) +{ + *menu_req = msg->msg[2]; +} + +struct cec_op_ui_command { + __u8 ui_cmd; + bool has_opt_arg; + union { + struct cec_op_channel_data channel_identifier; + __u8 ui_broadcast_type; + __u8 ui_sound_presentation_control; + __u8 play_mode; + __u8 ui_function_media; + __u8 ui_function_select_av_input; + __u8 ui_function_select_audio_input; + }; +}; + +static inline void cec_msg_user_control_pressed(struct cec_msg *msg, + const struct cec_op_ui_command *ui_cmd) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_USER_CONTROL_PRESSED; + msg->msg[2] = ui_cmd->ui_cmd; + if (!ui_cmd->has_opt_arg) + return; + switch (ui_cmd->ui_cmd) { + case 0x56: + case 0x57: + case 0x60: + case 0x68: + case 0x69: + case 0x6a: + /* The optional operand is one byte for all these ui commands */ + msg->len++; + msg->msg[3] = ui_cmd->play_mode; + break; + case 0x67: + msg->len += 4; + msg->msg[3] = (ui_cmd->channel_identifier.channel_number_fmt << 2) | + (ui_cmd->channel_identifier.major >> 8); + msg->msg[4] = ui_cmd->channel_identifier.major & 0xff; + msg->msg[5] = ui_cmd->channel_identifier.minor >> 8; + msg->msg[6] = ui_cmd->channel_identifier.minor & 0xff; + break; + } +} + +static inline void cec_ops_user_control_pressed(const struct cec_msg *msg, + struct cec_op_ui_command *ui_cmd) +{ + ui_cmd->ui_cmd = msg->msg[2]; + ui_cmd->has_opt_arg = false; + if (msg->len == 3) + return; + switch (ui_cmd->ui_cmd) { + case 0x56: + case 0x57: + case 0x60: + case 0x68: + case 0x69: + case 0x6a: + /* The optional operand is one byte for all these ui commands */ + ui_cmd->play_mode = msg->msg[3]; + ui_cmd->has_opt_arg = true; + break; + case 0x67: + if (msg->len < 7) + break; + ui_cmd->has_opt_arg = true; + ui_cmd->channel_identifier.channel_number_fmt = msg->msg[3] >> 2; + ui_cmd->channel_identifier.major = ((msg->msg[3] & 3) << 6) | msg->msg[4]; + ui_cmd->channel_identifier.minor = (msg->msg[5] << 8) | msg->msg[6]; + break; + } +} + +static inline void cec_msg_user_control_released(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_USER_CONTROL_RELEASED; +} + +/* Remote Control Passthrough Feature */ + +/* Power Status Feature */ +static inline void cec_msg_report_power_status(struct cec_msg *msg, + __u8 pwr_state) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_REPORT_POWER_STATUS; + msg->msg[2] = pwr_state; +} + +static inline void cec_ops_report_power_status(const struct cec_msg *msg, + __u8 *pwr_state) +{ + *pwr_state = msg->msg[2]; +} + +static inline void cec_msg_give_device_power_status(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_DEVICE_POWER_STATUS; + msg->reply = reply ? CEC_MSG_REPORT_POWER_STATUS : 0; +} + +/* General Protocol Messages */ +static inline void cec_msg_feature_abort(struct cec_msg *msg, + __u8 abort_msg, __u8 reason) +{ + msg->len = 4; + msg->msg[1] = CEC_MSG_FEATURE_ABORT; + msg->msg[2] = abort_msg; + msg->msg[3] = reason; +} + +static inline void cec_ops_feature_abort(const struct cec_msg *msg, + __u8 *abort_msg, __u8 *reason) +{ + *abort_msg = msg->msg[2]; + *reason = msg->msg[3]; +} + +/* This changes the current message into a feature abort message */ +static inline void cec_msg_reply_feature_abort(struct cec_msg *msg, __u8 reason) +{ + cec_msg_set_reply_to(msg, msg); + msg->len = 4; + msg->msg[2] = msg->msg[1]; + msg->msg[3] = reason; + msg->msg[1] = CEC_MSG_FEATURE_ABORT; +} + +static inline void cec_msg_abort(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_ABORT; +} + + +/* System Audio Control Feature */ +static inline void cec_msg_report_audio_status(struct cec_msg *msg, + __u8 aud_mute_status, + __u8 aud_vol_status) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_REPORT_AUDIO_STATUS; + msg->msg[2] = (aud_mute_status << 7) | (aud_vol_status & 0x7f); +} + +static inline void cec_ops_report_audio_status(const struct cec_msg *msg, + __u8 *aud_mute_status, + __u8 *aud_vol_status) +{ + *aud_mute_status = msg->msg[2] >> 7; + *aud_vol_status = msg->msg[2] & 0x7f; +} + +static inline void cec_msg_give_audio_status(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_AUDIO_STATUS; + msg->reply = reply ? CEC_MSG_REPORT_AUDIO_STATUS : 0; +} + +static inline void cec_msg_set_system_audio_mode(struct cec_msg *msg, + __u8 sys_aud_status) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_SET_SYSTEM_AUDIO_MODE; + msg->msg[2] = sys_aud_status; +} + +static inline void cec_ops_set_system_audio_mode(const struct cec_msg *msg, + __u8 *sys_aud_status) +{ + *sys_aud_status = msg->msg[2]; +} + +static inline void cec_msg_system_audio_mode_request(struct cec_msg *msg, + bool reply, + __u16 phys_addr) +{ + msg->len = phys_addr == 0xffff ? 2 : 4; + msg->msg[1] = CEC_MSG_SYSTEM_AUDIO_MODE_REQUEST; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; + msg->reply = reply ? CEC_MSG_SET_SYSTEM_AUDIO_MODE : 0; + +} + +static inline void cec_ops_system_audio_mode_request(const struct cec_msg *msg, + __u16 *phys_addr) +{ + if (msg->len < 4) + *phys_addr = 0xffff; + else + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_system_audio_mode_status(struct cec_msg *msg, + __u8 sys_aud_status) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_SYSTEM_AUDIO_MODE_STATUS; + msg->msg[2] = sys_aud_status; +} + +static inline void cec_ops_system_audio_mode_status(const struct cec_msg *msg, + __u8 *sys_aud_status) +{ + *sys_aud_status = msg->msg[2]; +} + +static inline void cec_msg_give_system_audio_mode_status(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS; + msg->reply = reply ? CEC_MSG_SYSTEM_AUDIO_MODE_STATUS : 0; +} + +static inline void cec_msg_report_short_audio_descriptor(struct cec_msg *msg, + __u8 num_descriptors, + const __u32 *descriptors) +{ + unsigned int i; + + if (num_descriptors > 4) + num_descriptors = 4; + msg->len = 2 + num_descriptors * 3; + msg->msg[1] = CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR; + for (i = 0; i < num_descriptors; i++) { + msg->msg[2 + i * 3] = (descriptors[i] >> 16) & 0xff; + msg->msg[3 + i * 3] = (descriptors[i] >> 8) & 0xff; + msg->msg[4 + i * 3] = descriptors[i] & 0xff; + } +} + +static inline void cec_ops_report_short_audio_descriptor(const struct cec_msg *msg, + __u8 *num_descriptors, + __u32 *descriptors) +{ + unsigned int i; + + *num_descriptors = (msg->len - 2) / 3; + if (*num_descriptors > 4) + *num_descriptors = 4; + for (i = 0; i < *num_descriptors; i++) + descriptors[i] = (msg->msg[2 + i * 3] << 16) | + (msg->msg[3 + i * 3] << 8) | + msg->msg[4 + i * 3]; +} + +static inline void cec_msg_request_short_audio_descriptor(struct cec_msg *msg, + bool reply, + __u8 num_descriptors, + const __u8 *audio_format_id, + const __u8 *audio_format_code) +{ + unsigned int i; + + if (num_descriptors > 4) + num_descriptors = 4; + msg->len = 2 + num_descriptors; + msg->msg[1] = CEC_MSG_REQUEST_SHORT_AUDIO_DESCRIPTOR; + msg->reply = reply ? CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR : 0; + for (i = 0; i < num_descriptors; i++) + msg->msg[2 + i] = (audio_format_id[i] << 6) | + (audio_format_code[i] & 0x3f); +} + +static inline void cec_ops_request_short_audio_descriptor(const struct cec_msg *msg, + __u8 *num_descriptors, + __u8 *audio_format_id, + __u8 *audio_format_code) +{ + unsigned int i; + + *num_descriptors = msg->len - 2; + if (*num_descriptors > 4) + *num_descriptors = 4; + for (i = 0; i < *num_descriptors; i++) { + audio_format_id[i] = msg->msg[2 + i] >> 6; + audio_format_code[i] = msg->msg[2 + i] & 0x3f; + } +} + + +/* Audio Rate Control Feature */ +static inline void cec_msg_set_audio_rate(struct cec_msg *msg, + __u8 audio_rate) +{ + msg->len = 3; + msg->msg[1] = CEC_MSG_SET_AUDIO_RATE; + msg->msg[2] = audio_rate; +} + +static inline void cec_ops_set_audio_rate(const struct cec_msg *msg, + __u8 *audio_rate) +{ + *audio_rate = msg->msg[2]; +} + + +/* Audio Return Channel Control Feature */ +static inline void cec_msg_report_arc_initiated(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_REPORT_ARC_INITIATED; +} + +static inline void cec_msg_initiate_arc(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_INITIATE_ARC; + msg->reply = reply ? CEC_MSG_REPORT_ARC_INITIATED : 0; +} + +static inline void cec_msg_request_arc_initiation(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_REQUEST_ARC_INITIATION; + msg->reply = reply ? CEC_MSG_INITIATE_ARC : 0; +} + +static inline void cec_msg_report_arc_terminated(struct cec_msg *msg) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_REPORT_ARC_TERMINATED; +} + +static inline void cec_msg_terminate_arc(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_TERMINATE_ARC; + msg->reply = reply ? CEC_MSG_REPORT_ARC_TERMINATED : 0; +} + +static inline void cec_msg_request_arc_termination(struct cec_msg *msg, + bool reply) +{ + msg->len = 2; + msg->msg[1] = CEC_MSG_REQUEST_ARC_TERMINATION; + msg->reply = reply ? CEC_MSG_TERMINATE_ARC : 0; +} + + +/* Dynamic Audio Lipsync Feature */ +/* Only for CEC 2.0 and up */ +static inline void cec_msg_report_current_latency(struct cec_msg *msg, + __u16 phys_addr, + __u8 video_latency, + __u8 low_latency_mode, + __u8 audio_out_compensated, + __u8 audio_out_delay) +{ + msg->len = 7; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_REPORT_CURRENT_LATENCY; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; + msg->msg[4] = video_latency; + msg->msg[5] = (low_latency_mode << 2) | audio_out_compensated; + msg->msg[6] = audio_out_delay; +} + +static inline void cec_ops_report_current_latency(const struct cec_msg *msg, + __u16 *phys_addr, + __u8 *video_latency, + __u8 *low_latency_mode, + __u8 *audio_out_compensated, + __u8 *audio_out_delay) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *video_latency = msg->msg[4]; + *low_latency_mode = (msg->msg[5] >> 2) & 1; + *audio_out_compensated = msg->msg[5] & 3; + *audio_out_delay = msg->msg[6]; +} + +static inline void cec_msg_request_current_latency(struct cec_msg *msg, + bool reply, + __u16 phys_addr) +{ + msg->len = 4; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_REQUEST_CURRENT_LATENCY; + msg->msg[2] = phys_addr >> 8; + msg->msg[3] = phys_addr & 0xff; + msg->reply = reply ? CEC_MSG_REPORT_CURRENT_LATENCY : 0; +} + +static inline void cec_ops_request_current_latency(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + + +/* Capability Discovery and Control Feature */ +static inline void cec_msg_cdc_hec_inquire_state(struct cec_msg *msg, + __u16 phys_addr1, + __u16 phys_addr2) +{ + msg->len = 9; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_INQUIRE_STATE; + msg->msg[5] = phys_addr1 >> 8; + msg->msg[6] = phys_addr1 & 0xff; + msg->msg[7] = phys_addr2 >> 8; + msg->msg[8] = phys_addr2 & 0xff; +} + +static inline void cec_ops_cdc_hec_inquire_state(const struct cec_msg *msg, + __u16 *phys_addr, + __u16 *phys_addr1, + __u16 *phys_addr2) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; + *phys_addr2 = (msg->msg[7] << 8) | msg->msg[8]; +} + +static inline void cec_msg_cdc_hec_report_state(struct cec_msg *msg, + __u16 target_phys_addr, + __u8 hec_func_state, + __u8 host_func_state, + __u8 enc_func_state, + __u8 cdc_errcode, + __u8 has_field, + __u16 hec_field) +{ + msg->len = has_field ? 10 : 8; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_REPORT_STATE; + msg->msg[5] = target_phys_addr >> 8; + msg->msg[6] = target_phys_addr & 0xff; + msg->msg[7] = (hec_func_state << 6) | + (host_func_state << 4) | + (enc_func_state << 2) | + cdc_errcode; + if (has_field) { + msg->msg[8] = hec_field >> 8; + msg->msg[9] = hec_field & 0xff; + } +} + +static inline void cec_ops_cdc_hec_report_state(const struct cec_msg *msg, + __u16 *phys_addr, + __u16 *target_phys_addr, + __u8 *hec_func_state, + __u8 *host_func_state, + __u8 *enc_func_state, + __u8 *cdc_errcode, + __u8 *has_field, + __u16 *hec_field) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *target_phys_addr = (msg->msg[5] << 8) | msg->msg[6]; + *hec_func_state = msg->msg[7] >> 6; + *host_func_state = (msg->msg[7] >> 4) & 3; + *enc_func_state = (msg->msg[7] >> 4) & 3; + *cdc_errcode = msg->msg[7] & 3; + *has_field = msg->len >= 10; + *hec_field = *has_field ? ((msg->msg[8] << 8) | msg->msg[9]) : 0; +} + +static inline void cec_msg_cdc_hec_set_state(struct cec_msg *msg, + __u16 phys_addr1, + __u16 phys_addr2, + __u8 hec_set_state, + __u16 phys_addr3, + __u16 phys_addr4, + __u16 phys_addr5) +{ + msg->len = 10; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_INQUIRE_STATE; + msg->msg[5] = phys_addr1 >> 8; + msg->msg[6] = phys_addr1 & 0xff; + msg->msg[7] = phys_addr2 >> 8; + msg->msg[8] = phys_addr2 & 0xff; + msg->msg[9] = hec_set_state; + if (phys_addr3 != CEC_PHYS_ADDR_INVALID) { + msg->msg[msg->len++] = phys_addr3 >> 8; + msg->msg[msg->len++] = phys_addr3 & 0xff; + if (phys_addr4 != CEC_PHYS_ADDR_INVALID) { + msg->msg[msg->len++] = phys_addr4 >> 8; + msg->msg[msg->len++] = phys_addr4 & 0xff; + if (phys_addr5 != CEC_PHYS_ADDR_INVALID) { + msg->msg[msg->len++] = phys_addr5 >> 8; + msg->msg[msg->len++] = phys_addr5 & 0xff; + } + } + } +} + +static inline void cec_ops_cdc_hec_set_state(const struct cec_msg *msg, + __u16 *phys_addr, + __u16 *phys_addr1, + __u16 *phys_addr2, + __u8 *hec_set_state, + __u16 *phys_addr3, + __u16 *phys_addr4, + __u16 *phys_addr5) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; + *phys_addr2 = (msg->msg[7] << 8) | msg->msg[8]; + *hec_set_state = msg->msg[9]; + *phys_addr3 = *phys_addr4 = *phys_addr5 = CEC_PHYS_ADDR_INVALID; + if (msg->len >= 12) + *phys_addr3 = (msg->msg[10] << 8) | msg->msg[11]; + if (msg->len >= 14) + *phys_addr4 = (msg->msg[12] << 8) | msg->msg[13]; + if (msg->len >= 16) + *phys_addr5 = (msg->msg[14] << 8) | msg->msg[15]; +} + +static inline void cec_msg_cdc_hec_set_state_adjacent(struct cec_msg *msg, + __u16 phys_addr1, + __u8 hec_set_state) +{ + msg->len = 8; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_SET_STATE_ADJACENT; + msg->msg[5] = phys_addr1 >> 8; + msg->msg[6] = phys_addr1 & 0xff; + msg->msg[7] = hec_set_state; +} + +static inline void cec_ops_cdc_hec_set_state_adjacent(const struct cec_msg *msg, + __u16 *phys_addr, + __u16 *phys_addr1, + __u8 *hec_set_state) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; + *hec_set_state = msg->msg[7]; +} + +static inline void cec_msg_cdc_hec_request_deactivation(struct cec_msg *msg, + __u16 phys_addr1, + __u16 phys_addr2, + __u16 phys_addr3) +{ + msg->len = 11; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_REQUEST_DEACTIVATION; + msg->msg[5] = phys_addr1 >> 8; + msg->msg[6] = phys_addr1 & 0xff; + msg->msg[7] = phys_addr2 >> 8; + msg->msg[8] = phys_addr2 & 0xff; + msg->msg[9] = phys_addr3 >> 8; + msg->msg[10] = phys_addr3 & 0xff; +} + +static inline void cec_ops_cdc_hec_request_deactivation(const struct cec_msg *msg, + __u16 *phys_addr, + __u16 *phys_addr1, + __u16 *phys_addr2, + __u16 *phys_addr3) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *phys_addr1 = (msg->msg[5] << 8) | msg->msg[6]; + *phys_addr2 = (msg->msg[7] << 8) | msg->msg[8]; + *phys_addr3 = (msg->msg[9] << 8) | msg->msg[10]; +} + +static inline void cec_msg_cdc_hec_notify_alive(struct cec_msg *msg) +{ + msg->len = 5; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_NOTIFY_ALIVE; +} + +static inline void cec_ops_cdc_hec_notify_alive(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_cdc_hec_discover(struct cec_msg *msg) +{ + msg->len = 5; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HEC_DISCOVER; +} + +static inline void cec_ops_cdc_hec_discover(const struct cec_msg *msg, + __u16 *phys_addr) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; +} + +static inline void cec_msg_cdc_hpd_set_state(struct cec_msg *msg, + __u8 input_port, + __u8 hpd_state) +{ + msg->len = 6; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HPD_SET_STATE; + msg->msg[5] = (input_port << 4) | hpd_state; +} + +static inline void cec_ops_cdc_hpd_set_state(const struct cec_msg *msg, + __u16 *phys_addr, + __u8 *input_port, + __u8 *hpd_state) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *input_port = msg->msg[5] >> 4; + *hpd_state = msg->msg[5] & 0xf; +} + +static inline void cec_msg_cdc_hpd_report_state(struct cec_msg *msg, + __u8 hpd_state, + __u8 hpd_error) +{ + msg->len = 6; + msg->msg[0] |= 0xf; /* broadcast */ + msg->msg[1] = CEC_MSG_CDC_MESSAGE; + /* msg[2] and msg[3] (phys_addr) are filled in by the CEC framework */ + msg->msg[4] = CEC_MSG_CDC_HPD_REPORT_STATE; + msg->msg[5] = (hpd_state << 4) | hpd_error; +} + +static inline void cec_ops_cdc_hpd_report_state(const struct cec_msg *msg, + __u16 *phys_addr, + __u8 *hpd_state, + __u8 *hpd_error) +{ + *phys_addr = (msg->msg[2] << 8) | msg->msg[3]; + *hpd_state = msg->msg[5] >> 4; + *hpd_error = msg->msg[5] & 0xf; +} + +#endif diff --git a/include/uapi/linux/cec.h b/include/uapi/linux/cec.h new file mode 100644 index 000000000000..f4ec0af67707 --- /dev/null +++ b/include/uapi/linux/cec.h @@ -0,0 +1,1065 @@ +/* + * cec - HDMI Consumer Electronics Control public header + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * Alternatively you can redistribute this file under the terms of the + * BSD license as stated below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. The names of its contributors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _CEC_UAPI_H +#define _CEC_UAPI_H + +#include + +#define CEC_MAX_MSG_SIZE 16 + +/** + * struct cec_msg - CEC message structure. + * @tx_ts: Timestamp in nanoseconds using CLOCK_MONOTONIC. Set by the + * driver when the message transmission has finished. + * @rx_ts: Timestamp in nanoseconds using CLOCK_MONOTONIC. Set by the + * driver when the message was received. + * @len: Length in bytes of the message. + * @timeout: The timeout (in ms) that is used to timeout CEC_RECEIVE. + * Set to 0 if you want to wait forever. This timeout can also be + * used with CEC_TRANSMIT as the timeout for waiting for a reply. + * If 0, then it will use a 1 second timeout instead of waiting + * forever as is done with CEC_RECEIVE. + * @sequence: The framework assigns a sequence number to messages that are + * sent. This can be used to track replies to previously sent + * messages. + * @flags: Set to 0. + * @msg: The message payload. + * @reply: This field is ignored with CEC_RECEIVE and is only used by + * CEC_TRANSMIT. If non-zero, then wait for a reply with this + * opcode. Set to CEC_MSG_FEATURE_ABORT if you want to wait for + * a possible ABORT reply. If there was an error when sending the + * msg or FeatureAbort was returned, then reply is set to 0. + * If reply is non-zero upon return, then len/msg are set to + * the received message. + * If reply is zero upon return and status has the + * CEC_TX_STATUS_FEATURE_ABORT bit set, then len/msg are set to + * the received feature abort message. + * If reply is zero upon return and status has the + * CEC_TX_STATUS_MAX_RETRIES bit set, then no reply was seen at + * all. If reply is non-zero for CEC_TRANSMIT and the message is a + * broadcast, then -EINVAL is returned. + * if reply is non-zero, then timeout is set to 1000 (the required + * maximum response time). + * @rx_status: The message receive status bits. Set by the driver. + * @tx_status: The message transmit status bits. Set by the driver. + * @tx_arb_lost_cnt: The number of 'Arbitration Lost' events. Set by the driver. + * @tx_nack_cnt: The number of 'Not Acknowledged' events. Set by the driver. + * @tx_low_drive_cnt: The number of 'Low Drive Detected' events. Set by the + * driver. + * @tx_error_cnt: The number of 'Error' events. Set by the driver. + */ +struct cec_msg { + __u64 tx_ts; + __u64 rx_ts; + __u32 len; + __u32 timeout; + __u32 sequence; + __u32 flags; + __u8 msg[CEC_MAX_MSG_SIZE]; + __u8 reply; + __u8 rx_status; + __u8 tx_status; + __u8 tx_arb_lost_cnt; + __u8 tx_nack_cnt; + __u8 tx_low_drive_cnt; + __u8 tx_error_cnt; +}; + +/** + * cec_msg_initiator - return the initiator's logical address. + * @msg: the message structure + */ +static inline __u8 cec_msg_initiator(const struct cec_msg *msg) +{ + return msg->msg[0] >> 4; +} + +/** + * cec_msg_destination - return the destination's logical address. + * @msg: the message structure + */ +static inline __u8 cec_msg_destination(const struct cec_msg *msg) +{ + return msg->msg[0] & 0xf; +} + +/** + * cec_msg_opcode - return the opcode of the message, -1 for poll + * @msg: the message structure + */ +static inline int cec_msg_opcode(const struct cec_msg *msg) +{ + return msg->len > 1 ? msg->msg[1] : -1; +} + +/** + * cec_msg_is_broadcast - return true if this is a broadcast message. + * @msg: the message structure + */ +static inline bool cec_msg_is_broadcast(const struct cec_msg *msg) +{ + return (msg->msg[0] & 0xf) == 0xf; +} + +/** + * cec_msg_init - initialize the message structure. + * @msg: the message structure + * @initiator: the logical address of the initiator + * @destination:the logical address of the destination (0xf for broadcast) + * + * The whole structure is zeroed, the len field is set to 1 (i.e. a poll + * message) and the initiator and destination are filled in. + */ +static inline void cec_msg_init(struct cec_msg *msg, + __u8 initiator, __u8 destination) +{ + memset(msg, 0, sizeof(*msg)); + msg->msg[0] = (initiator << 4) | destination; + msg->len = 1; +} + +/** + * cec_msg_set_reply_to - fill in destination/initiator in a reply message. + * @msg: the message structure for the reply + * @orig: the original message structure + * + * Set the msg destination to the orig initiator and the msg initiator to the + * orig destination. Note that msg and orig may be the same pointer, in which + * case the change is done in place. + */ +static inline void cec_msg_set_reply_to(struct cec_msg *msg, + struct cec_msg *orig) +{ + /* The destination becomes the initiator and vice versa */ + msg->msg[0] = (cec_msg_destination(orig) << 4) | + cec_msg_initiator(orig); + msg->reply = msg->timeout = 0; +} + +/* cec_msg flags field */ +#define CEC_MSG_FL_REPLY_TO_FOLLOWERS (1 << 0) + +/* cec_msg tx/rx_status field */ +#define CEC_TX_STATUS_OK (1 << 0) +#define CEC_TX_STATUS_ARB_LOST (1 << 1) +#define CEC_TX_STATUS_NACK (1 << 2) +#define CEC_TX_STATUS_LOW_DRIVE (1 << 3) +#define CEC_TX_STATUS_ERROR (1 << 4) +#define CEC_TX_STATUS_MAX_RETRIES (1 << 5) + +#define CEC_RX_STATUS_OK (1 << 0) +#define CEC_RX_STATUS_TIMEOUT (1 << 1) +#define CEC_RX_STATUS_FEATURE_ABORT (1 << 2) + +static inline bool cec_msg_status_is_ok(const struct cec_msg *msg) +{ + if (msg->tx_status && !(msg->tx_status & CEC_TX_STATUS_OK)) + return false; + if (msg->rx_status && !(msg->rx_status & CEC_RX_STATUS_OK)) + return false; + if (!msg->tx_status && !msg->rx_status) + return false; + return !(msg->rx_status & CEC_RX_STATUS_FEATURE_ABORT); +} + +#define CEC_LOG_ADDR_INVALID 0xff +#define CEC_PHYS_ADDR_INVALID 0xffff + +/* + * The maximum number of logical addresses one device can be assigned to. + * The CEC 2.0 spec allows for only 2 logical addresses at the moment. The + * Analog Devices CEC hardware supports 3. So let's go wild and go for 4. + */ +#define CEC_MAX_LOG_ADDRS 4 + +/* The logical addresses defined by CEC 2.0 */ +#define CEC_LOG_ADDR_TV 0 +#define CEC_LOG_ADDR_RECORD_1 1 +#define CEC_LOG_ADDR_RECORD_2 2 +#define CEC_LOG_ADDR_TUNER_1 3 +#define CEC_LOG_ADDR_PLAYBACK_1 4 +#define CEC_LOG_ADDR_AUDIOSYSTEM 5 +#define CEC_LOG_ADDR_TUNER_2 6 +#define CEC_LOG_ADDR_TUNER_3 7 +#define CEC_LOG_ADDR_PLAYBACK_2 8 +#define CEC_LOG_ADDR_RECORD_3 9 +#define CEC_LOG_ADDR_TUNER_4 10 +#define CEC_LOG_ADDR_PLAYBACK_3 11 +#define CEC_LOG_ADDR_BACKUP_1 12 +#define CEC_LOG_ADDR_BACKUP_2 13 +#define CEC_LOG_ADDR_SPECIFIC 14 +#define CEC_LOG_ADDR_UNREGISTERED 15 /* as initiator address */ +#define CEC_LOG_ADDR_BROADCAST 15 /* ad destination address */ + +/* The logical address types that the CEC device wants to claim */ +#define CEC_LOG_ADDR_TYPE_TV 0 +#define CEC_LOG_ADDR_TYPE_RECORD 1 +#define CEC_LOG_ADDR_TYPE_TUNER 2 +#define CEC_LOG_ADDR_TYPE_PLAYBACK 3 +#define CEC_LOG_ADDR_TYPE_AUDIOSYSTEM 4 +#define CEC_LOG_ADDR_TYPE_SPECIFIC 5 +#define CEC_LOG_ADDR_TYPE_UNREGISTERED 6 +/* + * Switches should use UNREGISTERED. + * Processors should use SPECIFIC. + */ + +#define CEC_LOG_ADDR_MASK_TV (1 << CEC_LOG_ADDR_TV) +#define CEC_LOG_ADDR_MASK_RECORD ((1 << CEC_LOG_ADDR_RECORD_1) | \ + (1 << CEC_LOG_ADDR_RECORD_2) | \ + (1 << CEC_LOG_ADDR_RECORD_3)) +#define CEC_LOG_ADDR_MASK_TUNER ((1 << CEC_LOG_ADDR_TUNER_1) | \ + (1 << CEC_LOG_ADDR_TUNER_2) | \ + (1 << CEC_LOG_ADDR_TUNER_3) | \ + (1 << CEC_LOG_ADDR_TUNER_4)) +#define CEC_LOG_ADDR_MASK_PLAYBACK ((1 << CEC_LOG_ADDR_PLAYBACK_1) | \ + (1 << CEC_LOG_ADDR_PLAYBACK_2) | \ + (1 << CEC_LOG_ADDR_PLAYBACK_3)) +#define CEC_LOG_ADDR_MASK_AUDIOSYSTEM (1 << CEC_LOG_ADDR_AUDIOSYSTEM) +#define CEC_LOG_ADDR_MASK_BACKUP ((1 << CEC_LOG_ADDR_BACKUP_1) | \ + (1 << CEC_LOG_ADDR_BACKUP_2)) +#define CEC_LOG_ADDR_MASK_SPECIFIC (1 << CEC_LOG_ADDR_SPECIFIC) +#define CEC_LOG_ADDR_MASK_UNREGISTERED (1 << CEC_LOG_ADDR_UNREGISTERED) + +static inline bool cec_has_tv(__u16 log_addr_mask) +{ + return log_addr_mask & CEC_LOG_ADDR_MASK_TV; +} + +static inline bool cec_has_record(__u16 log_addr_mask) +{ + return log_addr_mask & CEC_LOG_ADDR_MASK_RECORD; +} + +static inline bool cec_has_tuner(__u16 log_addr_mask) +{ + return log_addr_mask & CEC_LOG_ADDR_MASK_TUNER; +} + +static inline bool cec_has_playback(__u16 log_addr_mask) +{ + return log_addr_mask & CEC_LOG_ADDR_MASK_PLAYBACK; +} + +static inline bool cec_has_audiosystem(__u16 log_addr_mask) +{ + return log_addr_mask & CEC_LOG_ADDR_MASK_AUDIOSYSTEM; +} + +static inline bool cec_has_backup(__u16 log_addr_mask) +{ + return log_addr_mask & CEC_LOG_ADDR_MASK_BACKUP; +} + +static inline bool cec_has_specific(__u16 log_addr_mask) +{ + return log_addr_mask & CEC_LOG_ADDR_MASK_SPECIFIC; +} + +static inline bool cec_is_unregistered(__u16 log_addr_mask) +{ + return log_addr_mask & CEC_LOG_ADDR_MASK_UNREGISTERED; +} + +static inline bool cec_is_unconfigured(__u16 log_addr_mask) +{ + return log_addr_mask == 0; +} + +/* + * Use this if there is no vendor ID (CEC_G_VENDOR_ID) or if the vendor ID + * should be disabled (CEC_S_VENDOR_ID) + */ +#define CEC_VENDOR_ID_NONE 0xffffffff + +/* The message handling modes */ +/* Modes for initiator */ +#define CEC_MODE_NO_INITIATOR (0x0 << 0) +#define CEC_MODE_INITIATOR (0x1 << 0) +#define CEC_MODE_EXCL_INITIATOR (0x2 << 0) +#define CEC_MODE_INITIATOR_MSK 0x0f + +/* Modes for follower */ +#define CEC_MODE_NO_FOLLOWER (0x0 << 4) +#define CEC_MODE_FOLLOWER (0x1 << 4) +#define CEC_MODE_EXCL_FOLLOWER (0x2 << 4) +#define CEC_MODE_EXCL_FOLLOWER_PASSTHRU (0x3 << 4) +#define CEC_MODE_MONITOR (0xe << 4) +#define CEC_MODE_MONITOR_ALL (0xf << 4) +#define CEC_MODE_FOLLOWER_MSK 0xf0 + +/* Userspace has to configure the physical address */ +#define CEC_CAP_PHYS_ADDR (1 << 0) +/* Userspace has to configure the logical addresses */ +#define CEC_CAP_LOG_ADDRS (1 << 1) +/* Userspace can transmit messages (and thus become follower as well) */ +#define CEC_CAP_TRANSMIT (1 << 2) +/* + * Passthrough all messages instead of processing them. + */ +#define CEC_CAP_PASSTHROUGH (1 << 3) +/* Supports remote control */ +#define CEC_CAP_RC (1 << 4) +/* Hardware can monitor all messages, not just directed and broadcast. */ +#define CEC_CAP_MONITOR_ALL (1 << 5) + +/** + * struct cec_caps - CEC capabilities structure. + * @driver: name of the CEC device driver. + * @name: name of the CEC device. @driver + @name must be unique. + * @available_log_addrs: number of available logical addresses. + * @capabilities: capabilities of the CEC adapter. + * @version: version of the CEC adapter framework. + */ +struct cec_caps { + char driver[32]; + char name[32]; + __u32 available_log_addrs; + __u32 capabilities; + __u32 version; +}; + +/** + * struct cec_log_addrs - CEC logical addresses structure. + * @log_addr: the claimed logical addresses. Set by the driver. + * @log_addr_mask: current logical address mask. Set by the driver. + * @cec_version: the CEC version that the adapter should implement. Set by the + * caller. + * @num_log_addrs: how many logical addresses should be claimed. Set by the + * caller. + * @vendor_id: the vendor ID of the device. Set by the caller. + * @flags: flags. + * @osd_name: the OSD name of the device. Set by the caller. + * @primary_device_type: the primary device type for each logical address. + * Set by the caller. + * @log_addr_type: the logical address types. Set by the caller. + * @all_device_types: CEC 2.0: all device types represented by the logical + * address. Set by the caller. + * @features: CEC 2.0: The logical address features. Set by the caller. + */ +struct cec_log_addrs { + __u8 log_addr[CEC_MAX_LOG_ADDRS]; + __u16 log_addr_mask; + __u8 cec_version; + __u8 num_log_addrs; + __u32 vendor_id; + __u32 flags; + char osd_name[15]; + __u8 primary_device_type[CEC_MAX_LOG_ADDRS]; + __u8 log_addr_type[CEC_MAX_LOG_ADDRS]; + + /* CEC 2.0 */ + __u8 all_device_types[CEC_MAX_LOG_ADDRS]; + __u8 features[CEC_MAX_LOG_ADDRS][12]; +}; + +/* Allow a fallback to unregistered */ +#define CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK (1 << 0) +/* Passthrough RC messages to the input subsystem */ +#define CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU (1 << 1) +/* CDC-Only device: supports only CDC messages */ +#define CEC_LOG_ADDRS_FL_CDC_ONLY (1 << 2) + +/* Events */ + +/* Event that occurs when the adapter state changes */ +#define CEC_EVENT_STATE_CHANGE 1 +/* + * This event is sent when messages are lost because the application + * didn't empty the message queue in time + */ +#define CEC_EVENT_LOST_MSGS 2 + +#define CEC_EVENT_FL_INITIAL_STATE (1 << 0) + +/** + * struct cec_event_state_change - used when the CEC adapter changes state. + * @phys_addr: the current physical address + * @log_addr_mask: the current logical address mask + */ +struct cec_event_state_change { + __u16 phys_addr; + __u16 log_addr_mask; +}; + +/** + * struct cec_event_lost_msgs - tells you how many messages were lost due. + * @lost_msgs: how many messages were lost. + */ +struct cec_event_lost_msgs { + __u32 lost_msgs; +}; + +/** + * struct cec_event - CEC event structure + * @ts: the timestamp of when the event was sent. + * @event: the event. + * array. + * @state_change: the event payload for CEC_EVENT_STATE_CHANGE. + * @lost_msgs: the event payload for CEC_EVENT_LOST_MSGS. + * @raw: array to pad the union. + */ +struct cec_event { + __u64 ts; + __u32 event; + __u32 flags; + union { + struct cec_event_state_change state_change; + struct cec_event_lost_msgs lost_msgs; + __u32 raw[16]; + }; +}; + +/* ioctls */ + +/* Adapter capabilities */ +#define CEC_ADAP_G_CAPS _IOWR('a', 0, struct cec_caps) + +/* + * phys_addr is either 0 (if this is the CEC root device) + * or a valid physical address obtained from the sink's EDID + * as read by this CEC device (if this is a source device) + * or a physical address obtained and modified from a sink + * EDID and used for a sink CEC device. + * If nothing is connected, then phys_addr is 0xffff. + * See HDMI 1.4b, section 8.7 (Physical Address). + * + * The CEC_ADAP_S_PHYS_ADDR ioctl may not be available if that is handled + * internally. + */ +#define CEC_ADAP_G_PHYS_ADDR _IOR('a', 1, __u16) +#define CEC_ADAP_S_PHYS_ADDR _IOW('a', 2, __u16) + +/* + * Configure the CEC adapter. It sets the device type and which + * logical types it will try to claim. It will return which + * logical addresses it could actually claim. + * An error is returned if the adapter is disabled or if there + * is no physical address assigned. + */ + +#define CEC_ADAP_G_LOG_ADDRS _IOR('a', 3, struct cec_log_addrs) +#define CEC_ADAP_S_LOG_ADDRS _IOWR('a', 4, struct cec_log_addrs) + +/* Transmit/receive a CEC command */ +#define CEC_TRANSMIT _IOWR('a', 5, struct cec_msg) +#define CEC_RECEIVE _IOWR('a', 6, struct cec_msg) + +/* Dequeue CEC events */ +#define CEC_DQEVENT _IOWR('a', 7, struct cec_event) + +/* + * Get and set the message handling mode for this filehandle. + */ +#define CEC_G_MODE _IOR('a', 8, __u32) +#define CEC_S_MODE _IOW('a', 9, __u32) + +/* + * The remainder of this header defines all CEC messages and operands. + * The format matters since it the cec-ctl utility parses it to generate + * code for implementing all these messages. + * + * Comments ending with 'Feature' group messages for each feature. + * If messages are part of multiple features, then the "Has also" + * comment is used to list the previously defined messages that are + * supported by the feature. + * + * Before operands are defined a comment is added that gives the + * name of the operand and in brackets the variable name of the + * corresponding argument in the cec-funcs.h function. + */ + +/* Messages */ + +/* One Touch Play Feature */ +#define CEC_MSG_ACTIVE_SOURCE 0x82 +#define CEC_MSG_IMAGE_VIEW_ON 0x04 +#define CEC_MSG_TEXT_VIEW_ON 0x0d + + +/* Routing Control Feature */ + +/* + * Has also: + * CEC_MSG_ACTIVE_SOURCE + */ + +#define CEC_MSG_INACTIVE_SOURCE 0x9d +#define CEC_MSG_REQUEST_ACTIVE_SOURCE 0x85 +#define CEC_MSG_ROUTING_CHANGE 0x80 +#define CEC_MSG_ROUTING_INFORMATION 0x81 +#define CEC_MSG_SET_STREAM_PATH 0x86 + + +/* Standby Feature */ +#define CEC_MSG_STANDBY 0x36 + + +/* One Touch Record Feature */ +#define CEC_MSG_RECORD_OFF 0x0b +#define CEC_MSG_RECORD_ON 0x09 +/* Record Source Type Operand (rec_src_type) */ +#define CEC_OP_RECORD_SRC_OWN 1 +#define CEC_OP_RECORD_SRC_DIGITAL 2 +#define CEC_OP_RECORD_SRC_ANALOG 3 +#define CEC_OP_RECORD_SRC_EXT_PLUG 4 +#define CEC_OP_RECORD_SRC_EXT_PHYS_ADDR 5 +/* Service Identification Method Operand (service_id_method) */ +#define CEC_OP_SERVICE_ID_METHOD_BY_DIG_ID 0 +#define CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL 1 +/* Digital Service Broadcast System Operand (dig_bcast_system) */ +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN 0x00 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN 0x01 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN 0x02 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS 0x08 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS 0x09 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T 0x0a +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE 0x10 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT 0x11 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T 0x12 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C 0x18 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S 0x19 +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2 0x1a +#define CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T 0x1b +/* Analogue Broadcast Type Operand (ana_bcast_type) */ +#define CEC_OP_ANA_BCAST_TYPE_CABLE 0 +#define CEC_OP_ANA_BCAST_TYPE_SATELLITE 1 +#define CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL 2 +/* Broadcast System Operand (bcast_system) */ +#define CEC_OP_BCAST_SYSTEM_PAL_BG 0x00 +#define CEC_OP_BCAST_SYSTEM_SECAM_LQ 0x01 /* SECAM L' */ +#define CEC_OP_BCAST_SYSTEM_PAL_M 0x02 +#define CEC_OP_BCAST_SYSTEM_NTSC_M 0x03 +#define CEC_OP_BCAST_SYSTEM_PAL_I 0x04 +#define CEC_OP_BCAST_SYSTEM_SECAM_DK 0x05 +#define CEC_OP_BCAST_SYSTEM_SECAM_BG 0x06 +#define CEC_OP_BCAST_SYSTEM_SECAM_L 0x07 +#define CEC_OP_BCAST_SYSTEM_PAL_DK 0x08 +#define CEC_OP_BCAST_SYSTEM_OTHER 0x1f +/* Channel Number Format Operand (channel_number_fmt) */ +#define CEC_OP_CHANNEL_NUMBER_FMT_1_PART 0x01 +#define CEC_OP_CHANNEL_NUMBER_FMT_2_PART 0x02 + +#define CEC_MSG_RECORD_STATUS 0x0a +/* Record Status Operand (rec_status) */ +#define CEC_OP_RECORD_STATUS_CUR_SRC 0x01 +#define CEC_OP_RECORD_STATUS_DIG_SERVICE 0x02 +#define CEC_OP_RECORD_STATUS_ANA_SERVICE 0x03 +#define CEC_OP_RECORD_STATUS_EXT_INPUT 0x04 +#define CEC_OP_RECORD_STATUS_NO_DIG_SERVICE 0x05 +#define CEC_OP_RECORD_STATUS_NO_ANA_SERVICE 0x06 +#define CEC_OP_RECORD_STATUS_NO_SERVICE 0x07 +#define CEC_OP_RECORD_STATUS_INVALID_EXT_PLUG 0x09 +#define CEC_OP_RECORD_STATUS_INVALID_EXT_PHYS_ADDR 0x0a +#define CEC_OP_RECORD_STATUS_UNSUP_CA 0x0b +#define CEC_OP_RECORD_STATUS_NO_CA_ENTITLEMENTS 0x0c +#define CEC_OP_RECORD_STATUS_CANT_COPY_SRC 0x0d +#define CEC_OP_RECORD_STATUS_NO_MORE_COPIES 0x0e +#define CEC_OP_RECORD_STATUS_NO_MEDIA 0x10 +#define CEC_OP_RECORD_STATUS_PLAYING 0x11 +#define CEC_OP_RECORD_STATUS_ALREADY_RECORDING 0x12 +#define CEC_OP_RECORD_STATUS_MEDIA_PROT 0x13 +#define CEC_OP_RECORD_STATUS_NO_SIGNAL 0x14 +#define CEC_OP_RECORD_STATUS_MEDIA_PROBLEM 0x15 +#define CEC_OP_RECORD_STATUS_NO_SPACE 0x16 +#define CEC_OP_RECORD_STATUS_PARENTAL_LOCK 0x17 +#define CEC_OP_RECORD_STATUS_TERMINATED_OK 0x1a +#define CEC_OP_RECORD_STATUS_ALREADY_TERM 0x1b +#define CEC_OP_RECORD_STATUS_OTHER 0x1f + +#define CEC_MSG_RECORD_TV_SCREEN 0x0f + + +/* Timer Programming Feature */ +#define CEC_MSG_CLEAR_ANALOGUE_TIMER 0x33 +/* Recording Sequence Operand (recording_seq) */ +#define CEC_OP_REC_SEQ_SUNDAY 0x01 +#define CEC_OP_REC_SEQ_MONDAY 0x02 +#define CEC_OP_REC_SEQ_TUESDAY 0x04 +#define CEC_OP_REC_SEQ_WEDNESDAY 0x08 +#define CEC_OP_REC_SEQ_THURSDAY 0x10 +#define CEC_OP_REC_SEQ_FRIDAY 0x20 +#define CEC_OP_REC_SEQ_SATERDAY 0x40 +#define CEC_OP_REC_SEQ_ONCE_ONLY 0x00 + +#define CEC_MSG_CLEAR_DIGITAL_TIMER 0x99 + +#define CEC_MSG_CLEAR_EXT_TIMER 0xa1 +/* External Source Specifier Operand (ext_src_spec) */ +#define CEC_OP_EXT_SRC_PLUG 0x04 +#define CEC_OP_EXT_SRC_PHYS_ADDR 0x05 + +#define CEC_MSG_SET_ANALOGUE_TIMER 0x34 +#define CEC_MSG_SET_DIGITAL_TIMER 0x97 +#define CEC_MSG_SET_EXT_TIMER 0xa2 + +#define CEC_MSG_SET_TIMER_PROGRAM_TITLE 0x67 +#define CEC_MSG_TIMER_CLEARED_STATUS 0x43 +/* Timer Cleared Status Data Operand (timer_cleared_status) */ +#define CEC_OP_TIMER_CLR_STAT_RECORDING 0x00 +#define CEC_OP_TIMER_CLR_STAT_NO_MATCHING 0x01 +#define CEC_OP_TIMER_CLR_STAT_NO_INFO 0x02 +#define CEC_OP_TIMER_CLR_STAT_CLEARED 0x80 + +#define CEC_MSG_TIMER_STATUS 0x35 +/* Timer Overlap Warning Operand (timer_overlap_warning) */ +#define CEC_OP_TIMER_OVERLAP_WARNING_NO_OVERLAP 0 +#define CEC_OP_TIMER_OVERLAP_WARNING_OVERLAP 1 +/* Media Info Operand (media_info) */ +#define CEC_OP_MEDIA_INFO_UNPROT_MEDIA 0 +#define CEC_OP_MEDIA_INFO_PROT_MEDIA 1 +#define CEC_OP_MEDIA_INFO_NO_MEDIA 2 +/* Programmed Indicator Operand (prog_indicator) */ +#define CEC_OP_PROG_IND_NOT_PROGRAMMED 0 +#define CEC_OP_PROG_IND_PROGRAMMED 1 +/* Programmed Info Operand (prog_info) */ +#define CEC_OP_PROG_INFO_ENOUGH_SPACE 0x08 +#define CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE 0x09 +#define CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE 0x0b +#define CEC_OP_PROG_INFO_NONE_AVAILABLE 0x0a +/* Not Programmed Error Info Operand (prog_error) */ +#define CEC_OP_PROG_ERROR_NO_FREE_TIMER 0x01 +#define CEC_OP_PROG_ERROR_DATE_OUT_OF_RANGE 0x02 +#define CEC_OP_PROG_ERROR_REC_SEQ_ERROR 0x03 +#define CEC_OP_PROG_ERROR_INV_EXT_PLUG 0x04 +#define CEC_OP_PROG_ERROR_INV_EXT_PHYS_ADDR 0x05 +#define CEC_OP_PROG_ERROR_CA_UNSUPP 0x06 +#define CEC_OP_PROG_ERROR_INSUF_CA_ENTITLEMENTS 0x07 +#define CEC_OP_PROG_ERROR_RESOLUTION_UNSUPP 0x08 +#define CEC_OP_PROG_ERROR_PARENTAL_LOCK 0x09 +#define CEC_OP_PROG_ERROR_CLOCK_FAILURE 0x0a +#define CEC_OP_PROG_ERROR_DUPLICATE 0x0e + + +/* System Information Feature */ +#define CEC_MSG_CEC_VERSION 0x9e +/* CEC Version Operand (cec_version) */ +#define CEC_OP_CEC_VERSION_1_3A 4 +#define CEC_OP_CEC_VERSION_1_4 5 +#define CEC_OP_CEC_VERSION_2_0 6 + +#define CEC_MSG_GET_CEC_VERSION 0x9f +#define CEC_MSG_GIVE_PHYSICAL_ADDR 0x83 +#define CEC_MSG_GET_MENU_LANGUAGE 0x91 +#define CEC_MSG_REPORT_PHYSICAL_ADDR 0x84 +/* Primary Device Type Operand (prim_devtype) */ +#define CEC_OP_PRIM_DEVTYPE_TV 0 +#define CEC_OP_PRIM_DEVTYPE_RECORD 1 +#define CEC_OP_PRIM_DEVTYPE_TUNER 3 +#define CEC_OP_PRIM_DEVTYPE_PLAYBACK 4 +#define CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM 5 +#define CEC_OP_PRIM_DEVTYPE_SWITCH 6 +#define CEC_OP_PRIM_DEVTYPE_PROCESSOR 7 + +#define CEC_MSG_SET_MENU_LANGUAGE 0x32 +#define CEC_MSG_REPORT_FEATURES 0xa6 /* HDMI 2.0 */ +/* All Device Types Operand (all_device_types) */ +#define CEC_OP_ALL_DEVTYPE_TV 0x80 +#define CEC_OP_ALL_DEVTYPE_RECORD 0x40 +#define CEC_OP_ALL_DEVTYPE_TUNER 0x20 +#define CEC_OP_ALL_DEVTYPE_PLAYBACK 0x10 +#define CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM 0x08 +#define CEC_OP_ALL_DEVTYPE_SWITCH 0x04 +/* + * And if you wondering what happened to PROCESSOR devices: those should + * be mapped to a SWITCH. + */ + +/* Valid for RC Profile and Device Feature operands */ +#define CEC_OP_FEAT_EXT 0x80 /* Extension bit */ +/* RC Profile Operand (rc_profile) */ +#define CEC_OP_FEAT_RC_TV_PROFILE_NONE 0x00 +#define CEC_OP_FEAT_RC_TV_PROFILE_1 0x02 +#define CEC_OP_FEAT_RC_TV_PROFILE_2 0x06 +#define CEC_OP_FEAT_RC_TV_PROFILE_3 0x0a +#define CEC_OP_FEAT_RC_TV_PROFILE_4 0x0e +#define CEC_OP_FEAT_RC_SRC_HAS_DEV_ROOT_MENU 0x50 +#define CEC_OP_FEAT_RC_SRC_HAS_DEV_SETUP_MENU 0x48 +#define CEC_OP_FEAT_RC_SRC_HAS_CONTENTS_MENU 0x44 +#define CEC_OP_FEAT_RC_SRC_HAS_MEDIA_TOP_MENU 0x42 +#define CEC_OP_FEAT_RC_SRC_HAS_MEDIA_CONTEXT_MENU 0x41 +/* Device Feature Operand (dev_features) */ +#define CEC_OP_FEAT_DEV_HAS_RECORD_TV_SCREEN 0x40 +#define CEC_OP_FEAT_DEV_HAS_SET_OSD_STRING 0x20 +#define CEC_OP_FEAT_DEV_HAS_DECK_CONTROL 0x10 +#define CEC_OP_FEAT_DEV_HAS_SET_AUDIO_RATE 0x08 +#define CEC_OP_FEAT_DEV_SINK_HAS_ARC_TX 0x04 +#define CEC_OP_FEAT_DEV_SOURCE_HAS_ARC_RX 0x02 + +#define CEC_MSG_GIVE_FEATURES 0xa5 /* HDMI 2.0 */ + + +/* Deck Control Feature */ +#define CEC_MSG_DECK_CONTROL 0x42 +/* Deck Control Mode Operand (deck_control_mode) */ +#define CEC_OP_DECK_CTL_MODE_SKIP_FWD 1 +#define CEC_OP_DECK_CTL_MODE_SKIP_REV 2 +#define CEC_OP_DECK_CTL_MODE_STOP 3 +#define CEC_OP_DECK_CTL_MODE_EJECT 4 + +#define CEC_MSG_DECK_STATUS 0x1b +/* Deck Info Operand (deck_info) */ +#define CEC_OP_DECK_INFO_PLAY 0x11 +#define CEC_OP_DECK_INFO_RECORD 0x12 +#define CEC_OP_DECK_INFO_PLAY_REV 0x13 +#define CEC_OP_DECK_INFO_STILL 0x14 +#define CEC_OP_DECK_INFO_SLOW 0x15 +#define CEC_OP_DECK_INFO_SLOW_REV 0x16 +#define CEC_OP_DECK_INFO_FAST_FWD 0x17 +#define CEC_OP_DECK_INFO_FAST_REV 0x18 +#define CEC_OP_DECK_INFO_NO_MEDIA 0x19 +#define CEC_OP_DECK_INFO_STOP 0x1a +#define CEC_OP_DECK_INFO_SKIP_FWD 0x1b +#define CEC_OP_DECK_INFO_SKIP_REV 0x1c +#define CEC_OP_DECK_INFO_INDEX_SEARCH_FWD 0x1d +#define CEC_OP_DECK_INFO_INDEX_SEARCH_REV 0x1e +#define CEC_OP_DECK_INFO_OTHER 0x1f + +#define CEC_MSG_GIVE_DECK_STATUS 0x1a +/* Status Request Operand (status_req) */ +#define CEC_OP_STATUS_REQ_ON 1 +#define CEC_OP_STATUS_REQ_OFF 2 +#define CEC_OP_STATUS_REQ_ONCE 3 + +#define CEC_MSG_PLAY 0x41 +/* Play Mode Operand (play_mode) */ +#define CEC_OP_PLAY_MODE_PLAY_FWD 0x24 +#define CEC_OP_PLAY_MODE_PLAY_REV 0x20 +#define CEC_OP_PLAY_MODE_PLAY_STILL 0x25 +#define CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MIN 0x05 +#define CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MED 0x06 +#define CEC_OP_PLAY_MODE_PLAY_FAST_FWD_MAX 0x07 +#define CEC_OP_PLAY_MODE_PLAY_FAST_REV_MIN 0x09 +#define CEC_OP_PLAY_MODE_PLAY_FAST_REV_MED 0x0a +#define CEC_OP_PLAY_MODE_PLAY_FAST_REV_MAX 0x0b +#define CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MIN 0x15 +#define CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MED 0x16 +#define CEC_OP_PLAY_MODE_PLAY_SLOW_FWD_MAX 0x17 +#define CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MIN 0x19 +#define CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MED 0x1a +#define CEC_OP_PLAY_MODE_PLAY_SLOW_REV_MAX 0x1b + + +/* Tuner Control Feature */ +#define CEC_MSG_GIVE_TUNER_DEVICE_STATUS 0x08 +#define CEC_MSG_SELECT_ANALOGUE_SERVICE 0x92 +#define CEC_MSG_SELECT_DIGITAL_SERVICE 0x93 +#define CEC_MSG_TUNER_DEVICE_STATUS 0x07 +/* Recording Flag Operand (rec_flag) */ +#define CEC_OP_REC_FLAG_USED 0 +#define CEC_OP_REC_FLAG_NOT_USED 1 +/* Tuner Display Info Operand (tuner_display_info) */ +#define CEC_OP_TUNER_DISPLAY_INFO_DIGITAL 0 +#define CEC_OP_TUNER_DISPLAY_INFO_NONE 1 +#define CEC_OP_TUNER_DISPLAY_INFO_ANALOGUE 2 + +#define CEC_MSG_TUNER_STEP_DECREMENT 0x06 +#define CEC_MSG_TUNER_STEP_INCREMENT 0x05 + + +/* Vendor Specific Commands Feature */ + +/* + * Has also: + * CEC_MSG_CEC_VERSION + * CEC_MSG_GET_CEC_VERSION + */ +#define CEC_MSG_DEVICE_VENDOR_ID 0x87 +#define CEC_MSG_GIVE_DEVICE_VENDOR_ID 0x8c +#define CEC_MSG_VENDOR_COMMAND 0x89 +#define CEC_MSG_VENDOR_COMMAND_WITH_ID 0xa0 +#define CEC_MSG_VENDOR_REMOTE_BUTTON_DOWN 0x8a +#define CEC_MSG_VENDOR_REMOTE_BUTTON_UP 0x8b + + +/* OSD Display Feature */ +#define CEC_MSG_SET_OSD_STRING 0x64 +/* Display Control Operand (disp_ctl) */ +#define CEC_OP_DISP_CTL_DEFAULT 0x00 +#define CEC_OP_DISP_CTL_UNTIL_CLEARED 0x40 +#define CEC_OP_DISP_CTL_CLEAR 0x80 + + +/* Device OSD Transfer Feature */ +#define CEC_MSG_GIVE_OSD_NAME 0x46 +#define CEC_MSG_SET_OSD_NAME 0x47 + + +/* Device Menu Control Feature */ +#define CEC_MSG_MENU_REQUEST 0x8d +/* Menu Request Type Operand (menu_req) */ +#define CEC_OP_MENU_REQUEST_ACTIVATE 0x00 +#define CEC_OP_MENU_REQUEST_DEACTIVATE 0x01 +#define CEC_OP_MENU_REQUEST_QUERY 0x02 + +#define CEC_MSG_MENU_STATUS 0x8e +/* Menu State Operand (menu_state) */ +#define CEC_OP_MENU_STATE_ACTIVATED 0x00 +#define CEC_OP_MENU_STATE_DEACTIVATED 0x01 + +#define CEC_MSG_USER_CONTROL_PRESSED 0x44 +/* UI Broadcast Type Operand (ui_bcast_type) */ +#define CEC_OP_UI_BCAST_TYPE_TOGGLE_ALL 0x00 +#define CEC_OP_UI_BCAST_TYPE_TOGGLE_DIG_ANA 0x01 +#define CEC_OP_UI_BCAST_TYPE_ANALOGUE 0x10 +#define CEC_OP_UI_BCAST_TYPE_ANALOGUE_T 0x20 +#define CEC_OP_UI_BCAST_TYPE_ANALOGUE_CABLE 0x30 +#define CEC_OP_UI_BCAST_TYPE_ANALOGUE_SAT 0x40 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL 0x50 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL_T 0x60 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL_CABLE 0x70 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL_SAT 0x80 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL_COM_SAT 0x90 +#define CEC_OP_UI_BCAST_TYPE_DIGITAL_COM_SAT2 0x91 +#define CEC_OP_UI_BCAST_TYPE_IP 0xa0 +/* UI Sound Presentation Control Operand (ui_snd_pres_ctl) */ +#define CEC_OP_UI_SND_PRES_CTL_DUAL_MONO 0x10 +#define CEC_OP_UI_SND_PRES_CTL_KARAOKE 0x20 +#define CEC_OP_UI_SND_PRES_CTL_DOWNMIX 0x80 +#define CEC_OP_UI_SND_PRES_CTL_REVERB 0x90 +#define CEC_OP_UI_SND_PRES_CTL_EQUALIZER 0xa0 +#define CEC_OP_UI_SND_PRES_CTL_BASS_UP 0xb1 +#define CEC_OP_UI_SND_PRES_CTL_BASS_NEUTRAL 0xb2 +#define CEC_OP_UI_SND_PRES_CTL_BASS_DOWN 0xb3 +#define CEC_OP_UI_SND_PRES_CTL_TREBLE_UP 0xc1 +#define CEC_OP_UI_SND_PRES_CTL_TREBLE_NEUTRAL 0xc2 +#define CEC_OP_UI_SND_PRES_CTL_TREBLE_DOWN 0xc3 + +#define CEC_MSG_USER_CONTROL_RELEASED 0x45 + + +/* Remote Control Passthrough Feature */ + +/* + * Has also: + * CEC_MSG_USER_CONTROL_PRESSED + * CEC_MSG_USER_CONTROL_RELEASED + */ + + +/* Power Status Feature */ +#define CEC_MSG_GIVE_DEVICE_POWER_STATUS 0x8f +#define CEC_MSG_REPORT_POWER_STATUS 0x90 +/* Power Status Operand (pwr_state) */ +#define CEC_OP_POWER_STATUS_ON 0 +#define CEC_OP_POWER_STATUS_STANDBY 1 +#define CEC_OP_POWER_STATUS_TO_ON 2 +#define CEC_OP_POWER_STATUS_TO_STANDBY 3 + + +/* General Protocol Messages */ +#define CEC_MSG_FEATURE_ABORT 0x00 +/* Abort Reason Operand (reason) */ +#define CEC_OP_ABORT_UNRECOGNIZED_OP 0 +#define CEC_OP_ABORT_INCORRECT_MODE 1 +#define CEC_OP_ABORT_NO_SOURCE 2 +#define CEC_OP_ABORT_INVALID_OP 3 +#define CEC_OP_ABORT_REFUSED 4 +#define CEC_OP_ABORT_UNDETERMINED 5 + +#define CEC_MSG_ABORT 0xff + + +/* System Audio Control Feature */ + +/* + * Has also: + * CEC_MSG_USER_CONTROL_PRESSED + * CEC_MSG_USER_CONTROL_RELEASED + */ +#define CEC_MSG_GIVE_AUDIO_STATUS 0x71 +#define CEC_MSG_GIVE_SYSTEM_AUDIO_MODE_STATUS 0x7d +#define CEC_MSG_REPORT_AUDIO_STATUS 0x7a +/* Audio Mute Status Operand (aud_mute_status) */ +#define CEC_OP_AUD_MUTE_STATUS_OFF 0 +#define CEC_OP_AUD_MUTE_STATUS_ON 1 + +#define CEC_MSG_REPORT_SHORT_AUDIO_DESCRIPTOR 0xa3 +#define CEC_MSG_REQUEST_SHORT_AUDIO_DESCRIPTOR 0xa4 +#define CEC_MSG_SET_SYSTEM_AUDIO_MODE 0x72 +/* System Audio Status Operand (sys_aud_status) */ +#define CEC_OP_SYS_AUD_STATUS_OFF 0 +#define CEC_OP_SYS_AUD_STATUS_ON 1 + +#define CEC_MSG_SYSTEM_AUDIO_MODE_REQUEST 0x70 +#define CEC_MSG_SYSTEM_AUDIO_MODE_STATUS 0x7e +/* Audio Format ID Operand (audio_format_id) */ +#define CEC_OP_AUD_FMT_ID_CEA861 0 +#define CEC_OP_AUD_FMT_ID_CEA861_CXT 1 + + +/* Audio Rate Control Feature */ +#define CEC_MSG_SET_AUDIO_RATE 0x9a +/* Audio Rate Operand (audio_rate) */ +#define CEC_OP_AUD_RATE_OFF 0 +#define CEC_OP_AUD_RATE_WIDE_STD 1 +#define CEC_OP_AUD_RATE_WIDE_FAST 2 +#define CEC_OP_AUD_RATE_WIDE_SLOW 3 +#define CEC_OP_AUD_RATE_NARROW_STD 4 +#define CEC_OP_AUD_RATE_NARROW_FAST 5 +#define CEC_OP_AUD_RATE_NARROW_SLOW 6 + + +/* Audio Return Channel Control Feature */ +#define CEC_MSG_INITIATE_ARC 0xc0 +#define CEC_MSG_REPORT_ARC_INITIATED 0xc1 +#define CEC_MSG_REPORT_ARC_TERMINATED 0xc2 +#define CEC_MSG_REQUEST_ARC_INITIATION 0xc3 +#define CEC_MSG_REQUEST_ARC_TERMINATION 0xc4 +#define CEC_MSG_TERMINATE_ARC 0xc5 + + +/* Dynamic Audio Lipsync Feature */ +/* Only for CEC 2.0 and up */ +#define CEC_MSG_REQUEST_CURRENT_LATENCY 0xa7 +#define CEC_MSG_REPORT_CURRENT_LATENCY 0xa8 +/* Low Latency Mode Operand (low_latency_mode) */ +#define CEC_OP_LOW_LATENCY_MODE_OFF 0 +#define CEC_OP_LOW_LATENCY_MODE_ON 1 +/* Audio Output Compensated Operand (audio_out_compensated) */ +#define CEC_OP_AUD_OUT_COMPENSATED_NA 0 +#define CEC_OP_AUD_OUT_COMPENSATED_DELAY 1 +#define CEC_OP_AUD_OUT_COMPENSATED_NO_DELAY 2 +#define CEC_OP_AUD_OUT_COMPENSATED_PARTIAL_DELAY 3 + + +/* Capability Discovery and Control Feature */ +#define CEC_MSG_CDC_MESSAGE 0xf8 +/* Ethernet-over-HDMI: nobody ever does this... */ +#define CEC_MSG_CDC_HEC_INQUIRE_STATE 0x00 +#define CEC_MSG_CDC_HEC_REPORT_STATE 0x01 +/* HEC Functionality State Operand (hec_func_state) */ +#define CEC_OP_HEC_FUNC_STATE_NOT_SUPPORTED 0 +#define CEC_OP_HEC_FUNC_STATE_INACTIVE 1 +#define CEC_OP_HEC_FUNC_STATE_ACTIVE 2 +#define CEC_OP_HEC_FUNC_STATE_ACTIVATION_FIELD 3 +/* Host Functionality State Operand (host_func_state) */ +#define CEC_OP_HOST_FUNC_STATE_NOT_SUPPORTED 0 +#define CEC_OP_HOST_FUNC_STATE_INACTIVE 1 +#define CEC_OP_HOST_FUNC_STATE_ACTIVE 2 +/* ENC Functionality State Operand (enc_func_state) */ +#define CEC_OP_ENC_FUNC_STATE_EXT_CON_NOT_SUPPORTED 0 +#define CEC_OP_ENC_FUNC_STATE_EXT_CON_INACTIVE 1 +#define CEC_OP_ENC_FUNC_STATE_EXT_CON_ACTIVE 2 +/* CDC Error Code Operand (cdc_errcode) */ +#define CEC_OP_CDC_ERROR_CODE_NONE 0 +#define CEC_OP_CDC_ERROR_CODE_CAP_UNSUPPORTED 1 +#define CEC_OP_CDC_ERROR_CODE_WRONG_STATE 2 +#define CEC_OP_CDC_ERROR_CODE_OTHER 3 +/* HEC Support Operand (hec_support) */ +#define CEC_OP_HEC_SUPPORT_NO 0 +#define CEC_OP_HEC_SUPPORT_YES 1 +/* HEC Activation Operand (hec_activation) */ +#define CEC_OP_HEC_ACTIVATION_ON 0 +#define CEC_OP_HEC_ACTIVATION_OFF 1 + +#define CEC_MSG_CDC_HEC_SET_STATE_ADJACENT 0x02 +#define CEC_MSG_CDC_HEC_SET_STATE 0x03 +/* HEC Set State Operand (hec_set_state) */ +#define CEC_OP_HEC_SET_STATE_DEACTIVATE 0 +#define CEC_OP_HEC_SET_STATE_ACTIVATE 1 + +#define CEC_MSG_CDC_HEC_REQUEST_DEACTIVATION 0x04 +#define CEC_MSG_CDC_HEC_NOTIFY_ALIVE 0x05 +#define CEC_MSG_CDC_HEC_DISCOVER 0x06 +/* Hotplug Detect messages */ +#define CEC_MSG_CDC_HPD_SET_STATE 0x10 +/* HPD State Operand (hpd_state) */ +#define CEC_OP_HPD_STATE_CP_EDID_DISABLE 0 +#define CEC_OP_HPD_STATE_CP_EDID_ENABLE 1 +#define CEC_OP_HPD_STATE_CP_EDID_DISABLE_ENABLE 2 +#define CEC_OP_HPD_STATE_EDID_DISABLE 3 +#define CEC_OP_HPD_STATE_EDID_ENABLE 4 +#define CEC_OP_HPD_STATE_EDID_DISABLE_ENABLE 5 +#define CEC_MSG_CDC_HPD_REPORT_STATE 0x11 +/* HPD Error Code Operand (hpd_error) */ +#define CEC_OP_HPD_ERROR_NONE 0 +#define CEC_OP_HPD_ERROR_INITIATOR_NOT_CAPABLE 1 +#define CEC_OP_HPD_ERROR_INITIATOR_WRONG_STATE 2 +#define CEC_OP_HPD_ERROR_OTHER 3 +#define CEC_OP_HPD_ERROR_NONE_NO_VIDEO 4 + +/* End of Messages */ + +/* Helper functions to identify the 'special' CEC devices */ + +static inline bool cec_is_2nd_tv(const struct cec_log_addrs *las) +{ + /* + * It is a second TV if the logical address is 14 or 15 and the + * primary device type is a TV. + */ + return las->num_log_addrs && + las->log_addr[0] >= CEC_LOG_ADDR_SPECIFIC && + las->primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_TV; +} + +static inline bool cec_is_processor(const struct cec_log_addrs *las) +{ + /* + * It is a processor if the logical address is 12-15 and the + * primary device type is a Processor. + */ + return las->num_log_addrs && + las->log_addr[0] >= CEC_LOG_ADDR_BACKUP_1 && + las->primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_PROCESSOR; +} + +static inline bool cec_is_switch(const struct cec_log_addrs *las) +{ + /* + * It is a switch if the logical address is 15 and the + * primary device type is a Switch and the CDC-Only flag is not set. + */ + return las->num_log_addrs == 1 && + las->log_addr[0] == CEC_LOG_ADDR_UNREGISTERED && + las->primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_SWITCH && + !(las->flags & CEC_LOG_ADDRS_FL_CDC_ONLY); +} + +static inline bool cec_is_cdc_only(const struct cec_log_addrs *las) +{ + /* + * It is a CDC-only device if the logical address is 15 and the + * primary device type is a Switch and the CDC-Only flag is set. + */ + return las->num_log_addrs == 1 && + las->log_addr[0] == CEC_LOG_ADDR_UNREGISTERED && + las->primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_SWITCH && + (las->flags & CEC_LOG_ADDRS_FL_CDC_ONLY); +} + +#endif -- cgit v1.3-7-g2ca7 From e381322b0190c1253d347de3f28b5c37756fb651 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 16 Sep 2016 14:16:48 -0500 Subject: leds: Introduce userspace LED class driver This driver creates a userspace leds driver similar to uinput. New LEDs are created by opening /dev/uleds and writing a uleds_user_dev struct. A new LED class device is registered with the name given in the struct. Reading will return a single byte that is the current brightness. The poll() syscall is also supported. It will be triggered whenever the brightness changes. Closing the file handle to /dev/uleds will remove the leds class device. Signed-off-by: David Lechner Signed-off-by: Jacek Anaszewski --- Documentation/leds/uleds.txt | 36 +++++++ drivers/leds/Kconfig | 8 ++ drivers/leds/Makefile | 3 + drivers/leds/uleds.c | 235 +++++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/Kbuild | 1 + include/uapi/linux/uleds.h | 24 +++++ 6 files changed, 307 insertions(+) create mode 100644 Documentation/leds/uleds.txt create mode 100644 drivers/leds/uleds.c create mode 100644 include/uapi/linux/uleds.h (limited to 'include/uapi/linux/Kbuild') diff --git a/Documentation/leds/uleds.txt b/Documentation/leds/uleds.txt new file mode 100644 index 000000000000..13e375a580f9 --- /dev/null +++ b/Documentation/leds/uleds.txt @@ -0,0 +1,36 @@ +Userspace LEDs +============== + +The uleds driver supports userspace LEDs. This can be useful for testing +triggers and can also be used to implement virtual LEDs. + + +Usage +===== + +When the driver is loaded, a character device is created at /dev/uleds. To +create a new LED class device, open /dev/uleds and write a uleds_user_dev +structure to it (found in kernel public header file linux/uleds.h). + + #define LED_MAX_NAME_SIZE 64 + + struct uleds_user_dev { + char name[LED_MAX_NAME_SIZE]; + }; + +A new LED class device will be created with the name given. The name can be +any valid sysfs device node name, but consider using the LED class naming +convention of "devicename:color:function". + +The current brightness is found by reading a single byte from the character +device. Values are unsigned: 0 to 255. Reading will block until the brightness +changes. The device node can also be polled to notify when the brightness value +changes. + +The LED class device will be removed when the open file handle to /dev/uleds +is closed. + +Multiple LED class devices are created by opening additional file handles to +/dev/uleds. + +See tools/leds/uledmon.c for an example userspace program. diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 7a628c6516f6..5fd3f4ca0c3c 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -659,6 +659,14 @@ config LEDS_MLXCPLD This option enabled support for the LEDs on the Mellanox boards. Say Y to enabled these. +config LEDS_USER + tristate "Userspace LED support" + depends on LEDS_CLASS + help + This option enables support for userspace LEDs. Say 'y' to enable this + support in kernel. To compile this driver as a module, choose 'm' here: + the module will be called uleds. + comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 3965070190f5..d5331ff5b563 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -75,5 +75,8 @@ obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o +# LED Userspace Drivers +obj-$(CONFIG_LEDS_USER) += uleds.o + # LED Triggers obj-$(CONFIG_LEDS_TRIGGERS) += trigger/ diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c new file mode 100644 index 000000000000..5e9e8a1fdefb --- /dev/null +++ b/drivers/leds/uleds.c @@ -0,0 +1,235 @@ +/* + * Userspace driver for the LED subsystem + * + * Copyright (C) 2016 David Lechner + * + * Based on uinput.c: Aristeu Sergio Rozanski Filho + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ULEDS_NAME "uleds" + +enum uleds_state { + ULEDS_STATE_UNKNOWN, + ULEDS_STATE_REGISTERED, +}; + +struct uleds_device { + struct uleds_user_dev user_dev; + struct led_classdev led_cdev; + struct mutex mutex; + enum uleds_state state; + wait_queue_head_t waitq; + int brightness; + bool new_data; +}; + +static struct miscdevice uleds_misc; + +static void uleds_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct uleds_device *udev = container_of(led_cdev, struct uleds_device, + led_cdev); + + if (udev->brightness != brightness) { + udev->brightness = brightness; + udev->new_data = true; + wake_up_interruptible(&udev->waitq); + } +} + +static int uleds_open(struct inode *inode, struct file *file) +{ + struct uleds_device *udev; + + udev = kzalloc(sizeof(*udev), GFP_KERNEL); + if (!udev) + return -ENOMEM; + + udev->led_cdev.name = udev->user_dev.name; + udev->led_cdev.brightness_set = uleds_brightness_set; + + mutex_init(&udev->mutex); + init_waitqueue_head(&udev->waitq); + udev->state = ULEDS_STATE_UNKNOWN; + + file->private_data = udev; + nonseekable_open(inode, file); + + return 0; +} + +static ssize_t uleds_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct uleds_device *udev = file->private_data; + const char *name; + int ret; + + if (count == 0) + return 0; + + ret = mutex_lock_interruptible(&udev->mutex); + if (ret) + return ret; + + if (udev->state == ULEDS_STATE_REGISTERED) { + ret = -EBUSY; + goto out; + } + + if (count != sizeof(struct uleds_user_dev)) { + ret = -EINVAL; + goto out; + } + + if (copy_from_user(&udev->user_dev, buffer, + sizeof(struct uleds_user_dev))) { + ret = -EFAULT; + goto out; + } + + name = udev->user_dev.name; + if (!name[0] || !strcmp(name, ".") || !strcmp(name, "..") || + strchr(name, '/')) { + ret = -EINVAL; + goto out; + } + + if (udev->user_dev.max_brightness <= 0) { + ret = -EINVAL; + goto out; + } + udev->led_cdev.max_brightness = udev->user_dev.max_brightness; + + ret = devm_led_classdev_register(uleds_misc.this_device, + &udev->led_cdev); + if (ret < 0) + goto out; + + udev->new_data = true; + udev->state = ULEDS_STATE_REGISTERED; + ret = count; + +out: + mutex_unlock(&udev->mutex); + + return ret; +} + +static ssize_t uleds_read(struct file *file, char __user *buffer, size_t count, + loff_t *ppos) +{ + struct uleds_device *udev = file->private_data; + ssize_t retval; + + if (count < sizeof(udev->brightness)) + return 0; + + do { + retval = mutex_lock_interruptible(&udev->mutex); + if (retval) + return retval; + + if (udev->state != ULEDS_STATE_REGISTERED) { + retval = -ENODEV; + } else if (!udev->new_data && (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + } else if (udev->new_data) { + retval = copy_to_user(buffer, &udev->brightness, + sizeof(udev->brightness)); + udev->new_data = false; + retval = sizeof(udev->brightness); + } + + mutex_unlock(&udev->mutex); + + if (retval) + break; + + if (!(file->f_flags & O_NONBLOCK)) + retval = wait_event_interruptible(udev->waitq, + udev->new_data || + udev->state != ULEDS_STATE_REGISTERED); + } while (retval == 0); + + return retval; +} + +static unsigned int uleds_poll(struct file *file, poll_table *wait) +{ + struct uleds_device *udev = file->private_data; + + poll_wait(file, &udev->waitq, wait); + + if (udev->new_data) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int uleds_release(struct inode *inode, struct file *file) +{ + struct uleds_device *udev = file->private_data; + + if (udev->state == ULEDS_STATE_REGISTERED) { + udev->state = ULEDS_STATE_UNKNOWN; + devm_led_classdev_unregister(uleds_misc.this_device, + &udev->led_cdev); + } + kfree(udev); + + return 0; +} + +static const struct file_operations uleds_fops = { + .owner = THIS_MODULE, + .open = uleds_open, + .release = uleds_release, + .read = uleds_read, + .write = uleds_write, + .poll = uleds_poll, + .llseek = no_llseek, +}; + +static struct miscdevice uleds_misc = { + .fops = &uleds_fops, + .minor = MISC_DYNAMIC_MINOR, + .name = ULEDS_NAME, +}; + +static int __init uleds_init(void) +{ + return misc_register(&uleds_misc); +} +module_init(uleds_init); + +static void __exit uleds_exit(void) +{ + misc_deregister(&uleds_misc); +} +module_exit(uleds_exit); + +MODULE_AUTHOR("David Lechner "); +MODULE_DESCRIPTION("Userspace driver for the LED subsystem"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index 6965d0909554..f196c539021e 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -425,6 +425,7 @@ header-y += udp.h header-y += uhid.h header-y += uinput.h header-y += uio.h +header-y += uleds.h header-y += ultrasound.h header-y += un.h header-y += unistd.h diff --git a/include/uapi/linux/uleds.h b/include/uapi/linux/uleds.h new file mode 100644 index 000000000000..95186578c46e --- /dev/null +++ b/include/uapi/linux/uleds.h @@ -0,0 +1,24 @@ +/* + * Userspace driver support for the LED subsystem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _UAPI__ULEDS_H_ +#define _UAPI__ULEDS_H_ + +#define LED_MAX_NAME_SIZE 64 + +struct uleds_user_dev { + char name[LED_MAX_NAME_SIZE]; + int max_brightness; +}; + +#endif /* _UAPI__ULEDS_H_ */ -- cgit v1.3-7-g2ca7 From dbaf0624ffa57ae6e7d87a823185ccd9a7852d3c Mon Sep 17 00:00:00 2001 From: Gonglei Date: Thu, 15 Dec 2016 10:03:16 +0800 Subject: crypto: add virtio-crypto driver This patch introduces virtio-crypto driver for Linux Kernel. The virtio crypto device is a virtual cryptography device as well as a kind of virtual hardware accelerator for virtual machines. The encryption anddecryption requests are placed in the data queue and are ultimately handled by thebackend crypto accelerators. The second queue is the control queue used to create or destroy sessions for symmetric algorithms and will control some advanced features in the future. The virtio crypto device provides the following cryptoservices: CIPHER, MAC, HASH, and AEAD. For more information about virtio-crypto device, please see: http://qemu-project.org/Features/VirtioCrypto CC: Michael S. Tsirkin CC: Cornelia Huck CC: Stefan Hajnoczi CC: Herbert Xu CC: Halil Pasic CC: David S. Miller CC: Zeng Xin Signed-off-by: Gonglei Signed-off-by: Michael S. Tsirkin --- MAINTAINERS | 9 + drivers/crypto/Kconfig | 2 + drivers/crypto/Makefile | 1 + drivers/crypto/virtio/Kconfig | 10 + drivers/crypto/virtio/Makefile | 5 + drivers/crypto/virtio/virtio_crypto_algs.c | 540 +++++++++++++++++++++++++++ drivers/crypto/virtio/virtio_crypto_common.h | 128 +++++++ drivers/crypto/virtio/virtio_crypto_core.c | 476 +++++++++++++++++++++++ drivers/crypto/virtio/virtio_crypto_mgr.c | 264 +++++++++++++ include/uapi/linux/Kbuild | 1 + include/uapi/linux/virtio_crypto.h | 450 ++++++++++++++++++++++ include/uapi/linux/virtio_ids.h | 1 + 12 files changed, 1887 insertions(+) create mode 100644 drivers/crypto/virtio/Kconfig create mode 100644 drivers/crypto/virtio/Makefile create mode 100644 drivers/crypto/virtio/virtio_crypto_algs.c create mode 100644 drivers/crypto/virtio/virtio_crypto_common.h create mode 100644 drivers/crypto/virtio/virtio_crypto_core.c create mode 100644 drivers/crypto/virtio/virtio_crypto_mgr.c create mode 100644 include/uapi/linux/virtio_crypto.h (limited to 'include/uapi/linux/Kbuild') diff --git a/MAINTAINERS b/MAINTAINERS index 59c9895d73d5..650ad4f6b608 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12988,6 +12988,7 @@ F: drivers/net/virtio_net.c F: drivers/block/virtio_blk.c F: include/linux/virtio_*.h F: include/uapi/linux/virtio_*.h +F: drivers/crypto/virtio/ VIRTIO DRIVERS FOR S390 M: Christian Borntraeger @@ -13024,6 +13025,14 @@ S: Maintained F: drivers/virtio/virtio_input.c F: include/uapi/linux/virtio_input.h +VIRTIO CRYPTO DRIVER +M: Gonglei +L: virtualization@lists.linux-foundation.org +L: linux-crypto@vger.kernel.org +S: Maintained +F: drivers/crypto/virtio/ +F: include/uapi/linux/virtio_crypto.h + VIA RHINE NETWORK DRIVER S: Orphan F: drivers/net/ethernet/via/via-rhine.c diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig index 4d2b81f2b223..79564785ae30 100644 --- a/drivers/crypto/Kconfig +++ b/drivers/crypto/Kconfig @@ -555,4 +555,6 @@ config CRYPTO_DEV_ROCKCHIP source "drivers/crypto/chelsio/Kconfig" +source "drivers/crypto/virtio/Kconfig" + endif # CRYPTO_HW diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile index ad7250fa1348..bc53cb833a06 100644 --- a/drivers/crypto/Makefile +++ b/drivers/crypto/Makefile @@ -32,3 +32,4 @@ obj-$(CONFIG_CRYPTO_DEV_VMX) += vmx/ obj-$(CONFIG_CRYPTO_DEV_SUN4I_SS) += sunxi-ss/ obj-$(CONFIG_CRYPTO_DEV_ROCKCHIP) += rockchip/ obj-$(CONFIG_CRYPTO_DEV_CHELSIO) += chelsio/ +obj-$(CONFIG_CRYPTO_DEV_VIRTIO) += virtio/ diff --git a/drivers/crypto/virtio/Kconfig b/drivers/crypto/virtio/Kconfig new file mode 100644 index 000000000000..d80f73366ae2 --- /dev/null +++ b/drivers/crypto/virtio/Kconfig @@ -0,0 +1,10 @@ +config CRYPTO_DEV_VIRTIO + tristate "VirtIO crypto driver" + depends on VIRTIO + select CRYPTO_AEAD + select CRYPTO_AUTHENC + select CRYPTO_BLKCIPHER + default m + help + This driver provides support for virtio crypto device. If you + choose 'M' here, this module will be called virtio_crypto. diff --git a/drivers/crypto/virtio/Makefile b/drivers/crypto/virtio/Makefile new file mode 100644 index 000000000000..dd342c947ff9 --- /dev/null +++ b/drivers/crypto/virtio/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_CRYPTO_DEV_VIRTIO) += virtio_crypto.o +virtio_crypto-objs := \ + virtio_crypto_algs.o \ + virtio_crypto_mgr.o \ + virtio_crypto_core.o diff --git a/drivers/crypto/virtio/virtio_crypto_algs.c b/drivers/crypto/virtio/virtio_crypto_algs.c new file mode 100644 index 000000000000..c2374df9abae --- /dev/null +++ b/drivers/crypto/virtio/virtio_crypto_algs.c @@ -0,0 +1,540 @@ + /* Algorithms supported by virtio crypto device + * + * Authors: Gonglei + * + * Copyright 2016 HUAWEI TECHNOLOGIES CO., LTD. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include + +#include +#include "virtio_crypto_common.h" + +/* + * The algs_lock protects the below global virtio_crypto_active_devs + * and crypto algorithms registion. + */ +static DEFINE_MUTEX(algs_lock); +static unsigned int virtio_crypto_active_devs; + +static u64 virtio_crypto_alg_sg_nents_length(struct scatterlist *sg) +{ + u64 total = 0; + + for (total = 0; sg; sg = sg_next(sg)) + total += sg->length; + + return total; +} + +static int +virtio_crypto_alg_validate_key(int key_len, uint32_t *alg) +{ + switch (key_len) { + case AES_KEYSIZE_128: + case AES_KEYSIZE_192: + case AES_KEYSIZE_256: + *alg = VIRTIO_CRYPTO_CIPHER_AES_CBC; + break; + default: + pr_err("virtio_crypto: Unsupported key length: %d\n", + key_len); + return -EINVAL; + } + return 0; +} + +static int virtio_crypto_alg_ablkcipher_init_session( + struct virtio_crypto_ablkcipher_ctx *ctx, + uint32_t alg, const uint8_t *key, + unsigned int keylen, + int encrypt) +{ + struct scatterlist outhdr, key_sg, inhdr, *sgs[3]; + unsigned int tmp; + struct virtio_crypto *vcrypto = ctx->vcrypto; + int op = encrypt ? VIRTIO_CRYPTO_OP_ENCRYPT : VIRTIO_CRYPTO_OP_DECRYPT; + int err; + unsigned int num_out = 0, num_in = 0; + + /* + * Avoid to do DMA from the stack, switch to using + * dynamically-allocated for the key + */ + uint8_t *cipher_key = kmalloc(keylen, GFP_ATOMIC); + + if (!cipher_key) + return -ENOMEM; + + memcpy(cipher_key, key, keylen); + + spin_lock(&vcrypto->ctrl_lock); + /* Pad ctrl header */ + vcrypto->ctrl.header.opcode = + cpu_to_le32(VIRTIO_CRYPTO_CIPHER_CREATE_SESSION); + vcrypto->ctrl.header.algo = cpu_to_le32(alg); + /* Set the default dataqueue id to 0 */ + vcrypto->ctrl.header.queue_id = 0; + + vcrypto->input.status = cpu_to_le32(VIRTIO_CRYPTO_ERR); + /* Pad cipher's parameters */ + vcrypto->ctrl.u.sym_create_session.op_type = + cpu_to_le32(VIRTIO_CRYPTO_SYM_OP_CIPHER); + vcrypto->ctrl.u.sym_create_session.u.cipher.para.algo = + vcrypto->ctrl.header.algo; + vcrypto->ctrl.u.sym_create_session.u.cipher.para.keylen = + cpu_to_le32(keylen); + vcrypto->ctrl.u.sym_create_session.u.cipher.para.op = + cpu_to_le32(op); + + sg_init_one(&outhdr, &vcrypto->ctrl, sizeof(vcrypto->ctrl)); + sgs[num_out++] = &outhdr; + + /* Set key */ + sg_init_one(&key_sg, cipher_key, keylen); + sgs[num_out++] = &key_sg; + + /* Return status and session id back */ + sg_init_one(&inhdr, &vcrypto->input, sizeof(vcrypto->input)); + sgs[num_out + num_in++] = &inhdr; + + err = virtqueue_add_sgs(vcrypto->ctrl_vq, sgs, num_out, + num_in, vcrypto, GFP_ATOMIC); + if (err < 0) { + spin_unlock(&vcrypto->ctrl_lock); + kzfree(cipher_key); + return err; + } + virtqueue_kick(vcrypto->ctrl_vq); + + /* + * Trapping into the hypervisor, so the request should be + * handled immediately. + */ + while (!virtqueue_get_buf(vcrypto->ctrl_vq, &tmp) && + !virtqueue_is_broken(vcrypto->ctrl_vq)) + cpu_relax(); + + if (le32_to_cpu(vcrypto->input.status) != VIRTIO_CRYPTO_OK) { + spin_unlock(&vcrypto->ctrl_lock); + pr_err("virtio_crypto: Create session failed status: %u\n", + le32_to_cpu(vcrypto->input.status)); + kzfree(cipher_key); + return -EINVAL; + } + + if (encrypt) + ctx->enc_sess_info.session_id = + le64_to_cpu(vcrypto->input.session_id); + else + ctx->dec_sess_info.session_id = + le64_to_cpu(vcrypto->input.session_id); + + spin_unlock(&vcrypto->ctrl_lock); + + kzfree(cipher_key); + return 0; +} + +static int virtio_crypto_alg_ablkcipher_close_session( + struct virtio_crypto_ablkcipher_ctx *ctx, + int encrypt) +{ + struct scatterlist outhdr, status_sg, *sgs[2]; + unsigned int tmp; + struct virtio_crypto_destroy_session_req *destroy_session; + struct virtio_crypto *vcrypto = ctx->vcrypto; + int err; + unsigned int num_out = 0, num_in = 0; + + spin_lock(&vcrypto->ctrl_lock); + vcrypto->ctrl_status.status = VIRTIO_CRYPTO_ERR; + /* Pad ctrl header */ + vcrypto->ctrl.header.opcode = + cpu_to_le32(VIRTIO_CRYPTO_CIPHER_DESTROY_SESSION); + /* Set the default virtqueue id to 0 */ + vcrypto->ctrl.header.queue_id = 0; + + destroy_session = &vcrypto->ctrl.u.destroy_session; + + if (encrypt) + destroy_session->session_id = + cpu_to_le64(ctx->enc_sess_info.session_id); + else + destroy_session->session_id = + cpu_to_le64(ctx->dec_sess_info.session_id); + + sg_init_one(&outhdr, &vcrypto->ctrl, sizeof(vcrypto->ctrl)); + sgs[num_out++] = &outhdr; + + /* Return status and session id back */ + sg_init_one(&status_sg, &vcrypto->ctrl_status.status, + sizeof(vcrypto->ctrl_status.status)); + sgs[num_out + num_in++] = &status_sg; + + err = virtqueue_add_sgs(vcrypto->ctrl_vq, sgs, num_out, + num_in, vcrypto, GFP_ATOMIC); + if (err < 0) { + spin_unlock(&vcrypto->ctrl_lock); + return err; + } + virtqueue_kick(vcrypto->ctrl_vq); + + while (!virtqueue_get_buf(vcrypto->ctrl_vq, &tmp) && + !virtqueue_is_broken(vcrypto->ctrl_vq)) + cpu_relax(); + + if (vcrypto->ctrl_status.status != VIRTIO_CRYPTO_OK) { + spin_unlock(&vcrypto->ctrl_lock); + pr_err("virtio_crypto: Close session failed status: %u, session_id: 0x%llx\n", + vcrypto->ctrl_status.status, + destroy_session->session_id); + + return -EINVAL; + } + spin_unlock(&vcrypto->ctrl_lock); + + return 0; +} + +static int virtio_crypto_alg_ablkcipher_init_sessions( + struct virtio_crypto_ablkcipher_ctx *ctx, + const uint8_t *key, unsigned int keylen) +{ + uint32_t alg; + int ret; + struct virtio_crypto *vcrypto = ctx->vcrypto; + + if (keylen > vcrypto->max_cipher_key_len) { + pr_err("virtio_crypto: the key is too long\n"); + goto bad_key; + } + + if (virtio_crypto_alg_validate_key(keylen, &alg)) + goto bad_key; + + /* Create encryption session */ + ret = virtio_crypto_alg_ablkcipher_init_session(ctx, + alg, key, keylen, 1); + if (ret) + return ret; + /* Create decryption session */ + ret = virtio_crypto_alg_ablkcipher_init_session(ctx, + alg, key, keylen, 0); + if (ret) { + virtio_crypto_alg_ablkcipher_close_session(ctx, 1); + return ret; + } + return 0; + +bad_key: + crypto_tfm_set_flags(ctx->tfm, CRYPTO_TFM_RES_BAD_KEY_LEN); + return -EINVAL; +} + +/* Note: kernel crypto API realization */ +static int virtio_crypto_ablkcipher_setkey(struct crypto_ablkcipher *tfm, + const uint8_t *key, + unsigned int keylen) +{ + struct virtio_crypto_ablkcipher_ctx *ctx = crypto_ablkcipher_ctx(tfm); + int ret; + + if (!ctx->vcrypto) { + /* New key */ + int node = virtio_crypto_get_current_node(); + struct virtio_crypto *vcrypto = + virtcrypto_get_dev_node(node); + if (!vcrypto) { + pr_err("virtio_crypto: Could not find a virtio device in the system"); + return -ENODEV; + } + + ctx->vcrypto = vcrypto; + } else { + /* Rekeying, we should close the created sessions previously */ + virtio_crypto_alg_ablkcipher_close_session(ctx, 1); + virtio_crypto_alg_ablkcipher_close_session(ctx, 0); + } + + ret = virtio_crypto_alg_ablkcipher_init_sessions(ctx, key, keylen); + if (ret) { + virtcrypto_dev_put(ctx->vcrypto); + ctx->vcrypto = NULL; + + return ret; + } + + return 0; +} + +static int +__virtio_crypto_ablkcipher_do_req(struct virtio_crypto_request *vc_req, + struct ablkcipher_request *req, + struct data_queue *data_vq, + __u8 op) +{ + struct crypto_ablkcipher *tfm = crypto_ablkcipher_reqtfm(req); + unsigned int ivsize = crypto_ablkcipher_ivsize(tfm); + struct virtio_crypto_ablkcipher_ctx *ctx = vc_req->ablkcipher_ctx; + struct virtio_crypto *vcrypto = ctx->vcrypto; + struct virtio_crypto_op_data_req *req_data; + int src_nents, dst_nents; + int err; + unsigned long flags; + struct scatterlist outhdr, iv_sg, status_sg, **sgs; + int i; + u64 dst_len; + unsigned int num_out = 0, num_in = 0; + int sg_total; + uint8_t *iv; + + src_nents = sg_nents_for_len(req->src, req->nbytes); + dst_nents = sg_nents(req->dst); + + pr_debug("virtio_crypto: Number of sgs (src_nents: %d, dst_nents: %d)\n", + src_nents, dst_nents); + + /* Why 3? outhdr + iv + inhdr */ + sg_total = src_nents + dst_nents + 3; + sgs = kzalloc_node(sg_total * sizeof(*sgs), GFP_ATOMIC, + dev_to_node(&vcrypto->vdev->dev)); + if (!sgs) + return -ENOMEM; + + req_data = kzalloc_node(sizeof(*req_data), GFP_ATOMIC, + dev_to_node(&vcrypto->vdev->dev)); + if (!req_data) { + kfree(sgs); + return -ENOMEM; + } + + vc_req->req_data = req_data; + vc_req->type = VIRTIO_CRYPTO_SYM_OP_CIPHER; + /* Head of operation */ + if (op) { + req_data->header.session_id = + cpu_to_le64(ctx->enc_sess_info.session_id); + req_data->header.opcode = + cpu_to_le32(VIRTIO_CRYPTO_CIPHER_ENCRYPT); + } else { + req_data->header.session_id = + cpu_to_le64(ctx->dec_sess_info.session_id); + req_data->header.opcode = + cpu_to_le32(VIRTIO_CRYPTO_CIPHER_DECRYPT); + } + req_data->u.sym_req.op_type = cpu_to_le32(VIRTIO_CRYPTO_SYM_OP_CIPHER); + req_data->u.sym_req.u.cipher.para.iv_len = cpu_to_le32(ivsize); + req_data->u.sym_req.u.cipher.para.src_data_len = + cpu_to_le32(req->nbytes); + + dst_len = virtio_crypto_alg_sg_nents_length(req->dst); + if (unlikely(dst_len > U32_MAX)) { + pr_err("virtio_crypto: The dst_len is beyond U32_MAX\n"); + err = -EINVAL; + goto free; + } + + pr_debug("virtio_crypto: src_len: %u, dst_len: %llu\n", + req->nbytes, dst_len); + + if (unlikely(req->nbytes + dst_len + ivsize + + sizeof(vc_req->status) > vcrypto->max_size)) { + pr_err("virtio_crypto: The length is too big\n"); + err = -EINVAL; + goto free; + } + + req_data->u.sym_req.u.cipher.para.dst_data_len = + cpu_to_le32((uint32_t)dst_len); + + /* Outhdr */ + sg_init_one(&outhdr, req_data, sizeof(*req_data)); + sgs[num_out++] = &outhdr; + + /* IV */ + + /* + * Avoid to do DMA from the stack, switch to using + * dynamically-allocated for the IV + */ + iv = kzalloc_node(ivsize, GFP_ATOMIC, + dev_to_node(&vcrypto->vdev->dev)); + if (!iv) { + err = -ENOMEM; + goto free; + } + memcpy(iv, req->info, ivsize); + sg_init_one(&iv_sg, iv, ivsize); + sgs[num_out++] = &iv_sg; + vc_req->iv = iv; + + /* Source data */ + for (i = 0; i < src_nents; i++) + sgs[num_out++] = &req->src[i]; + + /* Destination data */ + for (i = 0; i < dst_nents; i++) + sgs[num_out + num_in++] = &req->dst[i]; + + /* Status */ + sg_init_one(&status_sg, &vc_req->status, sizeof(vc_req->status)); + sgs[num_out + num_in++] = &status_sg; + + vc_req->sgs = sgs; + + spin_lock_irqsave(&data_vq->lock, flags); + err = virtqueue_add_sgs(data_vq->vq, sgs, num_out, + num_in, vc_req, GFP_ATOMIC); + virtqueue_kick(data_vq->vq); + spin_unlock_irqrestore(&data_vq->lock, flags); + if (unlikely(err < 0)) + goto free_iv; + + return 0; + +free_iv: + kzfree(iv); +free: + kzfree(req_data); + kfree(sgs); + return err; +} + +static int virtio_crypto_ablkcipher_encrypt(struct ablkcipher_request *req) +{ + struct crypto_ablkcipher *atfm = crypto_ablkcipher_reqtfm(req); + struct virtio_crypto_ablkcipher_ctx *ctx = crypto_ablkcipher_ctx(atfm); + struct virtio_crypto_request *vc_req = ablkcipher_request_ctx(req); + struct virtio_crypto *vcrypto = ctx->vcrypto; + int ret; + /* Use the first data virtqueue as default */ + struct data_queue *data_vq = &vcrypto->data_vq[0]; + + vc_req->ablkcipher_ctx = ctx; + vc_req->ablkcipher_req = req; + ret = __virtio_crypto_ablkcipher_do_req(vc_req, req, data_vq, 1); + if (ret < 0) { + pr_err("virtio_crypto: Encryption failed!\n"); + return ret; + } + + return -EINPROGRESS; +} + +static int virtio_crypto_ablkcipher_decrypt(struct ablkcipher_request *req) +{ + struct crypto_ablkcipher *atfm = crypto_ablkcipher_reqtfm(req); + struct virtio_crypto_ablkcipher_ctx *ctx = crypto_ablkcipher_ctx(atfm); + struct virtio_crypto_request *vc_req = ablkcipher_request_ctx(req); + struct virtio_crypto *vcrypto = ctx->vcrypto; + int ret; + /* Use the first data virtqueue as default */ + struct data_queue *data_vq = &vcrypto->data_vq[0]; + + vc_req->ablkcipher_ctx = ctx; + vc_req->ablkcipher_req = req; + + ret = __virtio_crypto_ablkcipher_do_req(vc_req, req, data_vq, 0); + if (ret < 0) { + pr_err("virtio_crypto: Decryption failed!\n"); + return ret; + } + + return -EINPROGRESS; +} + +static int virtio_crypto_ablkcipher_init(struct crypto_tfm *tfm) +{ + struct virtio_crypto_ablkcipher_ctx *ctx = crypto_tfm_ctx(tfm); + + tfm->crt_ablkcipher.reqsize = sizeof(struct virtio_crypto_request); + ctx->tfm = tfm; + + return 0; +} + +static void virtio_crypto_ablkcipher_exit(struct crypto_tfm *tfm) +{ + struct virtio_crypto_ablkcipher_ctx *ctx = crypto_tfm_ctx(tfm); + + if (!ctx->vcrypto) + return; + + virtio_crypto_alg_ablkcipher_close_session(ctx, 1); + virtio_crypto_alg_ablkcipher_close_session(ctx, 0); + virtcrypto_dev_put(ctx->vcrypto); + ctx->vcrypto = NULL; +} + +static struct crypto_alg virtio_crypto_algs[] = { { + .cra_name = "cbc(aes)", + .cra_driver_name = "virtio_crypto_aes_cbc", + .cra_priority = 501, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct virtio_crypto_ablkcipher_ctx), + .cra_alignmask = 0, + .cra_module = THIS_MODULE, + .cra_type = &crypto_ablkcipher_type, + .cra_init = virtio_crypto_ablkcipher_init, + .cra_exit = virtio_crypto_ablkcipher_exit, + .cra_u = { + .ablkcipher = { + .setkey = virtio_crypto_ablkcipher_setkey, + .decrypt = virtio_crypto_ablkcipher_decrypt, + .encrypt = virtio_crypto_ablkcipher_encrypt, + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .ivsize = AES_BLOCK_SIZE, + }, + }, +} }; + +int virtio_crypto_algs_register(void) +{ + int ret = 0; + + mutex_lock(&algs_lock); + if (++virtio_crypto_active_devs != 1) + goto unlock; + + ret = crypto_register_algs(virtio_crypto_algs, + ARRAY_SIZE(virtio_crypto_algs)); + if (ret) + virtio_crypto_active_devs--; + +unlock: + mutex_unlock(&algs_lock); + return ret; +} + +void virtio_crypto_algs_unregister(void) +{ + mutex_lock(&algs_lock); + if (--virtio_crypto_active_devs != 0) + goto unlock; + + crypto_unregister_algs(virtio_crypto_algs, + ARRAY_SIZE(virtio_crypto_algs)); + +unlock: + mutex_unlock(&algs_lock); +} diff --git a/drivers/crypto/virtio/virtio_crypto_common.h b/drivers/crypto/virtio/virtio_crypto_common.h new file mode 100644 index 000000000000..3d6566b02876 --- /dev/null +++ b/drivers/crypto/virtio/virtio_crypto_common.h @@ -0,0 +1,128 @@ +/* Common header for Virtio crypto device. + * + * Copyright 2016 HUAWEI TECHNOLOGIES CO., LTD. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _VIRTIO_CRYPTO_COMMON_H +#define _VIRTIO_CRYPTO_COMMON_H + +#include +#include +#include +#include +#include +#include + + +/* Internal representation of a data virtqueue */ +struct data_queue { + /* Virtqueue associated with this send _queue */ + struct virtqueue *vq; + + /* To protect the vq operations for the dataq */ + spinlock_t lock; + + /* Name of the tx queue: dataq.$index */ + char name[32]; +}; + +struct virtio_crypto { + struct virtio_device *vdev; + struct virtqueue *ctrl_vq; + struct data_queue *data_vq; + + /* To protect the vq operations for the controlq */ + spinlock_t ctrl_lock; + + /* Maximum of data queues supported by the device */ + u32 max_data_queues; + + /* Number of queue currently used by the driver */ + u32 curr_queue; + + /* Maximum length of cipher key */ + u32 max_cipher_key_len; + /* Maximum length of authenticated key */ + u32 max_auth_key_len; + /* Maximum size of per request */ + u64 max_size; + + /* Control VQ buffers: protected by the ctrl_lock */ + struct virtio_crypto_op_ctrl_req ctrl; + struct virtio_crypto_session_input input; + struct virtio_crypto_inhdr ctrl_status; + + unsigned long status; + atomic_t ref_count; + struct list_head list; + struct module *owner; + uint8_t dev_id; + + /* Does the affinity hint is set for virtqueues? */ + bool affinity_hint_set; +}; + +struct virtio_crypto_sym_session_info { + /* Backend session id, which come from the host side */ + __u64 session_id; +}; + +struct virtio_crypto_ablkcipher_ctx { + struct virtio_crypto *vcrypto; + struct crypto_tfm *tfm; + + struct virtio_crypto_sym_session_info enc_sess_info; + struct virtio_crypto_sym_session_info dec_sess_info; +}; + +struct virtio_crypto_request { + /* Cipher or aead */ + uint32_t type; + uint8_t status; + struct virtio_crypto_ablkcipher_ctx *ablkcipher_ctx; + struct ablkcipher_request *ablkcipher_req; + struct virtio_crypto_op_data_req *req_data; + struct scatterlist **sgs; + uint8_t *iv; +}; + +int virtcrypto_devmgr_add_dev(struct virtio_crypto *vcrypto_dev); +struct list_head *virtcrypto_devmgr_get_head(void); +void virtcrypto_devmgr_rm_dev(struct virtio_crypto *vcrypto_dev); +struct virtio_crypto *virtcrypto_devmgr_get_first(void); +int virtcrypto_dev_in_use(struct virtio_crypto *vcrypto_dev); +int virtcrypto_dev_get(struct virtio_crypto *vcrypto_dev); +void virtcrypto_dev_put(struct virtio_crypto *vcrypto_dev); +int virtcrypto_dev_started(struct virtio_crypto *vcrypto_dev); +struct virtio_crypto *virtcrypto_get_dev_node(int node); +int virtcrypto_dev_start(struct virtio_crypto *vcrypto); +void virtcrypto_dev_stop(struct virtio_crypto *vcrypto); + +static inline int virtio_crypto_get_current_node(void) +{ + int cpu, node; + + cpu = get_cpu(); + node = topology_physical_package_id(cpu); + put_cpu(); + + return node; +} + +int virtio_crypto_algs_register(void); +void virtio_crypto_algs_unregister(void); + +#endif /* _VIRTIO_CRYPTO_COMMON_H */ diff --git a/drivers/crypto/virtio/virtio_crypto_core.c b/drivers/crypto/virtio/virtio_crypto_core.c new file mode 100644 index 000000000000..fe70ec823b27 --- /dev/null +++ b/drivers/crypto/virtio/virtio_crypto_core.c @@ -0,0 +1,476 @@ + /* Driver for Virtio crypto device. + * + * Copyright 2016 HUAWEI TECHNOLOGIES CO., LTD. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include +#include "virtio_crypto_common.h" + + +static void +virtcrypto_clear_request(struct virtio_crypto_request *vc_req) +{ + if (vc_req) { + kzfree(vc_req->iv); + kzfree(vc_req->req_data); + kfree(vc_req->sgs); + } +} + +static void virtcrypto_dataq_callback(struct virtqueue *vq) +{ + struct virtio_crypto *vcrypto = vq->vdev->priv; + struct virtio_crypto_request *vc_req; + unsigned long flags; + unsigned int len; + struct ablkcipher_request *ablk_req; + int error; + unsigned int qid = vq->index; + + spin_lock_irqsave(&vcrypto->data_vq[qid].lock, flags); + do { + virtqueue_disable_cb(vq); + while ((vc_req = virtqueue_get_buf(vq, &len)) != NULL) { + if (vc_req->type == VIRTIO_CRYPTO_SYM_OP_CIPHER) { + switch (vc_req->status) { + case VIRTIO_CRYPTO_OK: + error = 0; + break; + case VIRTIO_CRYPTO_INVSESS: + case VIRTIO_CRYPTO_ERR: + error = -EINVAL; + break; + case VIRTIO_CRYPTO_BADMSG: + error = -EBADMSG; + break; + default: + error = -EIO; + break; + } + ablk_req = vc_req->ablkcipher_req; + virtcrypto_clear_request(vc_req); + + spin_unlock_irqrestore( + &vcrypto->data_vq[qid].lock, flags); + /* Finish the encrypt or decrypt process */ + ablk_req->base.complete(&ablk_req->base, error); + spin_lock_irqsave( + &vcrypto->data_vq[qid].lock, flags); + } + } + } while (!virtqueue_enable_cb(vq)); + spin_unlock_irqrestore(&vcrypto->data_vq[qid].lock, flags); +} + +static int virtcrypto_find_vqs(struct virtio_crypto *vi) +{ + vq_callback_t **callbacks; + struct virtqueue **vqs; + int ret = -ENOMEM; + int i, total_vqs; + const char **names; + + /* + * We expect 1 data virtqueue, followed by + * possible N-1 data queues used in multiqueue mode, + * followed by control vq. + */ + total_vqs = vi->max_data_queues + 1; + + /* Allocate space for find_vqs parameters */ + vqs = kcalloc(total_vqs, sizeof(*vqs), GFP_KERNEL); + if (!vqs) + goto err_vq; + callbacks = kcalloc(total_vqs, sizeof(*callbacks), GFP_KERNEL); + if (!callbacks) + goto err_callback; + names = kcalloc(total_vqs, sizeof(*names), GFP_KERNEL); + if (!names) + goto err_names; + + /* Parameters for control virtqueue */ + callbacks[total_vqs - 1] = NULL; + names[total_vqs - 1] = "controlq"; + + /* Allocate/initialize parameters for data virtqueues */ + for (i = 0; i < vi->max_data_queues; i++) { + callbacks[i] = virtcrypto_dataq_callback; + snprintf(vi->data_vq[i].name, sizeof(vi->data_vq[i].name), + "dataq.%d", i); + names[i] = vi->data_vq[i].name; + } + + ret = vi->vdev->config->find_vqs(vi->vdev, total_vqs, vqs, callbacks, + names); + if (ret) + goto err_find; + + vi->ctrl_vq = vqs[total_vqs - 1]; + + for (i = 0; i < vi->max_data_queues; i++) { + spin_lock_init(&vi->data_vq[i].lock); + vi->data_vq[i].vq = vqs[i]; + } + + kfree(names); + kfree(callbacks); + kfree(vqs); + + return 0; + +err_find: + kfree(names); +err_names: + kfree(callbacks); +err_callback: + kfree(vqs); +err_vq: + return ret; +} + +static int virtcrypto_alloc_queues(struct virtio_crypto *vi) +{ + vi->data_vq = kcalloc(vi->max_data_queues, sizeof(*vi->data_vq), + GFP_KERNEL); + if (!vi->data_vq) + return -ENOMEM; + + return 0; +} + +static void virtcrypto_clean_affinity(struct virtio_crypto *vi, long hcpu) +{ + int i; + + if (vi->affinity_hint_set) { + for (i = 0; i < vi->max_data_queues; i++) + virtqueue_set_affinity(vi->data_vq[i].vq, -1); + + vi->affinity_hint_set = false; + } +} + +static void virtcrypto_set_affinity(struct virtio_crypto *vcrypto) +{ + int i = 0; + int cpu; + + /* + * In single queue mode, we don't set the cpu affinity. + */ + if (vcrypto->curr_queue == 1 || vcrypto->max_data_queues == 1) { + virtcrypto_clean_affinity(vcrypto, -1); + return; + } + + /* + * In multiqueue mode, we let the queue to be private to one cpu + * by setting the affinity hint to eliminate the contention. + * + * TODO: adds cpu hotplug support by register cpu notifier. + * + */ + for_each_online_cpu(cpu) { + virtqueue_set_affinity(vcrypto->data_vq[i].vq, cpu); + if (++i >= vcrypto->max_data_queues) + break; + } + + vcrypto->affinity_hint_set = true; +} + +static void virtcrypto_free_queues(struct virtio_crypto *vi) +{ + kfree(vi->data_vq); +} + +static int virtcrypto_init_vqs(struct virtio_crypto *vi) +{ + int ret; + + /* Allocate send & receive queues */ + ret = virtcrypto_alloc_queues(vi); + if (ret) + goto err; + + ret = virtcrypto_find_vqs(vi); + if (ret) + goto err_free; + + get_online_cpus(); + virtcrypto_set_affinity(vi); + put_online_cpus(); + + return 0; + +err_free: + virtcrypto_free_queues(vi); +err: + return ret; +} + +static int virtcrypto_update_status(struct virtio_crypto *vcrypto) +{ + u32 status; + int err; + + virtio_cread(vcrypto->vdev, + struct virtio_crypto_config, status, &status); + + /* + * Unknown status bits would be a host error and the driver + * should consider the device to be broken. + */ + if (status & (~VIRTIO_CRYPTO_S_HW_READY)) { + dev_warn(&vcrypto->vdev->dev, + "Unknown status bits: 0x%x\n", status); + + virtio_break_device(vcrypto->vdev); + return -EPERM; + } + + if (vcrypto->status == status) + return 0; + + vcrypto->status = status; + + if (vcrypto->status & VIRTIO_CRYPTO_S_HW_READY) { + err = virtcrypto_dev_start(vcrypto); + if (err) { + dev_err(&vcrypto->vdev->dev, + "Failed to start virtio crypto device.\n"); + + return -EPERM; + } + dev_info(&vcrypto->vdev->dev, "Accelerator is ready\n"); + } else { + virtcrypto_dev_stop(vcrypto); + dev_info(&vcrypto->vdev->dev, "Accelerator is not ready\n"); + } + + return 0; +} + +static void virtcrypto_del_vqs(struct virtio_crypto *vcrypto) +{ + struct virtio_device *vdev = vcrypto->vdev; + + virtcrypto_clean_affinity(vcrypto, -1); + + vdev->config->del_vqs(vdev); + + virtcrypto_free_queues(vcrypto); +} + +static int virtcrypto_probe(struct virtio_device *vdev) +{ + int err = -EFAULT; + struct virtio_crypto *vcrypto; + u32 max_data_queues = 0, max_cipher_key_len = 0; + u32 max_auth_key_len = 0; + u64 max_size = 0; + + if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) + return -ENODEV; + + if (!vdev->config->get) { + dev_err(&vdev->dev, "%s failure: config access disabled\n", + __func__); + return -EINVAL; + } + + if (num_possible_nodes() > 1 && dev_to_node(&vdev->dev) < 0) { + /* + * If the accelerator is connected to a node with no memory + * there is no point in using the accelerator since the remote + * memory transaction will be very slow. + */ + dev_err(&vdev->dev, "Invalid NUMA configuration.\n"); + return -EINVAL; + } + + vcrypto = kzalloc_node(sizeof(*vcrypto), GFP_KERNEL, + dev_to_node(&vdev->dev)); + if (!vcrypto) + return -ENOMEM; + + virtio_cread(vdev, struct virtio_crypto_config, + max_dataqueues, &max_data_queues); + if (max_data_queues < 1) + max_data_queues = 1; + + virtio_cread(vdev, struct virtio_crypto_config, + max_cipher_key_len, &max_cipher_key_len); + virtio_cread(vdev, struct virtio_crypto_config, + max_auth_key_len, &max_auth_key_len); + virtio_cread(vdev, struct virtio_crypto_config, + max_size, &max_size); + + /* Add virtio crypto device to global table */ + err = virtcrypto_devmgr_add_dev(vcrypto); + if (err) { + dev_err(&vdev->dev, "Failed to add new virtio crypto device.\n"); + goto free; + } + vcrypto->owner = THIS_MODULE; + vcrypto = vdev->priv = vcrypto; + vcrypto->vdev = vdev; + + spin_lock_init(&vcrypto->ctrl_lock); + + /* Use single data queue as default */ + vcrypto->curr_queue = 1; + vcrypto->max_data_queues = max_data_queues; + vcrypto->max_cipher_key_len = max_cipher_key_len; + vcrypto->max_auth_key_len = max_auth_key_len; + vcrypto->max_size = max_size; + + dev_info(&vdev->dev, + "max_queues: %u, max_cipher_key_len: %u, max_auth_key_len: %u, max_size 0x%llx\n", + vcrypto->max_data_queues, + vcrypto->max_cipher_key_len, + vcrypto->max_auth_key_len, + vcrypto->max_size); + + err = virtcrypto_init_vqs(vcrypto); + if (err) { + dev_err(&vdev->dev, "Failed to initialize vqs.\n"); + goto free_dev; + } + virtio_device_ready(vdev); + + err = virtcrypto_update_status(vcrypto); + if (err) + goto free_vqs; + + return 0; + +free_vqs: + vcrypto->vdev->config->reset(vdev); + virtcrypto_del_vqs(vcrypto); +free_dev: + virtcrypto_devmgr_rm_dev(vcrypto); +free: + kfree(vcrypto); + return err; +} + +static void virtcrypto_free_unused_reqs(struct virtio_crypto *vcrypto) +{ + struct virtio_crypto_request *vc_req; + int i; + struct virtqueue *vq; + + for (i = 0; i < vcrypto->max_data_queues; i++) { + vq = vcrypto->data_vq[i].vq; + while ((vc_req = virtqueue_detach_unused_buf(vq)) != NULL) { + kfree(vc_req->req_data); + kfree(vc_req->sgs); + } + } +} + +static void virtcrypto_remove(struct virtio_device *vdev) +{ + struct virtio_crypto *vcrypto = vdev->priv; + + dev_info(&vdev->dev, "Start virtcrypto_remove.\n"); + + if (virtcrypto_dev_started(vcrypto)) + virtcrypto_dev_stop(vcrypto); + vdev->config->reset(vdev); + virtcrypto_free_unused_reqs(vcrypto); + virtcrypto_del_vqs(vcrypto); + virtcrypto_devmgr_rm_dev(vcrypto); + kfree(vcrypto); +} + +static void virtcrypto_config_changed(struct virtio_device *vdev) +{ + struct virtio_crypto *vcrypto = vdev->priv; + + virtcrypto_update_status(vcrypto); +} + +#ifdef CONFIG_PM_SLEEP +static int virtcrypto_freeze(struct virtio_device *vdev) +{ + struct virtio_crypto *vcrypto = vdev->priv; + + vdev->config->reset(vdev); + virtcrypto_free_unused_reqs(vcrypto); + if (virtcrypto_dev_started(vcrypto)) + virtcrypto_dev_stop(vcrypto); + + virtcrypto_del_vqs(vcrypto); + return 0; +} + +static int virtcrypto_restore(struct virtio_device *vdev) +{ + struct virtio_crypto *vcrypto = vdev->priv; + int err; + + err = virtcrypto_init_vqs(vcrypto); + if (err) + return err; + + virtio_device_ready(vdev); + err = virtcrypto_dev_start(vcrypto); + if (err) { + dev_err(&vdev->dev, "Failed to start virtio crypto device.\n"); + return -EFAULT; + } + + return 0; +} +#endif + +static unsigned int features[] = { + /* none */ +}; + +static struct virtio_device_id id_table[] = { + { VIRTIO_ID_CRYPTO, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static struct virtio_driver virtio_crypto_driver = { + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .feature_table = features, + .feature_table_size = ARRAY_SIZE(features), + .id_table = id_table, + .probe = virtcrypto_probe, + .remove = virtcrypto_remove, + .config_changed = virtcrypto_config_changed, +#ifdef CONFIG_PM_SLEEP + .freeze = virtcrypto_freeze, + .restore = virtcrypto_restore, +#endif +}; + +module_virtio_driver(virtio_crypto_driver); + +MODULE_DEVICE_TABLE(virtio, id_table); +MODULE_DESCRIPTION("virtio crypto device driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Gonglei "); diff --git a/drivers/crypto/virtio/virtio_crypto_mgr.c b/drivers/crypto/virtio/virtio_crypto_mgr.c new file mode 100644 index 000000000000..a69ff71de2c4 --- /dev/null +++ b/drivers/crypto/virtio/virtio_crypto_mgr.c @@ -0,0 +1,264 @@ + /* Management for virtio crypto devices (refer to adf_dev_mgr.c) + * + * Copyright 2016 HUAWEI TECHNOLOGIES CO., LTD. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include + +#include +#include "virtio_crypto_common.h" + +static LIST_HEAD(virtio_crypto_table); +static uint32_t num_devices; + +/* The table_lock protects the above global list and num_devices */ +static DEFINE_MUTEX(table_lock); + +#define VIRTIO_CRYPTO_MAX_DEVICES 32 + + +/* + * virtcrypto_devmgr_add_dev() - Add vcrypto_dev to the acceleration + * framework. + * @vcrypto_dev: Pointer to virtio crypto device. + * + * Function adds virtio crypto device to the global list. + * To be used by virtio crypto device specific drivers. + * + * Return: 0 on success, error code othewise. + */ +int virtcrypto_devmgr_add_dev(struct virtio_crypto *vcrypto_dev) +{ + struct list_head *itr; + + mutex_lock(&table_lock); + if (num_devices == VIRTIO_CRYPTO_MAX_DEVICES) { + pr_info("virtio_crypto: only support up to %d devices\n", + VIRTIO_CRYPTO_MAX_DEVICES); + mutex_unlock(&table_lock); + return -EFAULT; + } + + list_for_each(itr, &virtio_crypto_table) { + struct virtio_crypto *ptr = + list_entry(itr, struct virtio_crypto, list); + + if (ptr == vcrypto_dev) { + mutex_unlock(&table_lock); + return -EEXIST; + } + } + atomic_set(&vcrypto_dev->ref_count, 0); + list_add_tail(&vcrypto_dev->list, &virtio_crypto_table); + vcrypto_dev->dev_id = num_devices++; + mutex_unlock(&table_lock); + return 0; +} + +struct list_head *virtcrypto_devmgr_get_head(void) +{ + return &virtio_crypto_table; +} + +/* + * virtcrypto_devmgr_rm_dev() - Remove vcrypto_dev from the acceleration + * framework. + * @vcrypto_dev: Pointer to virtio crypto device. + * + * Function removes virtio crypto device from the acceleration framework. + * To be used by virtio crypto device specific drivers. + * + * Return: void + */ +void virtcrypto_devmgr_rm_dev(struct virtio_crypto *vcrypto_dev) +{ + mutex_lock(&table_lock); + list_del(&vcrypto_dev->list); + num_devices--; + mutex_unlock(&table_lock); +} + +/* + * virtcrypto_devmgr_get_first() + * + * Function returns the first virtio crypto device from the acceleration + * framework. + * + * To be used by virtio crypto device specific drivers. + * + * Return: pointer to vcrypto_dev or NULL if not found. + */ +struct virtio_crypto *virtcrypto_devmgr_get_first(void) +{ + struct virtio_crypto *dev = NULL; + + mutex_lock(&table_lock); + if (!list_empty(&virtio_crypto_table)) + dev = list_first_entry(&virtio_crypto_table, + struct virtio_crypto, + list); + mutex_unlock(&table_lock); + return dev; +} + +/* + * virtcrypto_dev_in_use() - Check whether vcrypto_dev is currently in use + * @vcrypto_dev: Pointer to virtio crypto device. + * + * To be used by virtio crypto device specific drivers. + * + * Return: 1 when device is in use, 0 otherwise. + */ +int virtcrypto_dev_in_use(struct virtio_crypto *vcrypto_dev) +{ + return atomic_read(&vcrypto_dev->ref_count) != 0; +} + +/* + * virtcrypto_dev_get() - Increment vcrypto_dev reference count + * @vcrypto_dev: Pointer to virtio crypto device. + * + * Increment the vcrypto_dev refcount and if this is the first time + * incrementing it during this period the vcrypto_dev is in use, + * increment the module refcount too. + * To be used by virtio crypto device specific drivers. + * + * Return: 0 when successful, EFAULT when fail to bump module refcount + */ +int virtcrypto_dev_get(struct virtio_crypto *vcrypto_dev) +{ + if (atomic_add_return(1, &vcrypto_dev->ref_count) == 1) + if (!try_module_get(vcrypto_dev->owner)) + return -EFAULT; + return 0; +} + +/* + * virtcrypto_dev_put() - Decrement vcrypto_dev reference count + * @vcrypto_dev: Pointer to virtio crypto device. + * + * Decrement the vcrypto_dev refcount and if this is the last time + * decrementing it during this period the vcrypto_dev is in use, + * decrement the module refcount too. + * To be used by virtio crypto device specific drivers. + * + * Return: void + */ +void virtcrypto_dev_put(struct virtio_crypto *vcrypto_dev) +{ + if (atomic_sub_return(1, &vcrypto_dev->ref_count) == 0) + module_put(vcrypto_dev->owner); +} + +/* + * virtcrypto_dev_started() - Check whether device has started + * @vcrypto_dev: Pointer to virtio crypto device. + * + * To be used by virtio crypto device specific drivers. + * + * Return: 1 when the device has started, 0 otherwise + */ +int virtcrypto_dev_started(struct virtio_crypto *vcrypto_dev) +{ + return (vcrypto_dev->status & VIRTIO_CRYPTO_S_HW_READY); +} + +/* + * virtcrypto_get_dev_node() - Get vcrypto_dev on the node. + * @node: Node id the driver works. + * + * Function returns the virtio crypto device used fewest on the node. + * + * To be used by virtio crypto device specific drivers. + * + * Return: pointer to vcrypto_dev or NULL if not found. + */ +struct virtio_crypto *virtcrypto_get_dev_node(int node) +{ + struct virtio_crypto *vcrypto_dev = NULL, *tmp_dev; + unsigned long best = ~0; + unsigned long ctr; + + mutex_lock(&table_lock); + list_for_each_entry(tmp_dev, virtcrypto_devmgr_get_head(), list) { + + if ((node == dev_to_node(&tmp_dev->vdev->dev) || + dev_to_node(&tmp_dev->vdev->dev) < 0) && + virtcrypto_dev_started(tmp_dev)) { + ctr = atomic_read(&tmp_dev->ref_count); + if (best > ctr) { + vcrypto_dev = tmp_dev; + best = ctr; + } + } + } + + if (!vcrypto_dev) { + pr_info("virtio_crypto: Could not find a device on node %d\n", + node); + /* Get any started device */ + list_for_each_entry(tmp_dev, + virtcrypto_devmgr_get_head(), list) { + if (virtcrypto_dev_started(tmp_dev)) { + vcrypto_dev = tmp_dev; + break; + } + } + } + mutex_unlock(&table_lock); + if (!vcrypto_dev) + return NULL; + + virtcrypto_dev_get(vcrypto_dev); + return vcrypto_dev; +} + +/* + * virtcrypto_dev_start() - Start virtio crypto device + * @vcrypto: Pointer to virtio crypto device. + * + * Function notifies all the registered services that the virtio crypto device + * is ready to be used. + * To be used by virtio crypto device specific drivers. + * + * Return: 0 on success, EFAULT when fail to register algorithms + */ +int virtcrypto_dev_start(struct virtio_crypto *vcrypto) +{ + if (virtio_crypto_algs_register()) { + pr_err("virtio_crypto: Failed to register crypto algs\n"); + return -EFAULT; + } + + return 0; +} + +/* + * virtcrypto_dev_stop() - Stop virtio crypto device + * @vcrypto: Pointer to virtio crypto device. + * + * Function notifies all the registered services that the virtio crypto device + * is ready to be used. + * To be used by virtio crypto device specific drivers. + * + * Return: void + */ +void virtcrypto_dev_stop(struct virtio_crypto *vcrypto) +{ + virtio_crypto_algs_unregister(); +} diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index bc2ef9fef7c8..a26c5c76ab62 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -462,6 +462,7 @@ header-y += virtio_rng.h header-y += virtio_scsi.h header-y += virtio_types.h header-y += virtio_vsock.h +header-y += virtio_crypto.h header-y += vm_sockets.h header-y += vt.h header-y += vtpm_proxy.h diff --git a/include/uapi/linux/virtio_crypto.h b/include/uapi/linux/virtio_crypto.h new file mode 100644 index 000000000000..50cdc8aebfcf --- /dev/null +++ b/include/uapi/linux/virtio_crypto.h @@ -0,0 +1,450 @@ +#ifndef _VIRTIO_CRYPTO_H +#define _VIRTIO_CRYPTO_H +/* This header is BSD licensed so anyone can use the definitions to implement + * compatible drivers/servers. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of IBM nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include +#include +#include +#include + + +#define VIRTIO_CRYPTO_SERVICE_CIPHER 0 +#define VIRTIO_CRYPTO_SERVICE_HASH 1 +#define VIRTIO_CRYPTO_SERVICE_MAC 2 +#define VIRTIO_CRYPTO_SERVICE_AEAD 3 + +#define VIRTIO_CRYPTO_OPCODE(service, op) (((service) << 8) | (op)) + +struct virtio_crypto_ctrl_header { +#define VIRTIO_CRYPTO_CIPHER_CREATE_SESSION \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_CIPHER, 0x02) +#define VIRTIO_CRYPTO_CIPHER_DESTROY_SESSION \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_CIPHER, 0x03) +#define VIRTIO_CRYPTO_HASH_CREATE_SESSION \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_HASH, 0x02) +#define VIRTIO_CRYPTO_HASH_DESTROY_SESSION \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_HASH, 0x03) +#define VIRTIO_CRYPTO_MAC_CREATE_SESSION \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_MAC, 0x02) +#define VIRTIO_CRYPTO_MAC_DESTROY_SESSION \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_MAC, 0x03) +#define VIRTIO_CRYPTO_AEAD_CREATE_SESSION \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x02) +#define VIRTIO_CRYPTO_AEAD_DESTROY_SESSION \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x03) + __le32 opcode; + __le32 algo; + __le32 flag; + /* data virtqueue id */ + __le32 queue_id; +}; + +struct virtio_crypto_cipher_session_para { +#define VIRTIO_CRYPTO_NO_CIPHER 0 +#define VIRTIO_CRYPTO_CIPHER_ARC4 1 +#define VIRTIO_CRYPTO_CIPHER_AES_ECB 2 +#define VIRTIO_CRYPTO_CIPHER_AES_CBC 3 +#define VIRTIO_CRYPTO_CIPHER_AES_CTR 4 +#define VIRTIO_CRYPTO_CIPHER_DES_ECB 5 +#define VIRTIO_CRYPTO_CIPHER_DES_CBC 6 +#define VIRTIO_CRYPTO_CIPHER_3DES_ECB 7 +#define VIRTIO_CRYPTO_CIPHER_3DES_CBC 8 +#define VIRTIO_CRYPTO_CIPHER_3DES_CTR 9 +#define VIRTIO_CRYPTO_CIPHER_KASUMI_F8 10 +#define VIRTIO_CRYPTO_CIPHER_SNOW3G_UEA2 11 +#define VIRTIO_CRYPTO_CIPHER_AES_F8 12 +#define VIRTIO_CRYPTO_CIPHER_AES_XTS 13 +#define VIRTIO_CRYPTO_CIPHER_ZUC_EEA3 14 + __le32 algo; + /* length of key */ + __le32 keylen; + +#define VIRTIO_CRYPTO_OP_ENCRYPT 1 +#define VIRTIO_CRYPTO_OP_DECRYPT 2 + /* encrypt or decrypt */ + __le32 op; + __le32 padding; +}; + +struct virtio_crypto_session_input { + /* Device-writable part */ + __le64 session_id; + __le32 status; + __le32 padding; +}; + +struct virtio_crypto_cipher_session_req { + struct virtio_crypto_cipher_session_para para; + __u8 padding[32]; +}; + +struct virtio_crypto_hash_session_para { +#define VIRTIO_CRYPTO_NO_HASH 0 +#define VIRTIO_CRYPTO_HASH_MD5 1 +#define VIRTIO_CRYPTO_HASH_SHA1 2 +#define VIRTIO_CRYPTO_HASH_SHA_224 3 +#define VIRTIO_CRYPTO_HASH_SHA_256 4 +#define VIRTIO_CRYPTO_HASH_SHA_384 5 +#define VIRTIO_CRYPTO_HASH_SHA_512 6 +#define VIRTIO_CRYPTO_HASH_SHA3_224 7 +#define VIRTIO_CRYPTO_HASH_SHA3_256 8 +#define VIRTIO_CRYPTO_HASH_SHA3_384 9 +#define VIRTIO_CRYPTO_HASH_SHA3_512 10 +#define VIRTIO_CRYPTO_HASH_SHA3_SHAKE128 11 +#define VIRTIO_CRYPTO_HASH_SHA3_SHAKE256 12 + __le32 algo; + /* hash result length */ + __le32 hash_result_len; + __u8 padding[8]; +}; + +struct virtio_crypto_hash_create_session_req { + struct virtio_crypto_hash_session_para para; + __u8 padding[40]; +}; + +struct virtio_crypto_mac_session_para { +#define VIRTIO_CRYPTO_NO_MAC 0 +#define VIRTIO_CRYPTO_MAC_HMAC_MD5 1 +#define VIRTIO_CRYPTO_MAC_HMAC_SHA1 2 +#define VIRTIO_CRYPTO_MAC_HMAC_SHA_224 3 +#define VIRTIO_CRYPTO_MAC_HMAC_SHA_256 4 +#define VIRTIO_CRYPTO_MAC_HMAC_SHA_384 5 +#define VIRTIO_CRYPTO_MAC_HMAC_SHA_512 6 +#define VIRTIO_CRYPTO_MAC_CMAC_3DES 25 +#define VIRTIO_CRYPTO_MAC_CMAC_AES 26 +#define VIRTIO_CRYPTO_MAC_KASUMI_F9 27 +#define VIRTIO_CRYPTO_MAC_SNOW3G_UIA2 28 +#define VIRTIO_CRYPTO_MAC_GMAC_AES 41 +#define VIRTIO_CRYPTO_MAC_GMAC_TWOFISH 42 +#define VIRTIO_CRYPTO_MAC_CBCMAC_AES 49 +#define VIRTIO_CRYPTO_MAC_CBCMAC_KASUMI_F9 50 +#define VIRTIO_CRYPTO_MAC_XCBC_AES 53 + __le32 algo; + /* hash result length */ + __le32 hash_result_len; + /* length of authenticated key */ + __le32 auth_key_len; + __le32 padding; +}; + +struct virtio_crypto_mac_create_session_req { + struct virtio_crypto_mac_session_para para; + __u8 padding[40]; +}; + +struct virtio_crypto_aead_session_para { +#define VIRTIO_CRYPTO_NO_AEAD 0 +#define VIRTIO_CRYPTO_AEAD_GCM 1 +#define VIRTIO_CRYPTO_AEAD_CCM 2 +#define VIRTIO_CRYPTO_AEAD_CHACHA20_POLY1305 3 + __le32 algo; + /* length of key */ + __le32 key_len; + /* hash result length */ + __le32 hash_result_len; + /* length of the additional authenticated data (AAD) in bytes */ + __le32 aad_len; + /* encrypt or decrypt, See above VIRTIO_CRYPTO_OP_* */ + __le32 op; + __le32 padding; +}; + +struct virtio_crypto_aead_create_session_req { + struct virtio_crypto_aead_session_para para; + __u8 padding[32]; +}; + +struct virtio_crypto_alg_chain_session_para { +#define VIRTIO_CRYPTO_SYM_ALG_CHAIN_ORDER_HASH_THEN_CIPHER 1 +#define VIRTIO_CRYPTO_SYM_ALG_CHAIN_ORDER_CIPHER_THEN_HASH 2 + __le32 alg_chain_order; +/* Plain hash */ +#define VIRTIO_CRYPTO_SYM_HASH_MODE_PLAIN 1 +/* Authenticated hash (mac) */ +#define VIRTIO_CRYPTO_SYM_HASH_MODE_AUTH 2 +/* Nested hash */ +#define VIRTIO_CRYPTO_SYM_HASH_MODE_NESTED 3 + __le32 hash_mode; + struct virtio_crypto_cipher_session_para cipher_param; + union { + struct virtio_crypto_hash_session_para hash_param; + struct virtio_crypto_mac_session_para mac_param; + __u8 padding[16]; + } u; + /* length of the additional authenticated data (AAD) in bytes */ + __le32 aad_len; + __le32 padding; +}; + +struct virtio_crypto_alg_chain_session_req { + struct virtio_crypto_alg_chain_session_para para; +}; + +struct virtio_crypto_sym_create_session_req { + union { + struct virtio_crypto_cipher_session_req cipher; + struct virtio_crypto_alg_chain_session_req chain; + __u8 padding[48]; + } u; + + /* Device-readable part */ + +/* No operation */ +#define VIRTIO_CRYPTO_SYM_OP_NONE 0 +/* Cipher only operation on the data */ +#define VIRTIO_CRYPTO_SYM_OP_CIPHER 1 +/* + * Chain any cipher with any hash or mac operation. The order + * depends on the value of alg_chain_order param + */ +#define VIRTIO_CRYPTO_SYM_OP_ALGORITHM_CHAINING 2 + __le32 op_type; + __le32 padding; +}; + +struct virtio_crypto_destroy_session_req { + /* Device-readable part */ + __le64 session_id; + __u8 padding[48]; +}; + +/* The request of the control virtqueue's packet */ +struct virtio_crypto_op_ctrl_req { + struct virtio_crypto_ctrl_header header; + + union { + struct virtio_crypto_sym_create_session_req + sym_create_session; + struct virtio_crypto_hash_create_session_req + hash_create_session; + struct virtio_crypto_mac_create_session_req + mac_create_session; + struct virtio_crypto_aead_create_session_req + aead_create_session; + struct virtio_crypto_destroy_session_req + destroy_session; + __u8 padding[56]; + } u; +}; + +struct virtio_crypto_op_header { +#define VIRTIO_CRYPTO_CIPHER_ENCRYPT \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_CIPHER, 0x00) +#define VIRTIO_CRYPTO_CIPHER_DECRYPT \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_CIPHER, 0x01) +#define VIRTIO_CRYPTO_HASH \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_HASH, 0x00) +#define VIRTIO_CRYPTO_MAC \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_MAC, 0x00) +#define VIRTIO_CRYPTO_AEAD_ENCRYPT \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x00) +#define VIRTIO_CRYPTO_AEAD_DECRYPT \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x01) + __le32 opcode; + /* algo should be service-specific algorithms */ + __le32 algo; + /* session_id should be service-specific algorithms */ + __le64 session_id; + /* control flag to control the request */ + __le32 flag; + __le32 padding; +}; + +struct virtio_crypto_cipher_para { + /* + * Byte Length of valid IV/Counter + * + * For block ciphers in CBC or F8 mode, or for Kasumi in F8 mode, or for + * SNOW3G in UEA2 mode, this is the length of the IV (which + * must be the same as the block length of the cipher). + * For block ciphers in CTR mode, this is the length of the counter + * (which must be the same as the block length of the cipher). + * For AES-XTS, this is the 128bit tweak, i, from IEEE Std 1619-2007. + * + * The IV/Counter will be updated after every partial cryptographic + * operation. + */ + __le32 iv_len; + /* length of source data */ + __le32 src_data_len; + /* length of dst data */ + __le32 dst_data_len; + __le32 padding; +}; + +struct virtio_crypto_hash_para { + /* length of source data */ + __le32 src_data_len; + /* hash result length */ + __le32 hash_result_len; +}; + +struct virtio_crypto_mac_para { + struct virtio_crypto_hash_para hash; +}; + +struct virtio_crypto_aead_para { + /* + * Byte Length of valid IV data pointed to by the below iv_addr + * parameter. + * + * For GCM mode, this is either 12 (for 96-bit IVs) or 16, in which + * case iv_addr points to J0. + * For CCM mode, this is the length of the nonce, which can be in the + * range 7 to 13 inclusive. + */ + __le32 iv_len; + /* length of additional auth data */ + __le32 aad_len; + /* length of source data */ + __le32 src_data_len; + /* length of dst data */ + __le32 dst_data_len; +}; + +struct virtio_crypto_cipher_data_req { + /* Device-readable part */ + struct virtio_crypto_cipher_para para; + __u8 padding[24]; +}; + +struct virtio_crypto_hash_data_req { + /* Device-readable part */ + struct virtio_crypto_hash_para para; + __u8 padding[40]; +}; + +struct virtio_crypto_mac_data_req { + /* Device-readable part */ + struct virtio_crypto_mac_para para; + __u8 padding[40]; +}; + +struct virtio_crypto_alg_chain_data_para { + __le32 iv_len; + /* Length of source data */ + __le32 src_data_len; + /* Length of destination data */ + __le32 dst_data_len; + /* Starting point for cipher processing in source data */ + __le32 cipher_start_src_offset; + /* Length of the source data that the cipher will be computed on */ + __le32 len_to_cipher; + /* Starting point for hash processing in source data */ + __le32 hash_start_src_offset; + /* Length of the source data that the hash will be computed on */ + __le32 len_to_hash; + /* Length of the additional auth data */ + __le32 aad_len; + /* Length of the hash result */ + __le32 hash_result_len; + __le32 reserved; +}; + +struct virtio_crypto_alg_chain_data_req { + /* Device-readable part */ + struct virtio_crypto_alg_chain_data_para para; +}; + +struct virtio_crypto_sym_data_req { + union { + struct virtio_crypto_cipher_data_req cipher; + struct virtio_crypto_alg_chain_data_req chain; + __u8 padding[40]; + } u; + + /* See above VIRTIO_CRYPTO_SYM_OP_* */ + __le32 op_type; + __le32 padding; +}; + +struct virtio_crypto_aead_data_req { + /* Device-readable part */ + struct virtio_crypto_aead_para para; + __u8 padding[32]; +}; + +/* The request of the data virtqueue's packet */ +struct virtio_crypto_op_data_req { + struct virtio_crypto_op_header header; + + union { + struct virtio_crypto_sym_data_req sym_req; + struct virtio_crypto_hash_data_req hash_req; + struct virtio_crypto_mac_data_req mac_req; + struct virtio_crypto_aead_data_req aead_req; + __u8 padding[48]; + } u; +}; + +#define VIRTIO_CRYPTO_OK 0 +#define VIRTIO_CRYPTO_ERR 1 +#define VIRTIO_CRYPTO_BADMSG 2 +#define VIRTIO_CRYPTO_NOTSUPP 3 +#define VIRTIO_CRYPTO_INVSESS 4 /* Invalid session id */ + +/* The accelerator hardware is ready */ +#define VIRTIO_CRYPTO_S_HW_READY (1 << 0) + +struct virtio_crypto_config { + /* See VIRTIO_CRYPTO_OP_* above */ + __u32 status; + + /* + * Maximum number of data queue + */ + __u32 max_dataqueues; + + /* + * Specifies the services mask which the device support, + * see VIRTIO_CRYPTO_SERVICE_* above + */ + __u32 crypto_services; + + /* Detailed algorithms mask */ + __u32 cipher_algo_l; + __u32 cipher_algo_h; + __u32 hash_algo; + __u32 mac_algo_l; + __u32 mac_algo_h; + __u32 aead_algo; + /* Maximum length of cipher key */ + __u32 max_cipher_key_len; + /* Maximum length of authenticated key */ + __u32 max_auth_key_len; + __u32 reserve; + /* Maximum size of each crypto request's content */ + __u64 max_size; +}; + +struct virtio_crypto_inhdr { + /* See VIRTIO_CRYPTO_* above */ + __u8 status; +}; +#endif diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index 3228d582234a..6d5c3b2d4f4d 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -42,5 +42,6 @@ #define VIRTIO_ID_GPU 16 /* virtio GPU */ #define VIRTIO_ID_INPUT 18 /* virtio input */ #define VIRTIO_ID_VSOCK 19 /* virtio vsock transport */ +#define VIRTIO_ID_CRYPTO 20 /* virtio crypto */ #endif /* _LINUX_VIRTIO_IDS_H */ -- cgit v1.3-7-g2ca7