diff mbox

[v2,01/25] media: lirc: implement scancode sending

Message ID 88e30a50734f7d132ac8a6234acc7335cbbb3a56.1507192751.git.sean@mess.org (mailing list archive)
State New, archived
Headers show

Commit Message

Sean Young Oct. 5, 2017, 8:45 a.m. UTC
This introduces a new lirc mode: scancode. Any device which can send raw IR
can now also send scancodes.

int main()
{
	int mode, fd = open("/dev/lirc0", O_RDWR);

        mode = LIRC_MODE_SCANCODE;
	if (ioctl(fd, LIRC_SET_SEND_MODE, &mode)) {
		// kernel too old or lirc does not support transmit
	}
	struct lirc_scancode scancode = {
		.scancode = 0x1e3d,
		.rc_proto = RC_PROTO_RC5,
	};
	write(fd, &scancode, sizeof(scancode));
	close(fd);
}

The other fields of lirc_scancode must be set to 0.

Note that toggle (rc5, rc6) and repeats (nec) are not implemented. Nor is
there a method for holding down a key for a period.

Signed-off-by: Sean Young <sean@mess.org>
---
 drivers/media/rc/ir-lirc-codec.c | 101 ++++++++++++++++++++++++++++-----------
 drivers/media/rc/rc-core-priv.h  |   2 +-
 include/media/rc-map.h           |  54 +--------------------
 include/uapi/linux/lirc.h        |  93 +++++++++++++++++++++++++++++++++++
 4 files changed, 169 insertions(+), 81 deletions(-)

Comments

