Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

bottom part of serial port support on Unix #29033

Merged
merged 11 commits into from Jul 5, 2018
Merged

bottom part of serial port support on Unix #29033

merged 11 commits into from Jul 5, 2018

Conversation

wfurt
Copy link
Member

@wfurt wfurt commented Apr 11, 2018

This is part of original #28415 after addressing feedback. We publish System.IO.Ports as separate package so we can iterate on upper parts later.

I'm at the point where I can run Serial port tests and I still get 20-30 failures (out of few hundred) depending in what HW I uses. The upper part still may need some polishing so as the test suite.

In general issues seems to be in two areas:

  • discarding sent data when using USB/Serial adapter does not work well. That seems to be know limitation with Linux drivers. (or possibly USB)
  • manipulating buffer sizes does not work as on Windows. That breaks tests.

@wfurt wfurt added the os-linux Linux OS (any supported distro) label Apr 11, 2018
@wfurt wfurt self-assigned this Apr 11, 2018
@joshfree joshfree added this to the 2.2.0 milestone Apr 11, 2018
@joshfree
Copy link
Member

tagging this 2.2.0 per #29033

internal static partial class Sys
{
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_TermiosReset", SetLastError = true)]
internal static extern int TerminalReset(SafeFileHandle fd, int speed, int data, StopBits stop, Parity parity, Handshake flow);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods should have the same name as the native counterpart, minus the "SystemNative_" prefix, so e.g. TermiosReset instead of TerminalReset.

@@ -0,0 +1,360 @@
// Licensed to the .NET Foundation under one or more agreements.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're now trying to use C rather than C++ moving forward, so that more and more can be shared with Mono. It'd be good to not add to that debt. See #25032 for more background.
cc: @luhenry

extern "C" int32_t SystemNative_TermiosGetDtr(int fd)
{
return SystemNative_TermiosGetSignal(fd, TIOCM_DTR);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to expose so many externs? Or could we just expose TermiosGetSignal as well as a PAL'd set of symbols for the bit argument?

case B75:
return 75;
case B50:
return 50;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these constant B values not always have the same numerical value as what their name suggests?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They don't, they have completely different values. They are enum-like, with values going up by one upto B38400. Above that, there is a large jump in the numbering and then they go again by one.

/**
* Get states of serial line.
*/
extern "C" int32_t SystemNative_TermiosGetDcd(int fd);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use BEGIN_EXTERN_C and END_EXTERN_C before and after the functions declarations, that will avoid you to repeat extern "C".

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we can simply rename pal_termios.cpp to pal_termios.c, as there is no real dependency on libstdc++ in there, and we already have some C files in the codebase.

I know it was discussed previously, but I really see no harm either way. It's just a matter of picking dependencies sparingly for each build unit separately (just like I wouldn't add reference to a package in managed code unless I need it), otherwise one can always argue "we already depend on libstdc++ for 10 other reasons".

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still need to be able to be included in C++ files, thus the need for extern "C". My comment is only to help you reduce repetition, it's not a mandated or required change.

internal static extern int TerminalReset(SafeFileHandle fd, int speed, int data, StopBits stop, Parity parity, Handshake flow);

[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_TermiosGetDcd", SetLastError = true)]
internal static extern int TerminalGetCd(SafeFileHandle fd);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are missing "D" in the function name - the function should end "GetDcd" instead of "GetCd".

}

static speed_t
SystemNative_TermiosSpeed2rate(int speed)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the "r" in the "rate" in the function name should be upper case

}

static int
SystemNative_TermiosRate2speed(speed_t brate)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the "s" in the "speed" in the function name should be upper case

case B75:
return 75;
case B50:
return 50;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They don't, they have completely different values. They are enum-like, with values going up by one upto B38400. Above that, there is a large jump in the numbering and then they go again by one.

term.c_cflag &= ~(static_cast<tcflag_t>(PARODD));
break;
default:
term.c_cflag &= ~(static_cast<tcflag_t>(PARENB | PARODD));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - here you clear the parity bits in the default and in the code below, you pre-clear the xon/xoff and rts/cts bits. It would be nice to be consistent.

@wfurt
Copy link
Member Author

wfurt commented Apr 25, 2018

