Re: [PATCH 1/2] tty: add bits to manage multidrop mode

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 21/04/17 18:19, Rodolfo Giometti wrote:
> On 04/21/17 00:14, Stuart Longland wrote:
>> On 21/04/17 02:17, Rodolfo Giometti wrote:
>>> AFAIK the multidrop mode is supported also by Freescale (IMX6 and maybe
>>> IMX7) even if by using directly 9 bits transmission. The SENDA trick
>>> (that is autogically sending the next first byte as an address while the
>>> following ones as data) is Atmel specific. Atmel solution is really
>>> smart (even if limited to 1 byte addresses long) because it allows
>>> developers to send a message without any delay between the address and
>>> the data bytes.
>>
>> I've seen similar features on the NXP LPC81x series of microcontrollers.
>>
>> http://www.nxp.com/documents/user_manual/UM10601.pdf page 187 onwards
>> covers the USART for these chips.  The data register is natively 32-bits
>> wide, so can accommodate the 9th bit easily.
>>
>> On Atmel AVR, I've also seen it, there the 9th bit is held in a separate
>> register from what I recall, don't have a datasheet in front of me to
>> look it up.
> 
> Yes, Atmel CPUs can handle multidrop in two manners: the first one is by
> using the 9bits transmission while the second is by using the SENDA bit.
> The latter is obviously less versatile and less general then the former
> but it really better fits into actual Linux TTY subsystem.

Indeed, and if someone were to later figure out CS9 support, I doubt the
two are mutually exclusive.

>> The 9-bit mode sounded like a good idea, and I thought of how to
>> possibly implement it, but then I had to contend with where would I put
>> the extra bit in an 8-bit byte?
> 
> This is exactly the problem I notice in adding multidrop support in
> Linux trying to avoid the two calls of the tcsetattr() function which
> introduce a delay between the address and data bytes.

Fully agreed there… lots of protocols are sensitive to that kind of
timing.  Modbus/RTU, although it is purely 8-bit, is one that comes to
mind where it comes to timing between adjacent bytes.


> IMHO the best solution would be using 9bits transmission (CS9 define):
> 
>     term.c_cflag &= ~CSIZE;
>     term.c_cflag |= CS9;
>         ret = tcsetattr(fd, TCSADRAIN, &term);
>         ...
> 
>         /* Write data */
>         n = 0;
>         buf[n++] = 0x100;
>         buf[n++] = 0x01c;
>         buf[n++] = 0x001;
>         buf[n++] = 0x001;
>         buf[n++] = 0x000;
>         buf[n++] = 0x001;
>         buf[n++] = 0x0de;
>         buf[n++] = 0x0d4;
>         ret = writen(fd, buf, 2*n);
>         ...

Okay, this is obviously not pretty, but is it possible to cast the
stream of bytes into 16-bit words (host native byte ordering)?