Hans Verkuil Oct. 9, 2017, 9:14 a.m. UTC | #1
On 05/10/17 10:45, Sean Young wrote:
> This introduces a new lirc mode: scancode. Any device which can send raw IR
> can now also send scancodes.
> 
> int main()
> {
> 	int mode, fd = open("/dev/lirc0", O_RDWR);
> 
>         mode = LIRC_MODE_SCANCODE;
> 	if (ioctl(fd, LIRC_SET_SEND_MODE, &mode)) {
> 		// kernel too old or lirc does not support transmit
> 	}
> 	struct lirc_scancode scancode = {
> 		.scancode = 0x1e3d,
> 		.rc_proto = RC_PROTO_RC5,
> 	};
> 	write(fd, &scancode, sizeof(scancode));
> 	close(fd);
> }
> 
> The other fields of lirc_scancode must be set to 0.
> 
> Note that toggle (rc5, rc6) and repeats (nec) are not implemented. Nor is
> there a method for holding down a key for a period.
> 
> Signed-off-by: Sean Young <sean@mess.org>
> ---
>  drivers/media/rc/ir-lirc-codec.c | 101 ++++++++++++++++++++++++++++-----------
>  drivers/media/rc/rc-core-priv.h  |   2 +-
>  include/media/rc-map.h           |  54 +--------------------
>  include/uapi/linux/lirc.h        |  93 +++++++++++++++++++++++++++++++++++
>  4 files changed, 169 insertions(+), 81 deletions(-)
> 
> diff --git a/drivers/media/rc/ir-lirc-codec.c b/drivers/media/rc/ir-lirc-codec.c
> index bd046c41a53a..ef0b8df88613 100644
> --- a/drivers/media/rc/ir-lirc-codec.c
> +++ b/drivers/media/rc/ir-lirc-codec.c
> @@ -107,7 +107,8 @@ static ssize_t ir_lirc_transmit_ir(struct file *file, const char __user *buf,
>  {
>  	struct lirc_codec *lirc;
>  	struct rc_dev *dev;
> -	unsigned int *txbuf; /* buffer with values to transmit */
> +	unsigned int *txbuf = NULL;
> +	struct ir_raw_event *raw = NULL;
>  	ssize_t ret = -EINVAL;
>  	size_t count;
>  	ktime_t start;
> @@ -121,16 +122,51 @@ static ssize_t ir_lirc_transmit_ir(struct file *file, const char __user *buf,
>  	if (!lirc)
>  		return -EFAULT;
>  
> -	if (n < sizeof(unsigned) || n % sizeof(unsigned))
> -		return -EINVAL;
> +	if (lirc->send_mode == LIRC_MODE_SCANCODE) {
> +		struct lirc_scancode scan;
>  
> -	count = n / sizeof(unsigned);
> -	if (count > LIRCBUF_SIZE || count % 2 == 0)
> -		return -EINVAL;
> +		if (n != sizeof(scan))
> +			return -EINVAL;
>  
> -	txbuf = memdup_user(buf, n);
> -	if (IS_ERR(txbuf))
> -		return PTR_ERR(txbuf);
> +		if (copy_from_user(&scan, buf, sizeof(scan)))
> +			return -EFAULT;
> +
> +		if (scan.flags || scan.source || scan.target || scan.unused ||
> +		    scan.timestamp)
> +			return -EINVAL;
> +
> +		raw = kmalloc_array(LIRCBUF_SIZE, sizeof(*raw), GFP_KERNEL);
> +		if (!raw)
> +			return -ENOMEM;
> +
> +		ret = ir_raw_encode_scancode(scan.rc_proto, scan.scancode,
> +					     raw, LIRCBUF_SIZE);
> +		if (ret < 0)
> +			goto out;
> +
> +		count = ret;
> +
> +		txbuf = kmalloc_array(count, sizeof(unsigned int), GFP_KERNEL);
> +		if (!txbuf) {
> +			ret = -ENOMEM;
> +			goto out;
> +		}
> +
> +		for (i = 0; i < count; i++)
> +			/* Convert from NS to US */
> +			txbuf[i] = DIV_ROUND_UP(raw[i].duration, 1000);
> +	} else {
> +		if (n < sizeof(unsigned int) || n % sizeof(unsigned int))
> +			return -EINVAL;
> +
> +		count = n / sizeof(unsigned int);
> +		if (count > LIRCBUF_SIZE || count % 2 == 0)
> +			return -EINVAL;
> +
> +		txbuf = memdup_user(buf, n);
> +		if (IS_ERR(txbuf))
> +			return PTR_ERR(txbuf);
> +	}
>  
>  	dev = lirc->dev;
>  	if (!dev) {
> @@ -156,24 +192,31 @@ static ssize_t ir_lirc_transmit_ir(struct file *file, const char __user *buf,
>  	if (ret < 0)
>  		goto out;
>  
> -	for (duration = i = 0; i < ret; i++)
> -		duration += txbuf[i];
> -
> -	ret *= sizeof(unsigned int);
> -
> -	/*
> -	 * The lircd gap calculation expects the write function to
> -	 * wait for the actual IR signal to be transmitted before
> -	 * returning.
> -	 */
> -	towait = ktime_us_delta(ktime_add_us(start, duration), ktime_get());
> -	if (towait > 0) {
> -		set_current_state(TASK_INTERRUPTIBLE);
> -		schedule_timeout(usecs_to_jiffies(towait));
> +	if (lirc->send_mode == LIRC_MODE_SCANCODE) {
> +		ret = n;
> +	} else {
> +		for (duration = i = 0; i < ret; i++)
> +			duration += txbuf[i];
> +
> +
> +		ret *= sizeof(unsigned int);
> +
> +		/*
> +		 * The lircd gap calculation expects the write function to
> +		 * wait for the actual IR signal to be transmitted before
> +		 * returning.
> +		 */
> +		towait = ktime_us_delta(ktime_add_us(start, duration),
> +					ktime_get());
> +		if (towait > 0) {
> +			set_current_state(TASK_INTERRUPTIBLE);
> +			schedule_timeout(usecs_to_jiffies(towait));
> +		}
>  	}
>  
>  out:
>  	kfree(txbuf);
> +	kfree(raw);
>  	return ret;
>  }
>  
> @@ -202,20 +245,22 @@ static long ir_lirc_ioctl(struct file *filep, unsigned int cmd,
>  
>  	switch (cmd) {
>  
> -	/* legacy support */
> +	/* mode support */
>  	case LIRC_GET_SEND_MODE:
>  		if (!dev->tx_ir)
>  			return -ENOTTY;
>  
> -		val = LIRC_MODE_PULSE;
> +		val = lirc->send_mode;
>  		break;
>  
>  	case LIRC_SET_SEND_MODE:
>  		if (!dev->tx_ir)
>  			return -ENOTTY;
>  
> -		if (val != LIRC_MODE_PULSE)
> +		if (!(val == LIRC_MODE_PULSE || val == LIRC_MODE_SCANCODE))
>  			return -EINVAL;
> +
> +		lirc->send_mode = val;
>  		return 0;
>  
>  	/* TX settings */
> @@ -358,7 +403,7 @@ static int ir_lirc_register(struct rc_dev *dev)
>  	}
>  
>  	if (dev->tx_ir) {
> -		features |= LIRC_CAN_SEND_PULSE;
> +		features |= LIRC_CAN_SEND_PULSE | LIRC_CAN_SEND_SCANCODE;
>  		if (dev->s_tx_mask)
>  			features |= LIRC_CAN_SET_TRANSMITTER_MASK;
>  		if (dev->s_tx_carrier)
> @@ -397,6 +442,8 @@ static int ir_lirc_register(struct rc_dev *dev)
>  	if (rc < 0)
>  		goto out;
>  
> +	dev->raw->lirc.send_mode = LIRC_MODE_PULSE;
> +
>  	dev->raw->lirc.ldev = ldev;
>  	dev->raw->lirc.dev = dev;
>  	return 0;
> diff --git a/drivers/media/rc/rc-core-priv.h b/drivers/media/rc/rc-core-priv.h
> index ae4dd0c27731..43eabea9f152 100644
> --- a/drivers/media/rc/rc-core-priv.h
> +++ b/drivers/media/rc/rc-core-priv.h
> @@ -113,7 +113,7 @@ struct ir_raw_event_ctrl {
>  		u64 gap_duration;
>  		bool gap;
>  		bool send_timeout_reports;
> -
> +		u8 send_mode;
>  	} lirc;
>  	struct xmp_dec {
>  		int state;
> diff --git a/include/media/rc-map.h b/include/media/rc-map.h
> index 2a160e6e823c..00e033975eed 100644
> --- a/include/media/rc-map.h
> +++ b/include/media/rc-map.h
> @@ -10,59 +10,7 @@
>   */
>  
>  #include <linux/input.h>
> -
> -/**
> - * enum rc_proto - the Remote Controller protocol
> - *
> - * @RC_PROTO_UNKNOWN: Protocol not known
> - * @RC_PROTO_OTHER: Protocol known but proprietary
> - * @RC_PROTO_RC5: Philips RC5 protocol
> - * @RC_PROTO_RC5X_20: Philips RC5x 20 bit protocol
> - * @RC_PROTO_RC5_SZ: StreamZap variant of RC5
> - * @RC_PROTO_JVC: JVC protocol
> - * @RC_PROTO_SONY12: Sony 12 bit protocol
> - * @RC_PROTO_SONY15: Sony 15 bit protocol
> - * @RC_PROTO_SONY20: Sony 20 bit protocol
> - * @RC_PROTO_NEC: NEC protocol
> - * @RC_PROTO_NECX: Extended NEC protocol
> - * @RC_PROTO_NEC32: NEC 32 bit protocol
> - * @RC_PROTO_SANYO: Sanyo protocol
> - * @RC_PROTO_MCIR2_KBD: RC6-ish MCE keyboard
> - * @RC_PROTO_MCIR2_MSE: RC6-ish MCE mouse
> - * @RC_PROTO_RC6_0: Philips RC6-0-16 protocol
> - * @RC_PROTO_RC6_6A_20: Philips RC6-6A-20 protocol
> - * @RC_PROTO_RC6_6A_24: Philips RC6-6A-24 protocol
> - * @RC_PROTO_RC6_6A_32: Philips RC6-6A-32 protocol
> - * @RC_PROTO_RC6_MCE: MCE (Philips RC6-6A-32 subtype) protocol
> - * @RC_PROTO_SHARP: Sharp protocol
> - * @RC_PROTO_XMP: XMP protocol
> - * @RC_PROTO_CEC: CEC protocol
> - */
> -enum rc_proto {
> -	RC_PROTO_UNKNOWN	= 0,
> -	RC_PROTO_OTHER		= 1,
> -	RC_PROTO_RC5		= 2,
> -	RC_PROTO_RC5X_20	= 3,
> -	RC_PROTO_RC5_SZ		= 4,
> -	RC_PROTO_JVC		= 5,
> -	RC_PROTO_SONY12		= 6,
> -	RC_PROTO_SONY15		= 7,
> -	RC_PROTO_SONY20		= 8,
> -	RC_PROTO_NEC		= 9,
> -	RC_PROTO_NECX		= 10,
> -	RC_PROTO_NEC32		= 11,
> -	RC_PROTO_SANYO		= 12,
> -	RC_PROTO_MCIR2_KBD	= 13,
> -	RC_PROTO_MCIR2_MSE	= 14,
> -	RC_PROTO_RC6_0		= 15,
> -	RC_PROTO_RC6_6A_20	= 16,
> -	RC_PROTO_RC6_6A_24	= 17,
> -	RC_PROTO_RC6_6A_32	= 18,
> -	RC_PROTO_RC6_MCE	= 19,
> -	RC_PROTO_SHARP		= 20,
> -	RC_PROTO_XMP		= 21,
> -	RC_PROTO_CEC		= 22,
> -};
> +#include <uapi/linux/lirc.h>
>  
>  #define RC_PROTO_BIT_NONE		0ULL
>  #define RC_PROTO_BIT_UNKNOWN		BIT_ULL(RC_PROTO_UNKNOWN)
> diff --git a/include/uapi/linux/lirc.h b/include/uapi/linux/lirc.h
> index 991ab4570b8e..312e37812783 100644
> --- a/include/uapi/linux/lirc.h
> +++ b/include/uapi/linux/lirc.h
> @@ -46,12 +46,14 @@
>  #define LIRC_MODE_RAW                  0x00000001
>  #define LIRC_MODE_PULSE                0x00000002
>  #define LIRC_MODE_MODE2                0x00000004
> +#define LIRC_MODE_SCANCODE             0x00000008
>  #define LIRC_MODE_LIRCCODE             0x00000010
>  
>  
>  #define LIRC_CAN_SEND_RAW              LIRC_MODE2SEND(LIRC_MODE_RAW)
>  #define LIRC_CAN_SEND_PULSE            LIRC_MODE2SEND(LIRC_MODE_PULSE)
>  #define LIRC_CAN_SEND_MODE2            LIRC_MODE2SEND(LIRC_MODE_MODE2)
> +#define LIRC_CAN_SEND_SCANCODE         LIRC_MODE2SEND(LIRC_MODE_SCANCODE)
>  #define LIRC_CAN_SEND_LIRCCODE         LIRC_MODE2SEND(LIRC_MODE_LIRCCODE)
>  
>  #define LIRC_CAN_SEND_MASK             0x0000003f
> @@ -63,6 +65,7 @@
>  #define LIRC_CAN_REC_RAW               LIRC_MODE2REC(LIRC_MODE_RAW)
>  #define LIRC_CAN_REC_PULSE             LIRC_MODE2REC(LIRC_MODE_PULSE)
>  #define LIRC_CAN_REC_MODE2             LIRC_MODE2REC(LIRC_MODE_MODE2)
> +#define LIRC_CAN_REC_SCANCODE          LIRC_MODE2REC(LIRC_MODE_SCANCODE)
>  #define LIRC_CAN_REC_LIRCCODE          LIRC_MODE2REC(LIRC_MODE_LIRCCODE)
>  
>  #define LIRC_CAN_REC_MASK              LIRC_MODE2REC(LIRC_CAN_SEND_MASK)
> @@ -130,4 +133,94 @@
>  
>  #define LIRC_SET_WIDEBAND_RECEIVER     _IOW('i', 0x00000023, __u32)
>  
> +/*
> + * For raw IR devices, both raw IR (LIRC_MODE_MODE2) and decodes scancodes
> + * (LIRC_MODE_SCANCODE) can be read. By default, poll will show read
> + * ready for the last mode set by LIRC_SET_REC_MODE. Use LIRC_SET_POLL_MODE
> + * LIRC_MODE_SCANCODE | LIRC_MODE_MODE2 to show read ready for both
> + * modes.
> + */
> +#define LIRC_SET_POLL_MODE	       _IOW('i', 0x00000024, __u32)
> +
> +/*
> + * struct lirc_scancode - decoded scancode with protocol for use with
> + *	LIRC_MODE_SCANCODE
> + *
> + * @timestamp: Timestamp in nanoseconds using CLOCK_MONOTONIC when IR
> + *	was decoded.
> + * @flags: should be 0 for transmit. When receiving scancodes,
> + *	LIRC_SCANCODE_FLAG_TOGGLE or LIRC_SCANCODE_FLAG_REPEAT can be set
> + *	depending on the protocol
> + * @target: target for transmit. Unused, set to 0.
> + * @source: source for receive. Unused, set to 0.
> + * @unused: set to 0.
> + * @rc_proto: see enum rc_proto
> + * @scancode: the scancode received or to be sent
> + */
> +struct lirc_scancode {
> +	__u64	timestamp;
> +	__u32	flags;
> +	__u8	target;
> +	__u8	source;
> +	__u8	unused;
> +	__u8	rc_proto;
> +	__u64	scancode;

I'm thinking how this will be implemented using CEC. Some RC commands take arguments
(up to 4 bytes for the 0x67 (Tune Function) code), so how will they be handled?

See CEC table 6 in the HDMI 1.4 spec.

Should they be part of the scancode, or would it be better to add a '__u8 args[8];'
field?

I've no idea what makes sense, it's a weird corner case.

> +};
> +
> +#define LIRC_SCANCODE_FLAG_TOGGLE	1
> +#define LIRC_SCANCODE_FLAG_REPEAT	2

These flags need documentation.

Regards,

	Hans

> +
> +/**
> + * enum rc_proto - the Remote Controller protocol
> + *
> + * @RC_PROTO_UNKNOWN: Protocol not known
> + * @RC_PROTO_OTHER: Protocol known but proprietary
> + * @RC_PROTO_RC5: Philips RC5 protocol
> + * @RC_PROTO_RC5X_20: Philips RC5x 20 bit protocol
> + * @RC_PROTO_RC5_SZ: StreamZap variant of RC5
> + * @RC_PROTO_JVC: JVC protocol
> + * @RC_PROTO_SONY12: Sony 12 bit protocol
> + * @RC_PROTO_SONY15: Sony 15 bit protocol
> + * @RC_PROTO_SONY20: Sony 20 bit protocol
> + * @RC_PROTO_NEC: NEC protocol
> + * @RC_PROTO_NECX: Extended NEC protocol
> + * @RC_PROTO_NEC32: NEC 32 bit protocol
> + * @RC_PROTO_SANYO: Sanyo protocol
> + * @RC_PROTO_MCIR2_KBD: RC6-ish MCE keyboard
> + * @RC_PROTO_MCIR2_MSE: RC6-ish MCE mouse
> + * @RC_PROTO_RC6_0: Philips RC6-0-16 protocol
> + * @RC_PROTO_RC6_6A_20: Philips RC6-6A-20 protocol
> + * @RC_PROTO_RC6_6A_24: Philips RC6-6A-24 protocol
> + * @RC_PROTO_RC6_6A_32: Philips RC6-6A-32 protocol
> + * @RC_PROTO_RC6_MCE: MCE (Philips RC6-6A-32 subtype) protocol
> + * @RC_PROTO_SHARP: Sharp protocol
> + * @RC_PROTO_XMP: XMP protocol
> + * @RC_PROTO_CEC: CEC protocol
> + */
> +enum rc_proto {
> +	RC_PROTO_UNKNOWN	= 0,
> +	RC_PROTO_OTHER		= 1,
> +	RC_PROTO_RC5		= 2,
> +	RC_PROTO_RC5X_20	= 3,
> +	RC_PROTO_RC5_SZ		= 4,
> +	RC_PROTO_JVC		= 5,
> +	RC_PROTO_SONY12		= 6,
> +	RC_PROTO_SONY15		= 7,
> +	RC_PROTO_SONY20		= 8,
> +	RC_PROTO_NEC		= 9,
> +	RC_PROTO_NECX		= 10,
> +	RC_PROTO_NEC32		= 11,
> +	RC_PROTO_SANYO		= 12,
> +	RC_PROTO_MCIR2_KBD	= 13,
> +	RC_PROTO_MCIR2_MSE	= 14,
> +	RC_PROTO_RC6_0		= 15,
> +	RC_PROTO_RC6_6A_20	= 16,
> +	RC_PROTO_RC6_6A_24	= 17,
> +	RC_PROTO_RC6_6A_32	= 18,
> +	RC_PROTO_RC6_MCE	= 19,
> +	RC_PROTO_SHARP		= 20,
> +	RC_PROTO_XMP		= 21,
> +	RC_PROTO_CEC		= 22,
> +};
> +
>  #endif
>
Sean Young Oct. 9, 2017, 3:11 p.m. UTC | #2
On Mon, Oct 09, 2017 at 11:14:28AM +0200, Hans Verkuil wrote:
> On 05/10/17 10:45, Sean Young wrote:
-snip-
> > +/*
> > + * struct lirc_scancode - decoded scancode with protocol for use with
> > + *	LIRC_MODE_SCANCODE
> > + *
> > + * @timestamp: Timestamp in nanoseconds using CLOCK_MONOTONIC when IR
> > + *	was decoded.
> > + * @flags: should be 0 for transmit. When receiving scancodes,
> > + *	LIRC_SCANCODE_FLAG_TOGGLE or LIRC_SCANCODE_FLAG_REPEAT can be set
> > + *	depending on the protocol
> > + * @target: target for transmit. Unused, set to 0.
> > + * @source: source for receive. Unused, set to 0.
> > + * @unused: set to 0.
> > + * @rc_proto: see enum rc_proto
> > + * @scancode: the scancode received or to be sent
> > + */
> > +struct lirc_scancode {
> > +	__u64	timestamp;
> > +	__u32	flags;
> > +	__u8	target;
> > +	__u8	source;
> > +	__u8	unused;
> > +	__u8	rc_proto;
> > +	__u64	scancode;
> 
> I'm thinking how this will be implemented using CEC. Some RC commands take arguments
> (up to 4 bytes for the 0x67 (Tune Function) code), so how will they be handled?
> 
> See CEC table 6 in the HDMI 1.4 spec.
> 
> Should they be part of the scancode, or would it be better to add a '__u8 args[8];'
> field?
> 
> I've no idea what makes sense, it's a weird corner case.

I've given it some more thought.

For cec remote control passthrough, you have the tv with the IR receiver (A),
which then transmits CEC_MSG_USER_CONTROL_PRESSED and
CEC_MSG_USER_CONTROL_RELEASED cec messages to the correct target, with
arguments. Then on the target (B), it reads those commands and should execute
them as if it received them itself.

First of all (B) is already implemented in cec using rc-core. If RC
passthrough is enabled, then cec will pass those keycodes to rc-core (which
end up in an input device).

So the problem we are trying to solve here is (A). How I would see this
implemented is:

1) A physical IR receiver exists which has an rc-core driver and a /dev/lircN
   device. This is configured using ir-keytable to map to regular input events

2) A process receives input events, and decides that a particular key/command
   is not for itself (e.g. tell top set box to tune), so it knows what the
   target cec address is and the tune arguments, so it fills out a 
   cec_msg with the target, CEC_MSG_USER_CONTROL_PRESSED, 0x67, arguments,
   and then transmits it using the ioctl CEC_TRANSMIT, followed by
   another CEC_MSG_USER_CONTROL_RELEASED cec_msg sent using ioctl CEC_TRANSMIT.

In this way of viewing things, an rc-core device is either cec or lirc, and
thus rc-core lirc devices have a /dev/lircN and rc-core cec devices have a
/dev/cecN.

So, the alternative which is being proposed is that a cec device has both
a /dev/cecN and a /dev/lircN. In this case step 2) would look like:

2) A process receives input events, and decides that a particular key/command
   is not for itself (e.g. tell top set box to tune), so it knows what the
   target cec address is and the tune arguments, so it fills in a 
   lirc_scancode with the target, CEC_MSG_USER_CONTROL_PRESSED, 0x67, arguments,
   and then transmits it using write() to the /dev/lircN device, which
   then passes it on to cec_transmit() in drivers/media/cec/cec-api.c
   (without having a cec_fh), and then another lirc_scancode is
   filled in CEC_MSG_USER_CONTROL_RELEASED and sent.

Now, I think that this has a number of problems:

 - It's a lot of API for simply doing a CEC_TRANSMIT

 - and another chardev for a cec device (i.e. /dev/lircN).

 - lirc scancode tx deals with scancodes, for cec rc passthrough it isn't
   really scancodes.

 - Wiring this up is not going to be pretty or easy.

 - The lirc chardev has no other function other than sending
   CEC_MSG_USER_CONTROL_PRESSED and CEC_MSG_USER_CONTROL_RELEASED cec messages.
 
So what I am proposing is that we don't use lirc for sending rc passthrough
messages for cec.

I hope this makes sense and where not, please *do* tell me exactly where I
am wrong. I think that I missed something about the scancode tx idea.

> 
> > +};
> > +
> > +#define LIRC_SCANCODE_FLAG_TOGGLE	1
> > +#define LIRC_SCANCODE_FLAG_REPEAT	2
> 
> These flags need documentation.

They do, fair point.

Thanks,

Sean
Hans Verkuil Oct. 9, 2017, 3:26 p.m. UTC | #3
On 09/10/17 17:11, Sean Young wrote:
> On Mon, Oct 09, 2017 at 11:14:28AM +0200, Hans Verkuil wrote:
>> On 05/10/17 10:45, Sean Young wrote:
> -snip-
>>> +/*
>>> + * struct lirc_scancode - decoded scancode with protocol for use with
>>> + *	LIRC_MODE_SCANCODE
>>> + *
>>> + * @timestamp: Timestamp in nanoseconds using CLOCK_MONOTONIC when IR
>>> + *	was decoded.
>>> + * @flags: should be 0 for transmit. When receiving scancodes,
>>> + *	LIRC_SCANCODE_FLAG_TOGGLE or LIRC_SCANCODE_FLAG_REPEAT can be set
>>> + *	depending on the protocol
>>> + * @target: target for transmit. Unused, set to 0.
>>> + * @source: source for receive. Unused, set to 0.
>>> + * @unused: set to 0.
>>> + * @rc_proto: see enum rc_proto
>>> + * @scancode: the scancode received or to be sent
>>> + */
>>> +struct lirc_scancode {
>>> +	__u64	timestamp;
>>> +	__u32	flags;
>>> +	__u8	target;
>>> +	__u8	source;
>>> +	__u8	unused;
>>> +	__u8	rc_proto;
>>> +	__u64	scancode;
>>
>> I'm thinking how this will be implemented using CEC. Some RC commands take arguments
>> (up to 4 bytes for the 0x67 (Tune Function) code), so how will they be handled?
>>
>> See CEC table 6 in the HDMI 1.4 spec.
>>
>> Should they be part of the scancode, or would it be better to add a '__u8 args[8];'
>> field?
>>
>> I've no idea what makes sense, it's a weird corner case.
> 
> I've given it some more thought.
> 
> For cec remote control passthrough, you have the tv with the IR receiver (A),
> which then transmits CEC_MSG_USER_CONTROL_PRESSED and
> CEC_MSG_USER_CONTROL_RELEASED cec messages to the correct target, with
> arguments. Then on the target (B), it reads those commands and should execute
> them as if it received them itself.
> 
> First of all (B) is already implemented in cec using rc-core. If RC
> passthrough is enabled, then cec will pass those keycodes to rc-core (which
> end up in an input device).
> 
> So the problem we are trying to solve here is (A). How I would see this
> implemented is:
> 
> 1) A physical IR receiver exists which has an rc-core driver and a /dev/lircN
>    device. This is configured using ir-keytable to map to regular input events
> 
> 2) A process receives input events, and decides that a particular key/command
>    is not for itself (e.g. tell top set box to tune), so it knows what the
>    target cec address is and the tune arguments, so it fills out a 
>    cec_msg with the target, CEC_MSG_USER_CONTROL_PRESSED, 0x67, arguments,
>    and then transmits it using the ioctl CEC_TRANSMIT, followed by
>    another CEC_MSG_USER_CONTROL_RELEASED cec_msg sent using ioctl CEC_TRANSMIT.
> 
> In this way of viewing things, an rc-core device is either cec or lirc, and
> thus rc-core lirc devices have a /dev/lircN and rc-core cec devices have a
> /dev/cecN.
> 
> So, the alternative which is being proposed is that a cec device has both
> a /dev/cecN and a /dev/lircN. In this case step 2) would look like:
> 
> 2) A process receives input events, and decides that a particular key/command
>    is not for itself (e.g. tell top set box to tune), so it knows what the
>    target cec address is and the tune arguments, so it fills in a 
>    lirc_scancode with the target, CEC_MSG_USER_CONTROL_PRESSED, 0x67, arguments,
>    and then transmits it using write() to the /dev/lircN device, which
>    then passes it on to cec_transmit() in drivers/media/cec/cec-api.c
>    (without having a cec_fh), and then another lirc_scancode is
>    filled in CEC_MSG_USER_CONTROL_RELEASED and sent.
> 
> Now, I think that this has a number of problems:
> 
>  - It's a lot of API for simply doing a CEC_TRANSMIT
> 
>  - and another chardev for a cec device (i.e. /dev/lircN).
> 
>  - lirc scancode tx deals with scancodes, for cec rc passthrough it isn't
>    really scancodes.
> 
>  - Wiring this up is not going to be pretty or easy.
> 
>  - The lirc chardev has no other function other than sending
>    CEC_MSG_USER_CONTROL_PRESSED and CEC_MSG_USER_CONTROL_RELEASED cec messages.
>  
> So what I am proposing is that we don't use lirc for sending rc passthrough
> messages for cec.
> 
> I hope this makes sense and where not, please *do* tell me exactly where I
> am wrong. I think that I missed something about the scancode tx idea.

You are right, it makes no sense. Let's forget about this.

Regards,

	Hans

> 
>>
>>> +};
>>> +
>>> +#define LIRC_SCANCODE_FLAG_TOGGLE	1
>>> +#define LIRC_SCANCODE_FLAG_REPEAT	2
>>
>> These flags need documentation.
> 
> They do, fair point.
> 
> Thanks,
> 
> Sean
>
diff mbox

