Invalid argument error from tcsetattr when setting speed to anything other than predefined

I'm trying to communicate with an RS-485 device using a USB-to-RS-485 adapter based on a CP21012N. It's capable of speeds up to 3 Mbaud. However, when I call tcsetattr() with speed set to anything other than one of the predefined constants (using cfsetspeed()), I get an "Invalid argument" error back from the call.

Googling, I found https://github.com/avrdudes/avrdude/issues/771

Seems like macOS really can't accept baud rates outside the predefined set without resorting to ioctl?

I have a vague recollection that expecting the Bnnnn baud rate constants to equal nnnnn can fail on other platforms too, maybe old linux versions or unusual architectures or old glibc versions or linux running a libc that isn’t glibc or something.

Use the ioctl - that’s what it’s for.

I have a vague recollection...

I've found the code in question, where I have this comment:

// The glibc docs used to say:
//   In the GNU library, the functions [cfsetispeed, cfsetospeed, etc] accept
//   speeds measured in bits per second as input, and return speed values
//   measured in bits per second. Other libraries require speeds to be indicated
//   by special codes.
// At some point, however, this note has been removed and currently the
// Bnnnn definitions are not the same as the numeric values; attempting to
// use a numeric baud rate will result in an error.  So Bnnnn symbols for
// baud rates must be used.

I added that in 2017.

The macOS man page says

The input and output baud rates are found in the termios structure. The unsigned integer speed_t is typedef'd in the include file ⟨termios.h⟩. The value of the integer corresponds directly to the baud rate being represented; however, the following symbolic values are defined

and goes on to list the defined speeds. It notably does not say only those speeds are allowed.

It seems to me there’s no good reason to restrict the setting to those values, only that it should accept at least those values.

If ioctl can set the speed, meaning the underlying drivers and hardware can handle it, surely tcsetattr shouldn’t make a point of failing.

Moreover, cfsetspeed does not return an error, only tcsetattr does.

I tried to find the implementation of tcsetattr in the XNU sources, but it's not there (only the headers). FWIW, I filed FB13989879.

There’s another reason why I think it's wrong for tcsetattr to not accept arbitrary speeds:

If you set the port speed with ioctl, then read the terminal settings, the struct will show the actual set speed, even if it's an "invalid" speed.

If you then modify some aspect of the termios struct (say, the timeout), and try to tcsetattr, it will fail, because it doesn't accept the speed.

Invalid argument error from tcsetattr when setting speed to anything other than predefined
 
 
Q