Userspace is responsible from the first byte written after CS9 is set,
until the last word before CS9 is cleared, in ensuring that all writes
are an even number of bytes, or -EMSGSIZE is returned ("too big" in the
sense you've sent one more than is valid).  This can be checked before
we start handling the actual data.

If we ensure that the buffer is always allocated on a 16-bit alignment
(or better; native word size alignment, so 32 or 64-bits), then you can
treat the buffer as an array of uint16_t instead of uint8_t without issue.

The cleaner way around is to make the tty layer natively deal with
uint16_t… technically superior, but as you point out, that's a lot of work.

> Then my solution tries to keep it simple just still
> using 8bits transmission but adding a system call to define how many
> address bytes we have into next message:
> 
>          /* Transmission: enable parity multidrop */
>         term.c_cflag |= PARENB | CMSPAR | PARMD;
>         ret = tcsetattr(fd, TCSADRAIN, &term);
>     ...
> 
>     mdrop.addr_len = 1;
>     ret = ioctl(fd, TCSSENDA, &mdrop);
>     ...
> 
>         /* Write data */
>         n = 0;
>         buf[n++] = 0x00;
>         buf[n++] = 0x1c;
>         buf[n++] = 0x01;
>         buf[n++] = 0x01;
>         buf[n++] = 0x00;
>         buf[n++] = 0x01;
>         buf[n++] = 0xde;
>         buf[n++] = 0xd4;
>         ret = writen(fd, buf, n);
>     ...
> 
> [Note that Atmel CPUs support only 1 byte for address length]

That looks clean and simple enough… I used the example of two address
bytes to depict a "generic" solution, with the thought of that `mdrop`
struct perhaps having a couple of members:

	struct mdrop_t {
		int addr_len;
		uint8_t* addr;
	} mdrop;

	uint8_t addr[1] = {0x00};
	mdrop.addr_len = 1;
	mdrop.addr = addr;
	ret = ioctl(fd, TCSSETADDR, &mdrop);

Then each frame, you'd call

	ret = ioctl(fd, TCSSENDA);

before writing a frame.  That would automatically prepend that buffer.
Want to send another frame?  Call ioctl(fd, TCSSENDA) again then start
writing.

Your example there though would be better if you're sending to lots of
addresses… in my example, you'd have to call TCSSETADDR again to set a
different address.  In that regard, your solution is much simpler (and
requires fewer ioctls to boot).

The thorny issue IMO is on receive.  Userspace really needs to know
where the address starts and ends.  You can assume a fixed size, but if
the protocol uses a variable address length, you're screwed.

Whilst not essential, an in-kernel frame address filter could be useful
for that purpose.  I'm taking the idea from how CANbus filters work.
(Thus, LinuxCAN might be worth looking at for ideas here.)

http://www.cse.dmu.ac.uk/~eg/tele/CanbusIDandMask.html

The idea being that the kernel starts to buffer when it sees the
"address" bit go high but delays notifying userspace until it has the
full address.  If a mismatch is discovered, it clears the receive buffer
and waits for the next frame.

Otherwise, it sets a counter to say how many bytes are address bytes
(accessed via ioctl) and makes the entire frame (with address) available
to userspace.  This stops when the address bit goes high again,
indicating a new frame.

Doing without the filter is doable too; we just start buffering when
address goes high and notify userspace when we receive the first data
byte, the userspace application can then ask how many of the incoming
bytes are address bytes and start reading as normal.

A further ioctl might be used to manually reset the state machine from
userspace ready for the start of another frame.

If a new frame comes in before the previous one is read… I guess we
either ignore the new frame or we throw away the not-yet read one,
unless we want to do something fancy like copying entire frames to
specially allocated buffers.

> Since multidrop is used with RS485 we can think to add all these stuff
> into TIOCSRS485 ioctl() as below:
> 
>         rs485conf.flags |= SER_MULTIDROP_ENABLED;
>         rs485conf.delay_rts_after_send = 0;
> 
>         ret = ioctl(fd, TIOCSRS485, &rs485conf);
>     ...

Conceivably you could use multi-drop with a number of scenarios, not
just RS-485.  e.g. DebugWire on AVR uses a UART-style link on the reset
pin where the line is effectively open-collector and the Tx pulls the
line low.  The same style of signalling could be used for multi-drop
communications over short distances.

I am splitting hairs here though. :-)

> However we still need and ioctl() command to state when new address
> bytes should be sent in the data stream...
> 
> We can use different solutions but the real problem is avoiding a delay
> between the address and data bytes and to do so we must find a way to
> completely support 9 bits transmission or tricks as the SENDA bit.

Indeed, whilst thinking about transmission though, we should not forget
the need to be able to receive properly either. :-)

I have a pet project where I'm coding up a LPC810 MCU to use as an I²C
UART chip since the chip lacks the RAM/flash to do much useful (but
*does* have hardware UARTs) and there's a distinct lack of I²C UART
chips out there in a DIP package, target being for adding extra UARTs to
hobby single-board computers like the Raspberry Pi.

Whatever solution gets decided here, I'll probably be coding the
firmware (which will be GPLv2 licensed) in that chip to suit the
interface in the Linux kernel.
-- 
Stuart Longland (aka Redhatter, VK4MSL)

I haven't lost my mind...
  ...it's backed up on a tape somewhere.
--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux PPP]     [Linux FS]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Linmodem]     [Device Mapper]     [Linux Kernel for ARM]

  Powered by Linux