Porting plptools to Haiku

I’m in the process of trying to port plptools, a userspace open source implementation of Psion’s PLP (serial link) protocol, to Haiku. I’ve managed to get most of it working. However, I’m having an issue regarding RS232 DSR detection, an important part of the PLP protocol.

Currently, ncpd (the main PLP service) can’t detect when DSR is high. I think it might be struggling with DTR, too.

I’ve tried with two separate USB RS232 adapters - a FTDI FT232R and a Prolific PL2303TA. I also have a CH340 and a PL2303RA that I can test with.

How complete is the DSR/DTR implementation in Haiku? I’ve just been looking through the driver source (ports/usb_serial), and I can’t see much about DSR.

Also, the code in ncpd currently uses ioctl.h to talk to the serial ports. Should ioctl work, or do I need to look at moving to BSerialPort?

ioctl istelf should work, but the available commands are usually non-standard, and will be different from one OS to another.

usually, serial ports are controlled by termios (tcgetattr, tcsetattr, etc) rather than ioctl. That is more standardized.

I have not tested DSR/DTR recently and with manual polling.

BSerialPort is just a wrapper above termios and possibly ioctl, if you are porting existing code that uses termios and ioctl, it’s probably better to stay close to the original code and skip the wrapper. But you can look at BSerialPort code in Haiku sources to see what it does.

1 Like

I mention it just in case, but have you tested those usb adapters with other operating systems ? Many of them do not offer DSR/DTR, only basic serial without flow control.

Yes, they’ve all been tested on Linux (with plptools) and Windows (XP with PsiWin). I did some tests here: USB RS232 Shenanigans (Updated 2025-05-28) | Details | Hackaday.io

I’m happy to do the testing. Is there any Haiku software that I can use for this?

I think SerialConnect should support hardware flow control, but does not report the status of the control lines yet. I should probably add that to the status bar.

So, I’ve done a bit of testing to work out what’s going on with Haiku and RTS/CTS/DTR/DSR.

This bit of C code (based on this code) checks RTS/CTS/DTR/DSR every second, toggling RTS and DTR between checks.

On Linux using both the Prolific TA adapter and the FTDI FT232 adapter, I get the following output when plugged into the test Psion Series 3c which is setting DTR and CTS high:

DTR:1 DSR:1 RTS:1 CTS:1
DTR:0 DSR:1 RTS:0 CTS:1
DTR:1 DSR:1 RTS:1 CTS:1
DTR:0 DSR:1 RTS:0 CTS:1
DTR:1 DSR:1 RTS:1 CTS:1  --- UNPLUGGED THE 3C HERE
DTR:0 DSR:0 RTS:0 CTS:0
DTR:1 DSR:0 RTS:1 CTS:0
...

On Haiku, with both adapters:

DTR:0 DSR:0 RTS:0 CTS:1
DTR:0 DSR:0 RTS:0 CTS:1
DTR:0 DSR:0 RTS:0 CTS:1
DTR:0 DSR:0 RTS:0 CTS:1
DTR:0 DSR:0 RTS:0 CTS:1 --- UNPLUGGED THE 3C HERE
DTR:0 DSR:0 RTS:0 CTS:1
DTR:0 DSR:0 RTS:0 CTS:1
...

DTR, DSR, RTS and CTS are always read as off, off, off and on, respectively, no matter what is set at the other end.

I’ve also done some checks with an oscilloscope. When you first plug in an adapter, DTR is low. Although you can set DTR once, you can’t tell that it’s set. If you try to change it (set or unset), it goes low for a few milliseconds and then goes back high again. For CTS, it’s the opposite - if you try to change it, it goes high for a few milliseconds and then goes low again.

If I’m using ioctl() incorrectly, please do let me know!

2 Likes

Been digging into this again today.

For others reading this: As @PulkoMandy said, BSerialPort is just a wrapper for a subset of ioctl()'s features. ioctl() is actually in Haiku’s implementation of unistd.h. (sys/ioctl.h is just a wrapper for termios.h with 4 extra macros.) Jumping through a few wrapper functions, I found _kern_ioctl() in kernel/fs/fd.cpp.

Basically, I can’t get DSR/DTR/RTS/CTS to work, and I don’t think it’s me.

Is there anything I can to do test this further? Should I just log this as a bug?

EDIT: FYI, I’m testing on 64-bit R1B5.

What do you see if you run your program through strace?

I changed the loop to only run 10 times.