Patch

diff --git a/drivers/media/rc/ir-lirc-codec.c b/drivers/media/rc/ir-lirc-codec.c
index bd046c41a53a..ef0b8df88613 100644
--- a/drivers/media/rc/ir-lirc-codec.c
+++ b/drivers/media/rc/ir-lirc-codec.c
@@ -107,7 +107,8 @@  static ssize_t ir_lirc_transmit_ir(struct file *file, const char __user *buf,
 {
 	struct lirc_codec *lirc;
 	struct rc_dev *dev;
-	unsigned int *txbuf; /* buffer with values to transmit */
+	unsigned int *txbuf = NULL;
+	struct ir_raw_event *raw = NULL;
 	ssize_t ret = -EINVAL;
 	size_t count;
 	ktime_t start;
@@ -121,16 +122,51 @@  static ssize_t ir_lirc_transmit_ir(struct file *file, const char __user *buf,
 	if (!lirc)
 		return -EFAULT;
 
-	if (n < sizeof(unsigned) || n % sizeof(unsigned))
-		return -EINVAL;
+	if (lirc->send_mode == LIRC_MODE_SCANCODE) {
+		struct lirc_scancode scan;
 
-	count = n / sizeof(unsigned);
-	if (count > LIRCBUF_SIZE || count % 2 == 0)
-		return -EINVAL;
+		if (n != sizeof(scan))
+			return -EINVAL;
 
-	txbuf = memdup_user(buf, n);
-	if (IS_ERR(txbuf))
-		return PTR_ERR(txbuf);
+		if (copy_from_user(&scan, buf, sizeof(scan)))
+			return -EFAULT;
+
+		if (scan.flags || scan.source || scan.target || scan.unused ||
+		    scan.timestamp)
+			return -EINVAL;
+
+		raw = kmalloc_array(LIRCBUF_SIZE, sizeof(*raw), GFP_KERNEL);
+		if (!raw)
+			return -ENOMEM;
+
+		ret = ir_raw_encode_scancode(scan.rc_proto, scan.scancode,
+					     raw, LIRCBUF_SIZE);
+		if (ret < 0)
+			goto out;
+
+		count = ret;
+
+		txbuf = kmalloc_array(count, sizeof(unsigned int), GFP_KERNEL);
+		if (!txbuf) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		for (i = 0; i < count; i++)
+			/* Convert from NS to US */
+			txbuf[i] = DIV_ROUND_UP(raw[i].duration, 1000);
+	} else {
+		if (n < sizeof(unsigned int) || n % sizeof(unsigned int))
+			return -EINVAL;
+
+		count = n / sizeof(unsigned int);
+		if (count > LIRCBUF_SIZE || count % 2 == 0)
+			return -EINVAL;
+
+		txbuf = memdup_user(buf, n);
+		if (IS_ERR(txbuf))
+			return PTR_ERR(txbuf);
+	}
 
 	dev = lirc->dev;
 	if (!dev) {
@@ -156,24 +192,31 @@  static ssize_t ir_lirc_transmit_ir(struct file *file, const char __user *buf,
 	if (ret < 0)
 		goto out;
 
-	for (duration = i = 0; i < ret; i++)
-		duration += txbuf[i];
-
-	ret *= sizeof(unsigned int);
-
-	/*
-	 * The lircd gap calculation expects the write function to
-	 * wait for the actual IR signal to be transmitted before
-	 * returning.
-	 */
-	towait = ktime_us_delta(ktime_add_us(start, duration), ktime_get());
-	if (towait > 0) {
-		set_current_state(TASK_INTERRUPTIBLE);
-		schedule_timeout(usecs_to_jiffies(towait));
+	if (lirc->send_mode == LIRC_MODE_SCANCODE) {
+		ret = n;
+	} else {
+		for (duration = i = 0; i < ret; i++)
+			duration += txbuf[i];
+
+
+		ret *= sizeof(unsigned int);
+
+		/*
+		 * The lircd gap calculation expects the write function to
+		 * wait for the actual IR signal to be transmitted before
+		 * returning.
+		 */
+		towait = ktime_us_delta(ktime_add_us(start, duration),
+					ktime_get());
+		if (towait > 0) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(usecs_to_jiffies(towait));
+		}
 	}
 
 out:
 	kfree(txbuf);
+	kfree(raw);
 	return ret;
 }
 
@@ -202,20 +245,22 @@  static long ir_lirc_ioctl(struct file *filep, unsigned int cmd,
 
 	switch (cmd) {
 
-	/* legacy support */
+	/* mode support */
 	case LIRC_GET_SEND_MODE:
 		if (!dev->tx_ir)
 			return -ENOTTY;
 
-		val = LIRC_MODE_PULSE;
+		val = lirc->send_mode;
 		break;
 
 	case LIRC_SET_SEND_MODE:
 		if (!dev->tx_ir)
 			return -ENOTTY;
 
-		if (val != LIRC_MODE_PULSE)
+		if (!(val == LIRC_MODE_PULSE || val == LIRC_MODE_SCANCODE))
 			return -EINVAL;
+
+		lirc->send_mode = val;
 		return 0;
 
 	/* TX settings */
@@ -358,7 +403,7 @@  static int ir_lirc_register(struct rc_dev *dev)
 	}
 
 	if (dev->tx_ir) {
-		features |= LIRC_CAN_SEND_PULSE;
+		features |= LIRC_CAN_SEND_PULSE | LIRC_CAN_SEND_SCANCODE;
 		if (dev->s_tx_mask)
 			features |= LIRC_CAN_SET_TRANSMITTER_MASK;
 		if (dev->s_tx_carrier)
@@ -397,6 +442,8 @@  static int ir_lirc_register(struct rc_dev *dev)
 	if (rc < 0)
 		goto out;
 
+	dev->raw->lirc.send_mode = LIRC_MODE_PULSE;
+
 	dev->raw->lirc.ldev = ldev;
 	dev->raw->lirc.dev = dev;
 	return 0;
diff --git a/drivers/media/rc/rc-core-priv.h b/drivers/media/rc/rc-core-priv.h
index ae4dd0c27731..43eabea9f152 100644
--- a/drivers/media/rc/rc-core-priv.h
+++ b/drivers/media/rc/rc-core-priv.h
@@ -113,7 +113,7 @@  struct ir_raw_event_ctrl {
 		u64 gap_duration;
 		bool gap;
 		bool send_timeout_reports;
-
+		u8 send_mode;
 	} lirc;
 	struct xmp_dec {
 		int state;
diff --git a/include/media/rc-map.h b/include/media/rc-map.h
index 2a160e6e823c..00e033975eed 100644
--- a/include/media/rc-map.h
+++ b/include/media/rc-map.h
@@ -10,59 +10,7 @@ 
  */
 
 #include <linux/input.h>
-
-/**
- * enum rc_proto - the Remote Controller protocol
- *
- * @RC_PROTO_UNKNOWN: Protocol not known
- * @RC_PROTO_OTHER: Protocol known but proprietary
- * @RC_PROTO_RC5: Philips RC5 protocol
- * @RC_PROTO_RC5X_20: Philips RC5x 20 bit protocol
- * @RC_PROTO_RC5_SZ: StreamZap variant of RC5
- * @RC_PROTO_JVC: JVC protocol
- * @RC_PROTO_SONY12: Sony 12 bit protocol
- * @RC_PROTO_SONY15: Sony 15 bit protocol
- * @RC_PROTO_SONY20: Sony 20 bit protocol
- * @RC_PROTO_NEC: NEC protocol
- * @RC_PROTO_NECX: Extended NEC protocol
- * @RC_PROTO_NEC32: NEC 32 bit protocol
- * @RC_PROTO_SANYO: Sanyo protocol
- * @RC_PROTO_MCIR2_KBD: RC6-ish MCE keyboard
- * @RC_PROTO_MCIR2_MSE: RC6-ish MCE mouse
- * @RC_PROTO_RC6_0: Philips RC6-0-16 protocol
- * @RC_PROTO_RC6_6A_20: Philips RC6-6A-20 protocol
- * @RC_PROTO_RC6_6A_24: Philips RC6-6A-24 protocol
- * @RC_PROTO_RC6_6A_32: Philips RC6-6A-32 protocol
- * @RC_PROTO_RC6_MCE: MCE (Philips RC6-6A-32 subtype) protocol
- * @RC_PROTO_SHARP: Sharp protocol
- * @RC_PROTO_XMP: XMP protocol
- * @RC_PROTO_CEC: CEC protocol
- */
-enum rc_proto {
-	RC_PROTO_UNKNOWN	= 0,
-	RC_PROTO_OTHER		= 1,
-	RC_PROTO_RC5		= 2,
-	RC_PROTO_RC5X_20	= 3,
-	RC_PROTO_RC5_SZ		= 4,
-	RC_PROTO_JVC		= 5,
-	RC_PROTO_SONY12		= 6,
-	RC_PROTO_SONY15		= 7,
-	RC_PROTO_SONY20		= 8,
-	RC_PROTO_NEC		= 9,
-	RC_PROTO_NECX		= 10,
-	RC_PROTO_NEC32		= 11,
-	RC_PROTO_SANYO		= 12,
-	RC_PROTO_MCIR2_KBD	= 13,
-	RC_PROTO_MCIR2_MSE	= 14,
-	RC_PROTO_RC6_0		= 15,
-	RC_PROTO_RC6_6A_20	= 16,
-	RC_PROTO_RC6_6A_24	= 17,
-	RC_PROTO_RC6_6A_32	= 18,
-	RC_PROTO_RC6_MCE	= 19,
-	RC_PROTO_SHARP		= 20,
-	RC_PROTO_XMP		= 21,
-	RC_PROTO_CEC		= 22,
-};
+#include <uapi/linux/lirc.h>
 
 #define RC_PROTO_BIT_NONE		0ULL
 #define RC_PROTO_BIT_UNKNOWN		BIT_ULL(RC_PROTO_UNKNOWN)
diff --git a/include/uapi/linux/lirc.h b/include/uapi/linux/lirc.h
index 991ab4570b8e..312e37812783 100644
--- a/include/uapi/linux/lirc.h
+++ b/include/uapi/linux/lirc.h
@@ -46,12 +46,14 @@ 
 #define LIRC_MODE_RAW                  0x00000001
 #define LIRC_MODE_PULSE                0x00000002
 #define LIRC_MODE_MODE2                0x00000004
+#define LIRC_MODE_SCANCODE             0x00000008
 #define LIRC_MODE_LIRCCODE             0x00000010
 
 
 #define LIRC_CAN_SEND_RAW              LIRC_MODE2SEND(LIRC_MODE_RAW)
 #define LIRC_CAN_SEND_PULSE            LIRC_MODE2SEND(LIRC_MODE_PULSE)
 #define LIRC_CAN_SEND_MODE2            LIRC_MODE2SEND(LIRC_MODE_MODE2)
+#define LIRC_CAN_SEND_SCANCODE         LIRC_MODE2SEND(LIRC_MODE_SCANCODE)
 #define LIRC_CAN_SEND_LIRCCODE         LIRC_MODE2SEND(LIRC_MODE_LIRCCODE)
 
 #define LIRC_CAN_SEND_MASK             0x0000003f
@@ -63,6 +65,7 @@ 
 #define LIRC_CAN_REC_RAW               LIRC_MODE2REC(LIRC_MODE_RAW)
 #define LIRC_CAN_REC_PULSE             LIRC_MODE2REC(LIRC_MODE_PULSE)
 #define LIRC_CAN_REC_MODE2             LIRC_MODE2REC(LIRC_MODE_MODE2)
+#define LIRC_CAN_REC_SCANCODE          LIRC_MODE2REC(LIRC_MODE_SCANCODE)
 #define LIRC_CAN_REC_LIRCCODE          LIRC_MODE2REC(LIRC_MODE_LIRCCODE)
 
 #define LIRC_CAN_REC_MASK              LIRC_MODE2REC(LIRC_CAN_SEND_MASK)
@@ -130,4 +133,94 @@ 
 
 #define LIRC_SET_WIDEBAND_RECEIVER     _IOW('i', 0x00000023, __u32)
 
+/*
+ * For raw IR devices, both raw IR (LIRC_MODE_MODE2) and decodes scancodes
+ * (LIRC_MODE_SCANCODE) can be read. By default, poll will show read
+ * ready for the last mode set by LIRC_SET_REC_MODE. Use LIRC_SET_POLL_MODE
+ * LIRC_MODE_SCANCODE | LIRC_MODE_MODE2 to show read ready for both
+ * modes.
+ */
+#define LIRC_SET_POLL_MODE	       _IOW('i', 0x00000024, __u32)
+
+/*
+ * struct lirc_scancode - decoded scancode with protocol for use with
+ *	LIRC_MODE_SCANCODE
+ *
+ * @timestamp: Timestamp in nanoseconds using CLOCK_MONOTONIC when IR
+ *	was decoded.
+ * @flags: should be 0 for transmit. When receiving scancodes,
+ *	LIRC_SCANCODE_FLAG_TOGGLE or LIRC_SCANCODE_FLAG_REPEAT can be set
+ *	depending on the protocol
+ * @target: target for transmit. Unused, set to 0.
+ * @source: source for receive. Unused, set to 0.
+ * @unused: set to 0.
+ * @rc_proto: see enum rc_proto
+ * @scancode: the scancode received or to be sent
+ */
+struct lirc_scancode {
+	__u64	timestamp;
+	__u32	flags;
+	__u8	target;
+	__u8	source;
+	__u8	unused;
+	__u8	rc_proto;
+	__u64	scancode;
+};
+
+#define LIRC_SCANCODE_FLAG_TOGGLE	1
+#define LIRC_SCANCODE_FLAG_REPEAT	2
+
+/**
+ * enum rc_proto - the Remote Controller protocol
+ *
+ * @RC_PROTO_UNKNOWN: Protocol not known
+ * @RC_PROTO_OTHER: Protocol known but proprietary
+ * @RC_PROTO_RC5: Philips RC5 protocol
+ * @RC_PROTO_RC5X_20: Philips RC5x 20 bit protocol
+ * @RC_PROTO_RC5_SZ: StreamZap variant of RC5
+ * @RC_PROTO_JVC: JVC protocol
+ * @RC_PROTO_SONY12: Sony 12 bit protocol
+ * @RC_PROTO_SONY15: Sony 15 bit protocol
+ * @RC_PROTO_SONY20: Sony 20 bit protocol
+ * @RC_PROTO_NEC: NEC protocol
+ * @RC_PROTO_NECX: Extended NEC protocol
+ * @RC_PROTO_NEC32: NEC 32 bit protocol
+ * @RC_PROTO_SANYO: Sanyo protocol
+ * @RC_PROTO_MCIR2_KBD: RC6-ish MCE keyboard
+ * @RC_PROTO_MCIR2_MSE: RC6-ish MCE mouse
+ * @RC_PROTO_RC6_0: Philips RC6-0-16 protocol
+ * @RC_PROTO_RC6_6A_20: Philips RC6-6A-20 protocol
+ * @RC_PROTO_RC6_6A_24: Philips RC6-6A-24 protocol
+ * @RC_PROTO_RC6_6A_32: Philips RC6-6A-32 protocol
+ * @RC_PROTO_RC6_MCE: MCE (Philips RC6-6A-32 subtype) protocol
+ * @RC_PROTO_SHARP: Sharp protocol
+ * @RC_PROTO_XMP: XMP protocol
+ * @RC_PROTO_CEC: CEC protocol
+ */
+enum rc_proto {
+	RC_PROTO_UNKNOWN	= 0,
+	RC_PROTO_OTHER		= 1,
+	RC_PROTO_RC5		= 2,
+	RC_PROTO_RC5X_20	= 3,
+	RC_PROTO_RC5_SZ		= 4,
+	RC_PROTO_JVC		= 5,
+	RC_PROTO_SONY12		= 6,
+	RC_PROTO_SONY15		= 7,
+	RC_PROTO_SONY20		= 8,
+	RC_PROTO_NEC		= 9,
+	RC_PROTO_NECX		= 10,
+	RC_PROTO_NEC32		= 11,
+	RC_PROTO_SANYO		= 12,
+	RC_PROTO_MCIR2_KBD	= 13,
+	RC_PROTO_MCIR2_MSE	= 14,
+	RC_PROTO_RC6_0		= 15,
+	RC_PROTO_RC6_6A_20	= 16,
+	RC_PROTO_RC6_6A_24	= 17,
+	RC_PROTO_RC6_6A_32	= 18,
+	RC_PROTO_RC6_MCE	= 19,
+	RC_PROTO_SHARP		= 20,
+	RC_PROTO_XMP		= 21,
+	RC_PROTO_CEC		= 22,
+};
+
 #endif