Thanks for all suggestions. I’m out without computer access until 5/8. I’ll deal with feedback when I get back.

@jkotas
Copy link
Member

jkotas commented May 9, 2018

Copying comment from #28415 (comment) here as well:

System.IO.Ports is standalone package, but this is adding dependency on System.Native. System.Native is internal implementation detail. We do not want things outside .NET Core distro to depend on it.

Should System.IO.Ports have its own supporting native library instead? Or depend on Mono.Posix?

@tedvanderveen
Copy link

Hi @wfurt @janvorli what's the expected timeframe for this PR to be merged?

case 50:
return B50;
}
return ((speed_t)(speed));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should return some indication of failure when we don't match any of the supported speeds instead of casting just the passed in value to the speed_t in case we don't find any match.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Linux supports custom speeds but I did not get to testing it.
I decided to return 0 if speed does not match one of the supported values and treat it as error.
On Unix B0 has special meaning but I don't think it is on Windows. So I think it is ok to use it. speed_t is unsigned long so we can possibly use other value.

static int TermiosGetStatus(int32_t fd)
{
int status = 0;
if(ioctl(fd, TIOCMGET, &status) < 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - please add space between the if and the opening ( at multiple places.

return -1;
}

status = TermiosGetStatus(fd);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - please remove one extra space between the status and =

{
switch (speed)
{
case 230400:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see that there are more speeds (upto B4000000) on my Ubuntu 16.04. Should we support all of them here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The high speeds are not portable. OSX does not have them and FreeBSD has some.
So I added them conditionally and we should be able to support them on platforms where available.

int32_t
TermiosGetSpeed(int32_t fd)
{
struct termios term;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - can you please remove the extra spaces between termios and term here and in the TermiosSetSpeed and TermiosReset functions below?

TermiosAvailableBytes(int32_t fd, int32_t readBuffer)
{
int32_t bytes;
if (ioctl (fd, readBuffer ? FIONREAD : TIOCOUTQ, &bytes) == -1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - please put the { on a separate line.

int32_t
TermiosDiscard(int32_t fd, int32_t queue)
{
return tcflush(fd, queue == 0 ? TCIOFLUSH : queue == 1 ? TCIFLUSH : TCOFLUSH);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - would you mind adding parentheses around each of the conditions here to make it easier to read?


cfmakeraw(&term);
term.c_cflag |= (CLOCAL | CREAD);
term.c_lflag &= ~((tcflag_t)(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ISIG | IEXTEN ));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder - why don't we set all the flags ourselves and depend on the preinitialization by the cfmakeraw instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we possibly could. In some ways I like it explicit as I see what this exactly does without looking at man pages (with cfmakeraw as base line)
Even if not supported in first round, I assume serial port will be supported on all Unix like systems so I would need to also verify that cfmakeraw() works same on all OSes.

AllQueues = 3,
};

[DllImport(Libraries.IOPortsNative, EntryPoint = "TermiosReset", SetLastError = true)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at other mini-PALs we have, it looks like the native functions should be named with underscore between the Termios and the rest of the function name to match the convention used elsewhere. So e.g. Termios_Reset.

internal static extern int TermiosGetSignal(SafeFileHandle fd, Signals signal, int set);

[DllImport(Libraries.IOPortsNative, EntryPoint = "Termios_SetSpeed", SetLastError = true)]
internal static extern int TermiosSetSpeed(SafeFileHandle fd, int speed);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The speed argument of the unmanaged method is unsigned.


int32_t Termios_AvailableBytes(int32_t fd, int32_t readBuffer);

int32_t Termios_Reset(int32_t fd, int speed, int dataBits, int stopBits, int parity, int handshake);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We prefer explicitly sized types. From https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/interop-guidelines.md#unix-shims:

Use int32_t, int64_t, etc. from stdint.h and not int, long, etc.

@wfurt
Copy link
Member Author

wfurt commented May 24, 2018

Windows Nano test failures are unrelated to this.

};

/* Interop/Unix/Interop.Termios.cs */
enum {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - can you please put the curly brace on a separate line like in the previous enums?

SignalDcd = 5,
};

enum {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - can you please put the curly brace on a separate line like in the previous enums?

{
internal enum Signals
{
SignalDtr = 1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub, what is the convention for enums shared between native and managed code - should the names of the members be prefixed by the "Signal" even on the managed side?
And, should the name of the enum type really be plural?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the names of the members be prefixed by the "Signal" even on the managed side?

In most cases we name the enum value the same as the native symbol and we name the enum something meaningful / descriptive, e.g.

        [Flags]
        internal enum MemoryMappedProtections
        {
            PROT_NONE = 0x0,
            PROT_READ = 0x1,
            PROT_WRITE = 0x2,
            PROT_EXEC = 0x4
        }

{
internal static partial class Serial
{
[DllImport(Libraries.IOPortsNative, EntryPoint = "SerialPortOpen", SetLastError = true)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that even for this function, we should follow the convention of other similar ones and name it with Serial_ prefix. Maybe Serial_OpenPort ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should prefix all of the exports with a name related to the library, e.g. everything in System.Native.so is prefixed with SystemNative_.


BEGIN_EXTERN_C

int SerialPortOpen(const char * name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've generally prefixed all exports with the name of the library, e.g. SystemIoPortsNative_SerialPortOpen.

SendQueue = 2,
};

static int TermiosGetStatus(int32_t fd)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other libraries, we've used intptr_t for file descriptors at the interop layer. Then we can use SafeHandle in the P/Invokes. We should do the same here, e.g.

int32_t SystemNative_GetIPv6MulticastOption(intptr_t socket, int32_t multicastOption, IPv6MulticastOption* option)
{
if (option == NULL)
{
return Error_EFAULT;
}
int fd = ToFileDescriptor(socket);


int32_t Termios_SetSignal(int32_t fd, int32_t signal, int32_t set)
{
int status;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be moved down to where it's actually initialized?

}

static speed_t
TermiosSpeed2Rate(int speed)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: why is this on its own line?

}

int32_t
Termios_GetSpeed(int32_t fd)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question for all of these... why have a newline between the return type and the name?



/* Open device file in non-blocking mode and without controlling terminal */
int SerialPortOpen(const char * name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in another comment, we should represent file descriptors at the interop layer as intptr_t. As it's currently written, on a 64-bit platform there's potentially going to be corruption here, as this is going to return a 32-bit value and the managed code is expected a 64-bit value.

@@ -0,0 +1,22 @@
project(System.IO.Ports.Native)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add C

project(System.IO.Ports.Native C)

#include "pal_types.h"
#include "pal_compiler.h"

BEGIN_EXTERN_C
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This macro and its END_ counterpart are not defined anymore and can be removed. Now we are pure C.


BEGIN_EXTERN_C

intptr_t SystemIoPortsNative_SerialPortOpen(const char * name);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefix misssing:
DLLEXPORT intptr_t

same for add SystemIOPortNative_* in .h files only.

return status;
}

int32_t SystemIoPortsNative_TermiosGetSignal(intptr_t handle, int32_t signal)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casing of IO, should be: SystemIOPortsNative_ ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. I used prefix @stephentoub suggested.

while ((result = open(name, O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK)) < 0 && errno == EINTR);
return result;
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: there are two extra blank lines at the end of this file, reduce it to one blank line?

#include <errno.h>
#include <pal_serial.h>


Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we have one blank line elsewhere

return -1;
}

static speed_t SystemIoPortsNative_TermiosSpeed2Rate(int speed)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you don't need the prefix for non-exported functions, though it's fine to have it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I previously did not have them for internal static functions.
But I decided it may be better to just keep everything same.
Thanks for all your feedback.

@stephentoub
Copy link
Member

@wfurt, looks like this needs to be rebased.

@stephentoub
Copy link
Member

looks like this needs to be rebased.

Actually, I'm wrong, it's fine for a squash and merge. It would only have issues with a rebase and merge because some of the earlier commits have conflicts.

@wfurt wfurt merged commit 83753bc into dotnet:master Jul 5, 2018
@wfurt wfurt deleted the serial2 branch June 15, 2020 18:21
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
* bottom part of serial port support

* small cleanup

* feedback from reviews

* remove previous changes to System.Native

* feedback from review

* use consistently int32_t

* update names and types based on feedback

* feedback from kasper3


Commit migrated from dotnet/corefx@83753bc
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.IO os-linux Linux OS (any supported distro)
Projects
None yet
8 participants