[   584] <... image_relocated resumed>  (388 us)
[   584] set_area_protection(0x6ef3, B_READ_AREA|B_EXECUTE_AREA) = 0x0 No error (89 us)
[   584] set_area_protection(0x6ef5, B_READ_AREA|B_EXECUTE_AREA) = 0x0 No error (75 us)
[   584] set_area_protection(0x6ef8, B_READ_AREA|B_EXECUTE_AREA) = 0x0 No error (63 us)
[   584] get_system_info(0x7f9d0a97ab80) = 0x0 No error (29 us)
[   584] get_system_info(0x7f9d0a97a980) = 0x0 No error (26 us)
[   584] reserve_address_range([0x100100000000], B_RANDOMIZED_BASE_ADDRESS, 0x1000000000) = 0x0 No error ([0x120f95aab000]) (56 us)
[   584] create_area("heap", [0x120f95aab000], B_EXACT_ADDRESS, 0x40000, 0x0, B_READ_AREA|B_WRITE_AREA) = 0x6efb ([0x120f95aab000]) (70 us)
[   584] resize_area(0x6efb, 0x50000) = 0x0 No error (30 us)
[   584] generic_syscall("random", 0x1, 0x7f9d0a97ab30, 0x10) = 0x0 No error (47 us)
[   584] resize_area(0x6efb, 0x60000) = 0x0 No error (27 us)
[   584] open(0xffffffff, "/dev/ports/usb0", O_RDWR, 0x0) = 0x3 (651 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae80, 0x0) = 0x0 No error (32 us)
[   584] read_stat(0x1, (nil), false, 0x7f9d0a978280, 0x80) = 0x0 No error (32 us)
[   584] ioctl(0x1, TCGETA, 0x7f9d0a978250, 0x20) = 0x0 No error (31 us)
DTR:0 DSR:0 RTS:0 CTS:1
[   584] write(0x1, 0xffffffffffffffff, 0x120f95b07730, 0x18) = 0x18 (34 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae5c, 0x0) = 0x0 No error (28 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae5c, 0x4) = 0x0 No error (845 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (28 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (925 us)
[   584] snooze_etc(0x204e730b, 0x0, 0x10, (nil)) = 0x0 No error (1000033 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae80, 0x0) = 0x0 No error (212 us)
DTR:0 DSR:0 RTS:0 CTS:1
[   584] write(0x1, 0xffffffffffffffff, 0x120f95b07730, 0x18) = 0x18 (220 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae5c, 0x0) = 0x0 No error (91 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae5c, 0x4) = 0x0 No error (887 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (45 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (893 us)
[   584] snooze_etc(0x205dc1af, 0x0, 0x10, (nil)) = 0x0 No error (1000047 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae80, 0x0) = 0x0 No error (217 us)
DTR:0 DSR:0 RTS:0 CTS:1
[   584] write(0x1, 0xffffffffffffffff, 0x120f95b07730, 0x18) = 0x18 (250 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae5c, 0x0) = 0x0 No error (104 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae5c, 0x4) = 0x0 No error (885 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (46 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (879 us)
[   584] snooze_etc(0x206d107d, 0x0, 0x10, (nil)) = 0x0 No error (1000054 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae80, 0x0) = 0x0 No error (208 us)
DTR:0 DSR:0 RTS:0 CTS:1
[   584] write(0x1, 0xffffffffffffffff, 0x120f95b07730, 0x18) = 0x18 (196 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae5c, 0x0) = 0x0 No error (61 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae5c, 0x4) = 0x0 No error (871 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (35 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (894 us)
[   584] snooze_etc(0x207c5ea9, 0x0, 0x10, (nil)) = 0x0 No error (1000037 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae80, 0x0) = 0x0 No error (225 us)
DTR:0 DSR:0 RTS:0 CTS:1
[   584] write(0x1, 0xffffffffffffffff, 0x120f95b07730, 0x18) = 0x18 (185 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae5c, 0x0) = 0x0 No error (134 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae5c, 0x4) = 0x0 No error (909 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (62 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (869 us)
[   584] snooze_etc(0x208badcf, 0x0, 0x10, (nil)) = 0x0 No error (1000047 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae80, 0x0) = 0x0 No error (232 us)
DTR:0 DSR:0 RTS:0 CTS:1
[   584] write(0x1, 0xffffffffffffffff, 0x120f95b07730, 0x18) = 0x18 (268 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae5c, 0x0) = 0x0 No error (85 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae5c, 0x4) = 0x0 No error (875 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (51 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (879 us)
[   584] snooze_etc(0x209afcb4, 0x0, 0x10, (nil)) = 0x0 No error (1000050 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae80, 0x0) = 0x0 No error (231 us)
DTR:0 DSR:0 RTS:0 CTS:1
[   584] write(0x1, 0xffffffffffffffff, 0x120f95b07730, 0x18) = 0x18 (187 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae5c, 0x0) = 0x0 No error (104 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae5c, 0x4) = 0x0 No error (883 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (51 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (884 us)
[   584] snooze_etc(0x20aa4ba3, 0x0, 0x10, (nil)) = 0x0 No error (1000059 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae80, 0x0) = 0x0 No error (234 us)
DTR:0 DSR:0 RTS:0 CTS:1
[   584] write(0x1, 0xffffffffffffffff, 0x120f95b07730, 0x18) = 0x18 (185 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae5c, 0x0) = 0x0 No error (64 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae5c, 0x4) = 0x0 No error (882 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (51 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (878 us)
[   584] snooze_etc(0x20b99a67, 0x0, 0x10, (nil)) = 0x0 No error (1000057 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae80, 0x0) = 0x0 No error (207 us)
DTR:0 DSR:0 RTS:0 CTS:1
[   584] write(0x1, 0xffffffffffffffff, 0x120f95b07730, 0x18) = 0x18 (232 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae5c, 0x0) = 0x0 No error (80 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae5c, 0x4) = 0x0 No error (876 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (40 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (888 us)
[   584] snooze_etc(0x20c8e91a, 0x0, 0x10, (nil)) = 0x0 No error (1000051 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae80, 0x0) = 0x0 No error (259 us)
DTR:0 DSR:0 RTS:0 CTS:1
[   584] write(0x1, 0xffffffffffffffff, 0x120f95b07730, 0x18) = 0x18 (252 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae5c, 0x0) = 0x0 No error (147 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae5c, 0x4) = 0x0 No error (916 us)
[   584] ioctl(0x3, TIOCMGET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (66 us)
[   584] ioctl(0x3, TIOCMSET, 0x7f9d0a97ae6c, 0x0) = 0x0 No error (893 us)
[   584] snooze_etc(0x20d8395b, 0x0, 0x10, (nil)) = 0x0 No error (1000056 us)
[   584] exit_team(0x0) (212 us)
strace: Failed to run thread 584: Bad port ID

The problem is more likely to be in the usb_serial driver not implementing this correctly, I think the userspace side is all ready for it? (There isn’t much to the userspace side of things for this, just send the ioctl with the right parameters).

Yeah I was looking at the driver and thought it would be useful to have a log of which ioctl calls were actually being made and in what order, since they affect the flags differently.

Would also be worth checking nothing else has the port open (lsof /dev/ports/usb0)

Just checked on a 32-bit Haiku box with a physical serial port. Long story short, DSR and CTS are being detected correctly, and I believe that DTR and RTS are being set correctly. (I haven’t got the oscilloscope out. However, when DTR is set high, the Psion turns on from standby.) So yes, it does look like this is an issue with usb_serial.

However, detection of the current state of DTR and RTS doesn’t work with TIOCMGET - it always returns false. So this could be a general issue with RS232 on Haiku.

Just to be clear, returning the current state of DTR and RTS isn’t a deal breaker for plptools - AFAICT plptools doesn’t need it to work. However, it does work on Linux, FreeBSD on NetBSD, so might be helpful for general POSIX compatibility.

Currently, Haiku’s usb_serial driver does nothing, on USB interrupt endpoint notification, so it doesn’t specifically handle the Interrupt SERIAL_STATE notification in which DCE control state signals change are reported according USB CDC specification.

And it doesn’t either implement much also on setting the control signals on DTE side, except for the FTDI kind of USB serial adapter.

So it’s safe to assume that, indeed, using usb serial ports under Haiku, you currently won’t have any control signals support.

Most of usage, since long, of a serial port by softwares don’t rely that much on control signals but on some data protocol above. For instance, instead of relying on DTE ring and carrier signals, most of softwares will assume that a modem with AT commands will be running on the DCE, and will send AT commands notifications on such events.

But, sure, a complete support will bring, well, a complete support.
It’s just situation where it’s actually needed to have control signals state available is very limited.
As you said, plptools doesn’t need it to work, as it’s often the case for so many cases.

Hence why nobody feel the need to add this missing support in usb_serial.

Plptools doesn’t need to know the current state of DTR and RTS (which is a bug in the main RS232 implementation, not usb_serial), but it does need to be able to set them reliably. It also needs to be able to read DSR and CTS. DTE is also used, although I don’t think it’s as essential.

For context, this is software that talks to pocket computers from the 90s. I know these control signals aren’t widely used now, but they are still used.

If you’re really interested, a breakdown of the protocol is here: Psion Link Protocol

So, what should I do now? I don’t think I have the skills to add these features to the driver.

I was not saying it’s not a problem, I was just giving my feeling on why this feature is not implemented yet.

Please search if it’s a known bug, if it’s not opening a bug ticket will be the next step.