kernel 6.8 and checking serial ports - failure to match driver name lazarus / 32-Linux ---------------------------------------------------------------------- robert rozee · 2024-11-24 15:00 hi,     i'm having troubles with some code i've been using for quite some time to validate serial ports, so that a user can be presented with a list if 'live' ports. jumping right into it, here is the main chunk of code i'm using, with a few writeln statements added in for debugging: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i // method based on information obtained from the following two sites:// https://www.lazarusforum.de/viewtopic.php?p=72837// https://stackoverflow.com/questions/2530096function CheckDevice(DeviceName:string):boolean;               // checks to see if the device named is a livevar DriverName:string;                                       // ... serial port. this is done by checking          FD:longint;                                        // ... entries in /sys/class/tty including the            SS:TSerialStruct;                                // ... presence of a link to a device driver.            ST:stat;             S:string;begin  Result:=false;   ST.st_mode:=0;  if (DeviceName<>'.') and (DeviceName<>'..') then  if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or            // this suffices with FPC prior to 3.20     DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then     // from FPC 3.20 onwards we need this instead  if fpLstat('/sys/class/tty/'+DeviceName+'/device', ST)=0 then  if fpS_ISLNK(ST.st_mode) then  begin    S:=fpReadLink('/sys/class/tty/'+DeviceName+'/device/driver');    DriverName:=ExtractFileName(S);     writeln('/sys/class/tty/'+DeviceName+'/device/driver');    writeln('fpReadLink returns ',S);    writeln('driver name is "',DriverName, '"');     if (DriverName<>'serial8250')//   and (DriverName<>'port')                                then Result:=true                                else begin                                     writeln('checking serial8250 device');                                     FD:=fpOpen('/dev/'+DeviceName, O_RDWR or O_NONBLOCK or O_NOCTTY);                                       if FD>0 then                                     try                                       if fpIOCtl(FD, TIOCGSERIAL, @SS)<>-1 then                                       if SS.typ<>0 then Result:=true;                                       fpclose(FD)                                       except end                                     end  end;  if Result then writeln('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');  writelnend; using a pre-6.8 kernel, passing in, for instance, 'ttyS2', will return a false result as ttyS2 does not exist as a functional serial port on my computer. whereas, ttyS0, tyS4 and ttyS5 are all valid serial ports and returned true with a pre-6.8 kernel. S4 and S5 are even genuine 16x50 silicon sitting on an PCI card. but when using a 6.8 kernel, all ttySn ports return a true result      as far as i have been able to determine, this is the result of either: a far more convoluted symlink arrangement within the /sys/ directory tree, or, possibly, fpReadLink not correctly handling paths that have a symlink in the middle. to be honest, i've just spent an evening trying to follow through the various symlinks and just ended up more confused than when i started! here is some sample output from the above function, run over the serial ports ttyS0..5, and ttyUSB*: Code: [Select]/sys/class/tty/ttyUSB1/device/driver fpReadLink returns ../../../../../../../../bus/usb- serial/drivers/cp210x driver name is "cp210x" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyS4/device/driver fpReadLink returns ../../../../../../../bus/serial-base/drivers/port driver name is "port" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyS2/device/driver fpReadLink returns ../../../../../bus/serial-base/drivers/port driver name is "port" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyS0/device/driver fpReadLink returns ../../../../../bus/serial-base/drivers/port driver name is "port" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyS5/device/driver fpReadLink returns ../../../../../../../bus/serial-base/drivers/port driver name is "port" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyUSB0/device/driver fpReadLink returns ../../../../../../../../bus/usb- serial/drivers/cp210x driver name is "cp210x" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyS3/device/driver fpReadLink returns ../../../../../bus/serial-base/drivers/port driver name is "port" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyS1/device/driver fpReadLink returns ../../../../../bus/serial-base/drivers/port driver name is "port" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> and here it is with the line   " and (DriverName<>'port') "   no longer commented out: Code: [Select]/sys/class/tty/ttyUSB1/device/driver fpReadLink returns ../../../../../../../../bus/usb- serial/drivers/cp210x driver name is "cp210x" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyS4/device/driver fpReadLink returns ../../../../../../../bus/serial-base/drivers/port driver name is "port" checking serial8250 device >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyS2/device/driver fpReadLink returns ../../../../../bus/serial-base/drivers/port driver name is "port" checking serial8250 device /sys/class/tty/ttyS0/device/driver fpReadLink returns ../../../../../bus/serial-base/drivers/port driver name is "port" checking serial8250 device >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyS5/device/driver fpReadLink returns ../../../../../../../bus/serial-base/drivers/port driver name is "port" checking serial8250 device >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyUSB0/device/driver fpReadLink returns ../../../../../../../../bus/usb- serial/drivers/cp210x driver name is "cp210x" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PASS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /sys/class/tty/ttyS3/device/driver fpReadLink returns ../../../../../bus/serial-base/drivers/port driver name is "port" checking serial8250 device /sys/class/tty/ttyS1/device/driver fpReadLink returns ../../../../../bus/serial-base/drivers/port driver name is "port" checking serial8250 device the added condition   " and (DriverName<>'port') "   is a bit of a hack based upon observed output from the debug statements, forcing the code through the path that tries to open the port. while this works in my case, i'm quite sure it is wrong! has anyone else encountered problems in checking serial ports when using a 6.8 kernel? any suggestions on a good way forward? i am pretty sure the above code, that i've been using for years, has been used by a number of other folks in the past. cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-11-24 15:39 I'm only on 6.1 (from Debian "stable") I'm afraid, and I've never had to search for ttyS ports: only the variants of USB devices. There /might/ /possibly/ be something in https://github.com/MarkMLl/serialcomms that helps. However the major "take-home" from doing that stuff was that the positioning of stuff in the /sys tree was very much at the whim of the kernel module writer, so it's entirely plausible that somebody has broken something by trying to "clean it up and adopt a standard layout". MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-11-25 09:45 Quote from: MarkMLl on November 24, 2024, 04:39:06 pmit's entirely plausible that somebody has broken something by trying to "clean it up and adopt a standard layout" that is the feeling i get. i think it can be summed up as: pre-6.8 kernel: /sys/class/tty/ttyS2/device/driver resolves to /sys/bus/platform/drivers/serial8250 6.8 kernel: /sys/class/tty/ttyS2/device/driver resolves to /sys/bus/serial- base/drivers/port this change breaks the check for the path ending in "serial8250". any idea where i can ask about this? i've just had a look for an online linux kernel mailing list, and there seem to be a number of them - which one is the 'official' one? quite separately to this, i am concerned about fpReadLink returning a string containing way too many back-steps ("/../"): Code: [Select]/sys/class/tty/ttyS5/device/driver fpReadLink returns ../../../../../../../bus/serial-base/drivers/portin the above example this is seven steps back up a directory tree that is nowhere near that deep. my guess is that this equates to a path that is: /sys/class/tty/ttyS5/device/driver/../../../../../../../bus/serial- base/drivers/port whereas, as far as i can figure, there should be only five back-steps. cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-11-25 10:08 Quote from: robert rozee on November 25, 2024, 10:45:33 amQuote from: MarkMLl on November 24, 2024, 04:39:06 pmit's entirely plausible that somebody has broken something by trying to "clean it up and adopt a standard layout" that is the feeling i get. i think it can be summed up as: pre-6.8 kernel: /sys/class/tty/ttyS2/device/driver resolves to /sys/bus/platform/driver2/serial8250 6.8 kernel: /sys/class/tty/ttyS2/device/driver resolves to /sys/bus/serial- base/drivers/port this change breaks the check for the path ending in "serial8250". I've just been sniffing around (because of the number of USB-connected devices I've got) and I think I see what's happening at least on older versions: ports which don't quite exist get allocated the serial8250 driver, while ones which really exist are allocated a more recent one. Not sure that helps you... Quoteany idea where i can ask about this? i've just had a look for a linux kernel mailing list, and there seem to be a number of them - which one is the 'official' one? I think the thing to do is find the Git commit that changed it, note who's responsible, and find where he's discussed it in the past... probably somewhere in https://lkml.org/ But if that's been committed it's unlikely that they'll backtrack, since that would leave one or a small number of kernel versions behaving anomalously rather than there being a recognisable change at one specific point. ...And the /sys tree is an internal interface. Quotequite separately to this, i am concerned about fpReadLink returning a string containing way too many back-steps ("/../"): Quote[/sys/class/tty/ttyS5/device/driver fpReadLink returns ../../../../../../../bus/serial- base/drivers/port/quote] in the above example this is seven steps back up a directory tree that is nowhere near that deep. my guess is that this equates to a path that is: /sys/class/tty/ttyS5/device/driver/../../../../../../../bus/serial- base/drivers/port whereas, as far as i can figure, there should be only five back-steps. But what you're seeing there is literally what the driver is telling you to look at, when you ask it for information about itself (remember that these are kernel abstractions, not "real" files and directories). There is almost certainly something in the RTL which would canonicalise that path, but while it would certainly be useful to be able to see it it might not be strictly correct to rely on it: and a defensive kernel hacker might be dismissive if you take that sort of liberty :-/ MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-11-25 12:24 Quote from: MarkMLl on November 25, 2024, 11:08:46 amI've just been sniffing around (because of the number of USB-connected devices I've got) and I think I see what's happening at least on older versions: ports which don't quite exist get allocated the serial8250 driver, while ones which really exist are allocated a more recent one with a pre-6.8 kernel, all the /dev/ttySxx ports seem to be associated with the "serial8250" driver, and my code has to iterate over all of them trying to open each /dev/ttySxx to verify if a given port's hardware exists or not. this check is handled with the fragment of code: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Result:=false;[...]FD:=fpOpen('/dev/'+DeviceName, O_RDWR or O_NONBLOCK or O_NOCTTY);if FD>0 thentry  if fpIOCtl(FD, TIOCGSERIAL, @SS)<>-1 then             // note: TIOCGSERIAL is not supported by all USB serial ports, so should only be used on fixed (8250 et al) ports  if SS.typ<>0 then Result:=true;  fpclose(FD)except end if a port is not associated with the "serial8250" driver, then it is assumed that the hardware is present. ie, removable (USB) serial ports do not use the "serial8250" driver; fixed serial ports always use the serial8250" driver. note, i didn't write this code, but merely translated it from what others have variously written, including some C versions. my assumption is that not trying to open removable/USB serial ports is to save time (i recall reading somewhere that some bluetooth devices may take a long time to respond). in the past i've found that this code works pretty reliably, but of course that is no guarantee the same approach will continue to work in future! with the 6.8 kernel, as far as i can tell all /dev/ttySxx port entries (existent or not) seem to be associated with a driver called "port", /sys/bus/serial-base/drivers/port. playing around a bit more with following along the symlinks, i'm now feeling a little more comfortable and can see (roughly) how all the extra back-steps are consumed: user@user-DH61BE:~$ user@user-DH61BE:/$ cd / user@user-DH61BE:/$ cd -P sys user@user-DH61BE:/sys$ cd -P class user@user-DH61BE:/sys/class$ cd -P tty user@user-DH61BE:/sys/class/tty$ cd -P ttyS5 user@user-DH61BE:/sys/devices/pci0000:00/0000:00:1c.0/0000:01:00.0/000 0:02:00.0/0000:02:00.0:0/0000:02:00.0:0.1/tty/ttyS5$ cd -P device user@user-DH61BE:/sys/devices/pci0000:00/0000:00:1c.0/0000:01:00.0/000 0:02:00.0/0000:02:00.0:0/0000:02:00.0:0.1$ cd -P ../../../../../../../bus/serial-base/drivers user@user-DH61BE:/sys/bus/serial-base/drivers$ user@user-DH61BE:/sys/bus/serial-base/drivers$ so it looks like fpReadLink must follow down the directory tree one level at a time, stopping before the last step into driver (which is a final symlink to a file). this gives it a count of how many back-steps are then needed: /sys/devices/pci0000:00/0000:00:1c.0/0000:01:00.0/0000:02:00.0/0000:02 :00.0:0/0000:02:00.0:0.1 so 7 back-steps, highlighted in red (4) and green (3). it would be a whole lot easier of fpReadLink were to return an absolute path, although in the present case we are just interested in the name at the end of the path. Quote from: MarkMLl on November 25, 2024, 11:08:46 ama defensive kernel hacker might be dismissive if you take that sort of liberty :-/ a good point! i just need to figure out if the path ... serial- base/drivers/port can be relied upon in the same way that the name serial8250 could previously be; just relying upon port as the identifier feels a tad dangerous! cheers, rob   :-) ---------------------------------------------------------------------- robert rozee · 2024-11-25 12:53 a few more data points, sorted, with 6.8 kernel: ttyS0    : port     : /sys/class/tty/ttyS0/device/driver/../../../../../bus/serial- base/drivers/port ttyS1    : port     : /sys/class/tty/ttyS1/device/driver/../../../../../bus/serial- base/drivers/port ttyS2    : port     : /sys/class/tty/ttyS2/device/driver/../../../../../bus/serial- base/drivers/port ttyS3    : port     : /sys/class/tty/ttyS3/device/driver/../../../../../bus/serial- base/drivers/port ttyS4    : port     : /sys/class/tty/ttyS4/device/driver/../../../../../../../bus/serial- base/drivers/port ttyS5    : port     : /sys/class/tty/ttyS5/device/driver/../../../../../../../bus/serial- base/drivers/port ttyS6    : port     : /sys/class/tty/ttyS6/device/driver/../../../../../bus/serial- base/drivers/port ttyS7    : port     : /sys/class/tty/ttyS7/device/driver/../../../../../bus/serial- base/drivers/port ttyS8    : port     : /sys/class/tty/ttyS8/device/driver/../../../../../bus/serial- base/drivers/port ttyS9    : port     : /sys/class/tty/ttyS9/device/driver/../../../../../bus/serial- base/drivers/port ttyACM0  : cdc_acm  : /sys/class/tty/ttyACM0/device/driver/../../../.. /../../../bus/usb/drivers/cdc_acm ttyUSB0  : cp210x   : /sys/class/tty/ttyUSB0/device/driver/../../../../../../../../bus/usb- serial/drivers/cp210x ttyUSB1  : cp210x   : /sys/class/tty/ttyUSB1/device/driver/../../../../../../../../bus/usb- serial/drivers/cp210x those ports in red are real ports that exist, the others are not real. i've clipped out the higher numbered ttySxx ports. note that there is a pattern amongst the real ports that is broken by ttyS0   :-( cheers, rob   :-) ---------------------------------------------------------------------- Thaddy · 2024-11-25 13:07 Looks more like something incompatible in the kernel, because what fpc does is perform straight system calls. I bet you can also reproduce it in other languages. ---------------------------------------------------------------------- robert rozee · 2024-11-25 13:34 Quote from: Thaddy on November 25, 2024, 02:07:00 pmLooks more like something incompatible in the kernel, because what fpc does is perform straight system calls. I bet you can also reproduce it in other languages. yes, i am sure i most certainly can. i'm just hoping that someone else on here has (or is) looking into ways around the problem! do you know of where/how to get hold of and communicate with the linux kernel developers? the code i've been using for many years has, up until now, provided a good solution to allow users to select serial ports from a short list of 'real' ports (ones that are connected to real hardware). but not now   :-( cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-11-25 14:06 Quote from: robert rozee on November 25, 2024, 01:24:27 pmif a port is not associated with the "serial8250" driver, then it is assumed that the hardware is present. ie, removable (USB) serial ports do not use the "serial8250" driver; fixed serial ports always use the serial8250" driver. No, that's definitely not a safe assumption. It will only use the serial8250 driver if PCI etc. enumeration can't explicitly identify the device as something more recent. I'm not going clambering over things, but I /think/ the machine I'm working on has two serial ports: it certainly has one because there's a classic serial SpaceMouse plugged into it (lsof confirms this is ttyS0). dmesg tells me Code: [Select][    0.562187] Serial: 8250/16550 driver, 4 ports, IRQ sharing enabled [    0.562347] 00:07: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A [    0.562974] 0000:00:16.3: ttyS1 at I/O 0xf0e0 (irq = 19, base_baud = 115200) is a 16550A Hence the 8250 driver has been loaded (which will almost always be the case for a PC), it has initially claimed all four potential ports (at the PC legacy addresses), it has recognised the legacy ttyS0 as a 16550A and released it, and it has enumerated ttyS1 on the PCI bus as a 16550A and released it. Subsequent to that PCI enumeration has caused the serial module to be loaded, which has claimed ttyS0 and ttyS1 while leaving ttyS2 and ttyS3 to serial8250. But if for some reason serial8250 couldn't decide what ttyS0 or ttyS1 was, it wouldn't release it. Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i /sys# find devices -name 'ttyS*' devices/pnp0/00:07/tty/ttyS0devices/pci0000:00/0 000:00:16.3/tty/ttyS1 # Reordered here for claritydevices/platform/ser ial8250/tty/ttyS2devices/platform/serial8250/tty/ttyS3 /sys# ls -l devices/pci0000:00/0000:00:16.3/tty/ttyS1/device lrwxrwxrwx 1 root root 0 Nov 23 15:19 devices/pci0000:00/0000:00:16.3/tty/ttyS1/device -> ../../../0000:00:16.3 /sys# ls -l devices/pci0000:00/0000:00:16.3/tty/ttyS1/device/driver lrwxrwxrwx 1 root root 0 Nov 23 15:19 devices/pci0000:00/0000:00:16.3/tty/ttyS1/device/driver -> ../../../bus/pci/drivers/serial /sys# ls -l devices/platform/serial8250/tty/ttyS2/device lrwxrwxrwx 1 root root 0 Nov 25 09:30 devices/platform/serial8250/tty/ttyS2/device -> ../../../serial8250 /sys# ls -l devices/platform/serial8250/tty/ttyS2/device/driver lrwxrwxrwx 1 root root 0 Nov 23 15:19 devices/platform/serial8250/tty/ttyS2/device/driver -> ../../../bus/platform/drivers/serial8250 /sys# ls -l devices/platform/serial8250/tty/ttyS3/device lrwxrwxrwx 1 root root 0 Nov 25 09:30 devices/platform/serial8250/tty/ttyS3/device -> ../../../serial8250 /sys# ls -l devices/platform/serial8250/tty/ttyS3/device/driver lrwxrwxrwx 1 root root 0 Nov 23 15:19 devices/platform/serial8250/tty/ttyS3/device/driver -> ../../../bus/platform/drivers/serial8250  So on this specific system, it's unambiguous that ttyS0 and ttyS1 are using the serial driver, while the non-present ttyS2 and ttyS3 are defaulting to serial8250. That's a moderately-recent Dell. I'd expect the lower-spec Dell underneath my desk to enumerate differently. But again I'd emphasise: the paths that you get from consulting the content of /sys are entirely at the whim of the kernel module authors. MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-11-25 14:55 well, dmesg indeed produces the following interesting results here: user@user-DH61BE:~$ user@user-DH61BE:~$ dmesg | grep "tty" [    0.000000] printk: legacy console [tty0] enabled [    0.252542] 00:05: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A [    0.276564] 0000:02:00.0: ttyS4 at I/O 0xe000 (irq = 16, base_baud = 921600) is a 16550A [    0.298087] 0000:02:00.0: ttyS5 at I/O 0xe008 (irq = 16, base_baud = 921600) is a 16550A [    2.885920] usb 1-1.1: cp210x converter now attached to ttyUSB0 [99704.693883] usb 3-3: cp210x converter now attached to ttyUSB1 [178459.275692] cdc_acm 1-1.5:1.0: ttyACM0: USB ACM device user@user-DH61BE:~$ ignoring tty0, the others cover all the real serial ports on the machine i'm sitting in front of, with no extras. would you advise simply using the output of dmesg to build a list of serial ports? this would, of course, also require either: (a) tracking unplug events reported in the output from dmesg as well, or (b) [perhaps easier] validating the list of ports collected against /dev/* and removing from our list any that are not present in /dev (this would also take care of tty0 as there is, on my desktop machine at least, no entry in /dev for tty0. on another machine running a much older kernel there is an entry for tty0). i would be very happy if a solution using the output of dmesg was workable! cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-11-25 15:12 Quote from: robert rozee on November 25, 2024, 03:55:10 pmi would be very happy if a solution using the output of dmesg was workable! No, you can't trust it because it is once more at the mercy of the module developers: who can change the detail they're including with even more impunity than they can change /sys content. The /only/ safe thing is to walk the /sys tree and look for patterns, which is what I'm doing in the Github project I pointed you at. Looking at /dev/ttyS[0123], you can't even rely on the device numbers to tell you anything useful. I was going to append to my earlier posting but thought I'd give you another interesting case to think about. You can use an RPi Pico (RP2040) to interpret a PC's LPC bus protocol (plugged into the TPM connector). It would be "relatively easy" to program one of those to look like the missing ttyS2 and ttyS3 ports, and if they didn't enumerate as 16550 chips they'd be left on the serial8250 module's list. You could detect their actual presence by using the 8250's loopback, but I don't think you /could/ enumerate them since LPC's not PCI so unless good old 1990s PnP worked... MarkMLl ---------------------------------------------------------------------- MarkMLl · 2024-11-27 10:18 I'm taking the opportunity to tidy up some stuff in my own port detection code, which leads me to a couple of observations. The first is that I think it's necessary to find out what bus the device is enumerated on: in the case of these non-PCI devices that's going to be /sys/devices/pnp* vs /sys/devices/pci*. It appears that at least some BIOSes either have the ports for 8250s hardcoded or there's an (EE)PROM record for them associated with (the successor to) the SuperIO device, whether or not they exist. If a device enumerates as a 16550 on the PCI bus, then it's reasonable to assume that it exists, although as you've discovered the path to check the driver name might vary. If a device appears as an 8250 on the PnP bus, then it might or might not exist. In that case use TIOCM_LOOP to do an internal chip wraparound, send a character and see if it appears on the input. I've not checked that latter yet, but as far as I can see the RTL and kernel support it so it /should/ work (i.e. in the same way that setting other MCR bits like DTR etc. work). MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-11-27 11:22 Quote from: MarkMLl on November 27, 2024, 11:18:20 amIn that case use TIOCM_LOOP to do an internal chip wraparound, send a character and see if it appears on the input what happens if you use TIOCM_LOOP on a port that has already been opened and locked exclusively by another process? in my own code i commonly do: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i try fpIOCtl(SerialHandle, TIOCEXCL, nil) except end;right after i open any serial port i'll be using, so as to avoid the embarrassment and potential mayhem of having two programs accessing it at the same time. would this work as an alternative to TIOCM_LOOP, as it is just reading status information? Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Result:=falseif fpIOCtl(FD, TIOCGSERIAL, @SS)<>-1 then if SS.typ<>0 then Result:=true; Quote from: MarkMLl on November 25, 2024, 04:12:34 pmQuote from: robert rozee on November 25, 2024, 03:55:10 pmi would be very happy if a solution using the output of dmesg was workable!No, you can't trust it because it is once more at the mercy of the module developers: who can change the detail they're including with even more impunity than they can change /sys content. The only safe thing is to walk the /sys tree and look for patterns years ago i worked with a rather clever engineer who had a note pinned up in his cubicle that read: For every complex problem there is a simple solution that does not work. but for my own application case, i wonder if dmesg may provide a relatively simple solution that, while far from perfect, is still good enough. i shall carry on digging, including through your code at https://github.com/MarkMLl/serialcomms cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-11-27 11:49 If it's already opened exclusively then you shouldn't (be able to) touch it. But if it's opened then it's reasonable to assume that it's a real device... I'm pretty sure that everything that's needed is accessible in the /sys tree, except possibly distinguishing between a non-existing PnP 8250 and a real one. There's also the obvious issue of hotplugging to be considered. I'm currently working through that project and disabling some tests- for example for idVendor/idProduct- if the bus is PnP or PCI. It /might/ /possibly/ be sufficient to note that a device is in the /sys/devices/platform subtree to decide that it's a dummy, so the combination of finding the bus type plus noting the kernel version might be sufficient to tell you where to look for the driver which in turn should allow you to distinguish between an 8250 and a 16550. And in practice you can obviously do that using code which is much more straightforward than I'm using: I'm not saying it's NBG, but it's probably excessively complex once you know what you're looking for. updated: The order that I'm checking the fields in the description is obviously wrong: since one appears to need to know the bus type in order to find the driver that must be done early, and must be saved as internal state. The reason for that order is that in most cases I was looking at USB or Bluetooth (etc.) devices where you were more likely to know the vendor/product than anything else, so putting them at the start of the record to allow things like the driver and serial number to be omitted makes sense. MarkMLl ---------------------------------------------------------------------- Dzandaa · 2024-11-27 14:39 Maybe irrelevant, but here's the function I use to find the USB ports (in my case AVR Arduino) in Linux: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$IFDEF UNIX} // **********************************// ***** Get Serial Ports Linux *****// **********************************function GetSerialPortNames: string;var SearchResult : TSearchRec; v: TStringList;begin  v := TStringList.Create;  {$IFDEF LINUX}   If (FindFirst('/dev/ttyUSB*', (faAnyFile And Not faDirectory), SearchResult) = 0) or      (FindFirst('/dev/ttyACM*', (faAnyFile And Not faDirectory), SearchResult) = 0) Then  {$ELSE} // For MacOS   If (FindFirst('/dev/tty.usbmodem*', (faAnyFile And Not faDirectory), SearchResult) = 0) or     (FindFirst('/dev/tty.usbserial*', (faAnyFile And Not faDirectory), SearchResult) = 0) Then  {$ENDIF}  begin   v.Add('/dev/'+SearchResult.Name);   while FindNext (SearchResult) = 0 Do   begin    v.Add('/dev/'+SearchResult.Name);   end;  end;  Result := v.CommaText;  FindClose (SearchResult);  v.Free;end;{$ENDIF} B-> ---------------------------------------------------------------------- robert rozee · 2024-11-27 14:43 well, below is my attempt at using just the output of dmesg, validated against /dev/tty* to eliminate USB devices that may have been unplugged. it actually seems to work reasonably well. note that i generally also provide the user with an "edit" tick box that allows them to enter any port name that is not in the list. cheers, rob   :-) addendum: fixed up some small defects in the below code that falsely triggered on words like "getty" Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program project1;                                                              // Robert Rozee, 2024 uses SysUtils, Process, Classes; var PortList:TStringList;     I, J, K:integer;        S, T:string;        flag:boolean; const letters=['a'..'z', 'A'..'Z'];      numbers=['0'..'9'];begin  PortList:=TStringList.Create;                                              // create object to hold list of port names  PortList.Sorted:=true;                                                     // list always remains sorted  PortList.Duplicates:=dupIgnore;                                            // .Duplicates requires Sorted:=true //if RunCommand('/bin/bash',['-c','dmesg | grep "tty"'], S) then             // the 'strictly correct' version  if RunCommand('bash -c dmesg | grep "tty"', S) then                          // shorter version seems to work fine!  begin    I:=pos('tty', S);                                                        // look for first occurrence of "tty"    while I<>0 do    begin      if (I>1) and (S[I-1] in letters) then delete(S, 1, I+2) else             // eg, matches "getty" -> discard up to "y"      begin        delete(S, 1, I-1);                                                   // delete everything preceding the port name         I:=1;        while S[I] in letters do inc(I);                                     // skip over letters, such as "ttyUSB"      while S[I] in numbers do inc(I);                                     // ... now skip over any trailing numbers         PortList.Add(copy(S, 1, I-1));                                       // add the found port name to the list of ports        delete(S, 1, I-1)                                                      // remove found port name from input string      end;      I:=pos('tty', S)                                                       // look for next occurrence of "tty"    end  end;   if PortList.Find('tty', I) then PortList.Delete(I);                          // .Find also requires Sorted:=true  if PortList.Find('tty0', I) then PortList.Delete(I);                       // "tty" and "tty0" are not useful to us   PortList.Sorted:=false;                                                    // we don't need this setting for any of the below code    for I:=PortList.Count-1 downto 0 do                                        // remove any tty devices that are not present in /dev/tty*    if not FileExists('/dev/'+PortList.Strings[I]) then PortList.Delete(I);  // these are (generally) going to be removable USB devices                                                                             // that have been unplugged   flag:=true;  while (PortList.Count>1) and flag do                                         // excessively complicated sort routine, tries to ensure the  begin                                                                      // 'fixed' serial ports appear first, and that port numbers    flag:=false;                                                             // are ordered correctly: 0,1,2...,8,9,10,11, etc.    for I:=0 to PortList.Count-2 do    begin      S:=PortList[I];      T:=PortList[I+1];      J:=1+length(S);      K:=1+length(T);       if (J-K)<0 then begin                                                  // pack S with zeros to left of numeric part                        while (J>1) and (S[J-1] in ['0'..'9']) do dec(J);                        while length(S)1) and (T[K-1] in ['0'..'9']) do dec(K);                      while length(T)1) and (K=1)) or                                               // bubble ttyS* ports to top of the list         ((J=K) and (S>T)) then                                              // within respective groups sort alphabetically      begin        PortList.Exchange(I, I+1);        flag:=true                                                           // flag set if at least one swap during this pass      end    end  end;    if PortList.Count=0 then writeln('no serial ports found')                    else for I:=0 to PortList.Count-1 do writeln(PortList.Strings[I]);  PortList.Freeend.  sample output: user@user-DH61BE:~/pascal/Serial Watcher mk2$ user@user-DH61BE:~/pascal/Serial Watcher mk2$ ./project1 ttyS0 ttyS4 ttyS5 ttyACM0 ttyUSB0 ttyUSB1 user@user-DH61BE:~/pascal/Serial Watcher mk2$ ---------------------------------------------------------------------- robert rozee · 2024-11-27 14:53 Quote from: Dzandaa on November 27, 2024, 03:39:40 pmMaybe irrelevant, but here's the function I use to find the USB ports (in my case AVR Arduino) in Linux: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i    If (FindFirst('/dev/ttyUSB*', (faAnyFile And Not faDirectory), SearchResult) = 0) or      (FindFirst('/dev/ttyACM*', (faAnyFile And Not faDirectory), SearchResult) = 0) Then hi Dzandaa,     your approach will likely be perfectly adequate for the vast majority of folks, as these days the number of machines with fixed ttySxx ports is likely to be rapidly shrinking - and pretty much everything else 'appears' when plugged into a USB port and 'disappears' when unplugged. btw, your code should probably have two separate loops, one for /dev/ttyUSB* and another for /dev/ttyACM* as otherwise it will only look for ports of one or the other variety, but not both. cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-11-27 14:55 Quote from: Dzandaa on November 27, 2024, 03:39:40 pmMaybe irrelevant, but here's the function I use to find the USB ports (in my case AVR Arduino) in Linux: The difference is that what we're doing is trying to get the kernel driver module so that we know what each port actually /is/. The ttyACM devices are particularly ratty, since than name gets assigned to /anything/ that claims to implement the USB serial class. The key appears to be to walk the /sys/devices tree to the extent that you can find what bus something's on, then using that knowledge read a symlink at a relative location determined by the bus type. Then do a substring match (not a full match) to see if the driver is pnp/drivers/serial (for legacy devices), pci/drivers/serial and so on. There's lots of devils in the details, but that's the gist. MarkMLl ---------------------------------------------------------------------- Dzandaa · 2024-11-27 15:05 @MarkMLI O.K, I understand the problem. B-> ---------------------------------------------------------------------- MarkMLl · 2024-11-27 15:41 Quote from: robert rozee on November 27, 2024, 03:43:28 pmwell, below is my attempt at using just the output of dmesg, validated against /dev/tty* to eliminate USB devices that may have been unplugged. it actually seems to work reasonably well. note that i generally also provide the user with an "edit" tick box that allows them to enter any port name that is not in the list. It's obviously your choice what you use. But apart from anything else you need to appreciate that some distreaux make dmesg a root-only operation, and the kernel buffer is of limited size which will wrap once full. You might possibly do better looking at /var/log/syslog*, which will at least be visible if you add the relevant user(s) to the adm group. MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-11-27 20:31 Quote from: MarkMLl on November 27, 2024, 04:41:27 pmsome distreaux make dmesg a root-only operation, and the kernel buffer is of limited size which will wrap once full well, that relegates dmesg to the category of a "simple solution that does not work"! i found reference online to a useful location, /proc/tty/driver/serial, but it is only readable by root, see below. at least it shows the information exists. note: it is mostly the ttySxx ports that are causing me problems. cheers, rob   :-) Code: [Select]user@user-DH61BE:~$ user@user-DH61BE:~$ sudo cat /proc/tty/driver/serial serinfo:1.0 driver revision: 0: uart:16550A port:000003F8 irq:4 tx:0 rx:0 RTS|DTR 1: uart:unknown port:000002F8 irq:3 2: uart:unknown port:000003E8 irq:4 3: uart:unknown port:000002E8 irq:3 4: uart:16550A port:0000E000 irq:16 tx:0 rx:0 RTS|DTR 5: uart:16550A port:0000E008 irq:16 tx:0 rx:0 RTS|DTR 6: uart:unknown port:00000000 irq:0 7: uart:unknown port:00000000 irq:0 8: uart:unknown port:00000000 irq:0 9: uart:unknown port:00000000 irq:0 10: uart:unknown port:00000000 irq:0 11: uart:unknown port:00000000 irq:0 12: uart:unknown port:00000000 irq:0 13: uart:unknown port:00000000 irq:0 14: uart:unknown port:00000000 irq:0 15: uart:unknown port:00000000 irq:0 16: uart:unknown port:00000000 irq:0 17: uart:unknown port:00000000 irq:0 18: uart:unknown port:00000000 irq:0 19: uart:unknown port:00000000 irq:0 20: uart:unknown port:00000000 irq:0 21: uart:unknown port:00000000 irq:0 22: uart:unknown port:00000000 irq:0 23: uart:unknown port:00000000 irq:0 24: uart:unknown port:00000000 irq:0 25: uart:unknown port:00000000 irq:0 26: uart:unknown port:00000000 irq:0 27: uart:unknown port:00000000 irq:0 28: uart:unknown port:00000000 irq:0 29: uart:unknown port:00000000 irq:0 30: uart:unknown port:00000000 irq:0 31: uart:unknown port:00000000 irq:0 user@user-DH61BE:~$ ---------------------------------------------------------------------- MarkMLl · 2024-11-27 20:49 Quote from: robert rozee on November 27, 2024, 09:31:41 pmi found reference online to a useful location, /proc/tty/driver/serial, but it is only readable by root, see below. at least it shows the information exists. note: it is mostly the ttySxx ports that are causing me problems. In any event, using /proc for anything other than per-process information is generally considered an embarrassment and it's probably best not relied on. Walking the /sys tree and using substring matches (rather than precise comparisons) is almost certainly the way to go. There's a couple of places where you have to apply a bit of magic to know where to start looking: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i (* This represents a hack that I'm not entirely happy about. Having found a  serial device name in the tree such as USB0 or ACM0, it appears to be  necessary to step back by a varying amount (../.. or ../../.. in the case of  those two examples) before starting to look for attributes such as idVendor.  The layout is probably dependent on the device driver (kernel module), but  it's not possible to identify this without knowing the tree layout; making  things worse, it's not possible to simply use a deep search every time since  that might pick up spurious USB hubs.*)function prefixPath(const device: string): string; begin  if Pos('ttyACM', device) > 0 then    result := '../../../'  else    result := '../../'end { prefixPath } ;  (* The position of the driver symlink relative to the device name will vary  depending on the bus type. For the moment, I'm assuming that this can also  refer to the device name rather than having to refer to the cached bus type.*)function driverPath(const device: string): string; begin  if Pos('ttyS', device) > 0 then    result := 'device/'  else    result := ''end { driverPath } ;  I'm not sure yet whether your discovery regarding kernel 6.8 requires that the second of those needs to be modified: I suspect not since the variable number of ../s in the symlink giving you problems will be accommodated by a substring match. I'm very much still working on this. My code is oriented more towards finding the serial device representing a specific instrument etc. rather than describing everything, but so far the changes I've made are on the periphery: the core walkDirs() function is standing up well. More tomorrow hopefully. MarkMLl ---------------------------------------------------------------------- MarkMLl · 2024-11-28 10:29 OK, I've uploaded some changes to https://github.com/MarkMLl/serialcomms . Nothing major, but includes detection of actual 16550s (on both pnp and pci busses) and dubious 8250s (on the platform bus). I find that I'm able to open a non-present device (which in my case is a platform 8250 on ttyS2 or ttyS3), but not to write to it let alone read a loopback. I'd expect existing but exclusively-opened devices to fail at the open stage. The "stepping back" problem looks like... a problem, as per yesterday evening's note I see that I'm having to handle that differently but am able to distinguish based on the device name (ttyACMx etc.). One way of tackling this would be to * Starting at /sys/devices, find a named device e.g. ttyS0. * From there step back until a particular combination of items (e.g. power) is found. * From there always do a substring match working forwards. The specimen units are oriented towards finding the port associated with a particular device, e.g. a multimeter. It's easy enough to get a list of all units that match a specific pattern (see the 8250 and 16550 units), I've not put significant work into getting a single list of all possible serial devices: it's superficially easy, but might get involved if one wants to retain all the info about bus type etc. I'm not entirely happy with the overall situation, since I feel we're very much at the mercy of how udev (hence possibly systemd) behaves. I've had some success in the past detecting plug/unplug events (note to self: jds6600code.pas) where those pass an environment which gives information on bus position etc., but it's inescapable that it's necessary to build up a list of what devices are already available when a program starts. Updated: this might be a viable alternative: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i $ ls -l /dev/ttyS0 crw-rw---- 1 root dialout 4, 64 Nov 23 15:19 /dev/ttyS0 $ ls -l /sys/dev/char/4:64 lrwxrwxrwx 1 root root 0 Nov 23 15:20 /sys/dev/char/4:64 -> ../../devices/pnp0/00:07/tty/ttyS0 $ udevadm info -a -p /sys/devices/pnp0/00:07/tty/ttyS0 | grep DRIVERS | grep -v '""'     DRIVERS=="serial"  ...and so on. That's working by parsing the attributes which are passed to the hotplug rules in /etc/udev/rules.d etc., which are much less likely to change than are arbitrary logged messages (dmesg, syslog, systemctl output and so on). MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-11-28 13:25 does the below help with your stepping-back problem? it expands symlinks while proceeding down through the tree. cheers, rob   :-) Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program project1; uses SysUtils, BaseUnix, StrUtils;  function normalize(S:string):string;           // expands any symlink at end of Svar T:string;    n:integer;begin  T:=fpReadLink(S);                          // try to follow a symlink (returns '' if fails)  if T='' then result:=S else                  // if T is empty, was not a symbolic link  begin    n:=0;    while pos('../', T)=1 do                 // strip off as many leading "../" from T as possible    begin    delete(T,1,3);      inc(n)                                   // keep count of number of steps    end;     inc(n);                                  // +1 to account for old filename at end of S    while n<>0 do                              // strip off filename and directories from end of S    begin      setlength(S, length(S)-1);               // trim one character at a time      if S[length(S)]='/' then dec(n)        // count down for each "/" removed (leaving a trailing "/")    end;    result:=S+T                                // return the two trimmed strings, concatinated together  endend;  var DeviceName, DriverName, S:string;                           SR:TSearchRec;  begin  if FindFirst('/dev/tty*', faAnyFile , SR) = 0 then  repeat    DeviceName:=SR.Name;    if (DeviceName<>'.') and (DeviceName<>'..') then    if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or              // this suffices with FPC prior to 3.20       DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then     // from FPC 3.20 onwards we need this instead    begin      S:='/sys';      S:=normalize(S+'/class');      S:=normalize(S+'/tty');      S:=normalize(S+'/'+DeviceName);      S:=normalize(S+'/device');      S:=normalize(S+'/driver');       DriverName:=S;      writeln(PadRight(DeviceName, 9),'-->  ', DriverName);     end;   until FindNext(SR) <> 0;  FindClose(SR)end. output on my desktop: Code: [Select]ttyS0    -->  /sys/bus/serial-base/drivers/port ttyS1    -->  /sys/bus/serial-base/drivers/port ttyS2    -->  /sys/bus/serial-base/drivers/port ttyS3    -->  /sys/bus/serial-base/drivers/port ttyS6    -->  /sys/bus/serial-base/drivers/port ttyS7    -->  /sys/bus/serial-base/drivers/port ttyS8    -->  /sys/bus/serial-base/drivers/port ttyS9    -->  /sys/bus/serial-base/drivers/port ttyS10   -->  /sys/bus/serial-base/drivers/port ttyS11   -->  /sys/bus/serial-base/drivers/port ttyS12   -->  /sys/bus/serial-base/drivers/port ttyS13   -->  /sys/bus/serial-base/drivers/port ttyS14   -->  /sys/bus/serial-base/drivers/port ttyS15   -->  /sys/bus/serial-base/drivers/port ttyS16   -->  /sys/bus/serial-base/drivers/port ttyS17   -->  /sys/bus/serial-base/drivers/port ttyS18   -->  /sys/bus/serial-base/drivers/port ttyS19   -->  /sys/bus/serial-base/drivers/port ttyS20   -->  /sys/bus/serial-base/drivers/port ttyS21   -->  /sys/bus/serial-base/drivers/port ttyS22   -->  /sys/bus/serial-base/drivers/port ttyS23   -->  /sys/bus/serial-base/drivers/port ttyS24   -->  /sys/bus/serial-base/drivers/port ttyS25   -->  /sys/bus/serial-base/drivers/port ttyS26   -->  /sys/bus/serial-base/drivers/port ttyS27   -->  /sys/bus/serial-base/drivers/port ttyS28   -->  /sys/bus/serial-base/drivers/port ttyS29   -->  /sys/bus/serial-base/drivers/port ttyS30   -->  /sys/bus/serial-base/drivers/port ttyS31   -->  /sys/bus/serial-base/drivers/port ttyS4    -->  /sys/bus/serial-base/drivers/port ttyS5    -->  /sys/bus/serial-base/drivers/port ttyUSB1  -->  /sys/bus/usb-serial/drivers/cp210x ttyACM0  -->  /sys/bus/usb/drivers/cdc_acm ttyUSB0  -->  /sys/bus/usb-serial/drivers/cp210x ---------------------------------------------------------------------- MarkMLl · 2024-11-28 13:41 Well, I've "put it to bed" for the moment but the main problem is finding out- for all related devices- whether there actually is a parent directory that contains some recognisable marker... 'power' appears to be a good candidate. So for the kernel I've got here: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i $ find . -name ttyS2 ./platform/serial8250/tty/ttyS2 $ ls ./platform/serial8250/tty/ttyS2/../.. driver  driver_override  modalias  power  subsystem  tty  uevent  The prefixPath() function I posted yesterday evening seems to suggest that the number of steps backwards depends on the kernel module being used as the driver, but at least having some recognisable marker means that we can potentially call it an algorithm rather than an arbitrary hack. MarkMLl ---------------------------------------------------------------------- vangli · 2024-11-28 14:08 Hi On my ubuntu 24.04 (kernel 6.8.0-49) it seems very consistent that the directory "/dev/serial/" shows which serial ports are active. However, I have only USB types of such, and hasn't been able to test with a true RS232 serial port using the build in UART on motherboard, which is reported other places. The directory will not be there if no (USB) serial ports are connected or active (powered??) as far as I can see, or is bounded to only plug and play devices. ---------------------------------------------------------------------- MarkMLl · 2024-11-28 14:54 /dev/serial is a recent innovation, and might not be portable to other distreaux. MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-12-03 11:27 with further digging... in /usr/share/fpcsrc/3.2.2/rtl/linux/termio.pp at line 37 we find: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {We can implement ttyname more efficiently using proc than by including the generic ttyname.inc file.} function TTYName(Handle:cint):string; { Return the name of the current tty described by handle f.  returns empty string in case of an error.} var s:string[32];    t:string[64]; begin  ttyname:='';  if isatty(handle)=1 then    begin    str(handle,s);      t:='/proc/self/fd/'+s+#0;      ttyname[0]:=char(fpreadlink(@t[1],@ttyname[1],255));    end;end; is this use of data from within /proc by the FPC RTL unwise? and applying IsATTY(FD) to each of my /dev/ttySxx correctly identifies those ports that are real: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program project1; uses SysUtils, BaseUnix, TermIO; var DeviceName:string;          SR:TSearchRec;            FD:longint; begin  if FindFirst('/dev/tty*', faAnyFile , SR) = 0 then  repeat    DeviceName:=SR.Name;     if (DeviceName<>'.') and (DeviceName<>'..') then    if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or           // this suffices with FPC prior to 3.20       DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then  // from FPC 3.20 onwards we need this instead    begin      FD:=fpOpen('/dev/'+DeviceName, O_RDWR or O_NONBLOCK or O_NOCTTY);    if FD>0 then      begin        if IsATTY(FD)<>0 then writeln(TTYname(FD));        fpclose(FD)      end    end  until FindNext(SR) <> 0;  FindClose(SR)end. Code: [Select]user@user-DH61BE:~/pascal/Serial Watcher/Serial Watcher mk4$ ./project1 /dev/ttyS0 /dev/ttyS4 /dev/ttyS5 /dev/ttyACM0 /dev/ttyUSB1 /dev/ttyUSB0 user@user-DH61BE:~/pascal/Serial Watcher/Serial Watcher mk4$ IsATTY just uses TCGetAttr, which in turn makes a TCGETS ioctl call: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Function TCGetAttr(fd:cint;var tios:TermIOS):cint; inline;begin  TCGetAttr:=fpIOCtl(fd,TCGETS,@tios);end; // [...] Function IsATTY (Handle:cint):cint;{  Check if the filehandle described by 'handle' is a TTY (Terminal)}var  t : Termios;begin  if TCGetAttr(Handle,t)=0 then    IsAtty:=1  else    IsAtty:=0;end; fyi, the above are all using Lazarus 3.6/FPC3.2.2, Linux Mint XFCE 22 x86_64, gtk2. cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-12-03 11:39 Quote from: robert rozee on December 03, 2024, 12:27:12 pmis this use of data from within /proc by the FPC RTL unwise? No, because self is a symlink to the current pid so is basically what /proc is intended for in "unix doctrine". I don't know whether /proc/self (as distinct from /proc/$pid) is portable to other than Linux, and there will obviously be variability once you're looking at the per-process data. The problem with /proc is the extent to which it's been allowed to accumulate non-process stuff related to network status, attached devices such as SCSI (and its successors) and so on: other unixes that I've seen are far stricter about its only containing per-process stuff. MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-12-03 13:03 can you think of any way we can perhaps make use of /proc/self/fd to get hold of the same (or similar) information held in the 'file' /proc/tty/driver/serial? it seems every path i follow ends up steering me back to the old code that worked pre-6.8 where there was the check for the driver being called "serial8250"; i'd be happy on just checking the UART is not called "unknown". (contents of my /proc/tty/driver/serial) Code: [Select]serinfo:1.0 driver revision: 0: uart:16550A port:000003F8 irq:4 tx:0 rx:0 1: uart:unknown port:000002F8 irq:3 2: uart:unknown port:000003E8 irq:4 3: uart:unknown port:000002E8 irq:3 4: uart:16550A port:0000E000 irq:16 tx:0 rx:0 5: uart:16550A port:0000E008 irq:16 tx:0 rx:0 6: uart:unknown port:00000000 irq:0 7: uart:unknown port:00000000 irq:0 8: uart:unknown port:00000000 irq:0 9: uart:unknown port:00000000 irq:0 etc... while IsATTY(FD)<>0 seems to work fine as a test, i'm still wary that it could (because of the ioctl call) introduce excessive delays when checking bluetooth devices; perhaps i should use the 'old test' for pre-6.8 kernels, and then for 6.8+ use dmesg | grep "tty" with checks for the buffer having not wrapped, with a fallback to using IsATTY(). cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-12-03 13:13 No, you need to look at the /sys tree to get driver etc. details. MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-12-05 09:02 using the driver paths provided by the below code: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program project1; uses SysUtils, BaseUnix, StrUtils;  function normalize(S:string):string;           // expands any symlink at end of Svar T:string;    n:integer;begin  T:=fpReadLink(S);                          // try to follow a symlink (returns '' if fails)  if T='' then result:=S else                  // if T is empty, was not a symbolic link  begin    n:=0;// to aviod '//' from appearing in result, probably should have in here: if pos('/', T)=1 then delete(T, 1, 1);    while pos('../', T)=1 do                   // strip off as many leading "../" from T as possible    begin      delete(T,1,3);    inc(n)                                   // keep count of number of steps    end;     inc(n);                                    // +1 to account for old filename at end of S    while n<>0 do                            // strip off filename and directories from end of S    begin      setlength(S, length(S)-1);               // trim one character at a time      if S[length(S)]='/' then dec(n)          // count down for each "/" removed (leaving a trailing "/")    end;    result:=S+T                                // return the two trimmed strings, concatenated together  endend; var DeviceName, DriverName, S:string;                           SR:TSearchRec; begin  if FindFirst('/dev/tty*', faAnyFile , SR) = 0 then  repeat    DeviceName:=SR.Name;    if (DeviceName<>'.') and (DeviceName<>'..') then    if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or              // this suffices with FPC prior to 3.20       DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then     // from FPC 3.20 onwards we need this instead    begin      S:='/sys';      S:=normalize(S+'/class');      S:=normalize(S+'/tty');      S:=normalize(S+'/'+DeviceName);      S:=normalize(S+'/device');      S:=normalize(S+'/driver');       DriverName:=S;      writeln(PadRight(DeviceName, 9),'-->  ', DriverName);     end;   until FindNext(SR) <> 0;  FindClose(SR)end. that produces this output on my desktop: Code: [Select]ttyS0    -->  /sys/bus/serial-base/drivers/port ttyS1    -->  /sys/bus/serial-base/drivers/port ttyS2    -->  /sys/bus/serial-base/drivers/port ttyS3    -->  /sys/bus/serial-base/drivers/port ttyS6    -->  /sys/bus/serial-base/drivers/port ttyS7    -->  /sys/bus/serial-base/drivers/port ttyS8    -->  /sys/bus/serial-base/drivers/port ttyS9    -->  /sys/bus/serial-base/drivers/port ttyS10   -->  /sys/bus/serial-base/drivers/port ttyS11   -->  /sys/bus/serial-base/drivers/port ttyS12   -->  /sys/bus/serial-base/drivers/port ttyS13   -->  /sys/bus/serial-base/drivers/port ttyS14   -->  /sys/bus/serial-base/drivers/port ttyS15   -->  /sys/bus/serial-base/drivers/port ttyS16   -->  /sys/bus/serial-base/drivers/port ttyS17   -->  /sys/bus/serial-base/drivers/port ttyS18   -->  /sys/bus/serial-base/drivers/port ttyS19   -->  /sys/bus/serial-base/drivers/port ttyS20   -->  /sys/bus/serial-base/drivers/port ttyS21   -->  /sys/bus/serial-base/drivers/port ttyS22   -->  /sys/bus/serial-base/drivers/port ttyS23   -->  /sys/bus/serial-base/drivers/port ttyS24   -->  /sys/bus/serial-base/drivers/port ttyS25   -->  /sys/bus/serial-base/drivers/port ttyS26   -->  /sys/bus/serial-base/drivers/port ttyS27   -->  /sys/bus/serial-base/drivers/port ttyS28   -->  /sys/bus/serial-base/drivers/port ttyS29   -->  /sys/bus/serial-base/drivers/port ttyS30   -->  /sys/bus/serial-base/drivers/port ttyS31   -->  /sys/bus/serial-base/drivers/port ttyS4    -->  /sys/bus/serial-base/drivers/port ttyS5    -->  /sys/bus/serial-base/drivers/port ttyUSB1  -->  /sys/bus/usb-serial/drivers/cp210x ttyACM0  -->  /sys/bus/usb/drivers/cdc_acm ttyUSB0  -->  /sys/bus/usb-serial/drivers/cp210x we can then search downwards, using cat */tty/ttyS0/type iterating through all the ttySxx ports: user@user-DH61BE:/sys/bus/serial-base/drivers/port$ ls 0000:02:00.0:0.0  serial8250:0.11  serial8250:0.16  serial8250:0.20  serial8250:0.25  serial8250:0.3   serial8250:0.8 0000:02:00.0:0.1  serial8250:0.12  serial8250:0.17  serial8250:0.21  serial8250:0.26  serial8250:0.30  serial8250:0.9 00:05:0.0         serial8250:0.13  serial8250:0.18  serial8250:0.22  serial8250:0.27  serial8250:0.31  uevent serial8250:0.1    serial8250:0.14  serial8250:0.19  serial8250:0.23  serial8250:0.28  serial8250:0.6 serial8250:0.10   serial8250:0.15  serial8250:0.2   serial8250:0.24  serial8250:0.29  serial8250:0.7 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ cat */tty/ttyS0/type 4 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ cat */tty/ttyS1/type 0 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ cat */tty/ttyS2/type 0 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ cat */tty/ttyS3/type 0 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ cat */tty/ttyS4/type 4 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ cat */tty/ttyS5/type 4 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ cat */tty/ttyS6/type 0 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ cat */tty/ttyS7/type 0 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ cat */tty/ttyS8/type 0 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ cat */tty/ttyS9/type 0 user@user-DH61BE:/sys/bus/serial-base/drivers/port$ type 0 = 'unknown', type 4 = '16550A'     (see: https://github.com/Distrotech/setserial/blob/master/setserial.c lines 46-61. so ports ttyS0, ttyS4, ttyS5 are real. just need to code the cat... part into pascal. cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-12-05 09:28 https://github.com/MarkMLl/serialcomms/blob/main/locateports.pas line 195 MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-12-05 16:18 Quote from: MarkMLl on December 05, 2024, 10:28:12 amhttps://github.com/MarkMLl/serialcomms/blob/main/locateports.pas line 195 it looks like you code is extracting far more information than i need, and seems to then use the information to make a 'best guess'. whereas, i'm more interested in a generic solution - i don't care about manufacturer, etc, i just want to know if there is real hardware (of any variety) present. having written a whole load of code to search down through the directory tree for a matching device name, i found that for the ttySxx ports you can (with kernel 6.8+) just look at '/sys/class/tty/'+DeviceName+'/type', which holds the UART type, with 0 -> no hardware present. hence i've arrived back at something fairly close to my original code: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program project1; uses SysUtils, BaseUnix, TermIO;  function normalize(S:string):string;           // expands any symlink at the end of Svar T:string;    n:integer;begin  T:=fpReadLink(S);                          // try to follow a symlink (returns '' if fails)   if T='' then result:=S else                  // if T is empty, there was no symbolic link present  begin    n:=0;    while pos('../', T)=1 do                 // strip off as many leading "../" from T as possible    begin      delete(T,1,3);      inc(n)                                 // keep count of number of steps    end;     inc(n);                                  // +1 to account for removing (old) filename at end of S     while n<>0 do                              // strip off (old) filename and directories from end of S    begin      setlength(S, length(S)-1);               // trim one character at a time      if S[length(S)]='/' then dec(n)          // count down for each "/" removed    end;                                       // when the loop exits it leaves a trailing "/"     T:=S+T;                                  // concatenate the two trimmed strings    repeat      n:=pos('//', T);                         // fixup for any double "//" where S and T are joined      if n<>0 then delete(T, n, 1)           // (in the present application this should never happen)    until n=0;     result:=T                                  // return an absolute path  endend;  type   TSerialStruct = packed record          typ: cint;          line: cint;          port: cuint;          irq:  cint;          flags: cint;          xmit_fifo_size: cint;          custom_divisor: cint;          baud_base: cint;          close_delay: cushort;          io_type: cchar;          reserved_char:  pcchar;        hub6: cint;          closing_wait: cushort; // time to wait before closing          closing_wait2: cushort; // no longer used...        iomem_base: pcchar;          iomem_reg_shift: cushort;        port_high: clong; // cookie passed into ioremap          overrun: array [1..64] of byte   end;  var DeviceName, DriverPath, S:string;                         SR:TSearchRec;                            T:text;                            I:integer;                         FD:longint;                           SS:TSerialStruct;                         ck:string;  begin  if FindFirst('/dev/tty*', faAnyFile , SR)=0 then  repeat    DeviceName:=SR.Name;     if (DeviceName<>'.') and (DeviceName<>'..') then    if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or         // this suffices with FPC prior to 3.20       DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then  // from FPC 3.20 onwards we need this instead    begin      S:='/sys';      S:=normalize(S+'/class');                                              // need all this palaver just so we can      S:=normalize(S+'/tty');                                                // check that DriverPath does not begin      S:=normalize(S+'/'+DeviceName);                                        // with "/sys/bus/serial-base", as this      S:=normalize(S+'/device');                                             // would indicate a real ttySxx under      S:=normalize(S+'/driver');                                             // kernel 6.8+       DriverPath:=S;       if (pos('/sys/bus/serial- base/', DriverPath)<>1)         and (ExtractFileName(DriverPath)<>'serial8250') then writeln(DeviceName, ' is a removable device') else      begin        S:='/sys/class/tty/'+DeviceName+'/type';       // MUCH easier to find the "type" file here!         try                                          // access may be blocked with pre-6.8 kernels          ck:='(/type)';          I:=-1;          assign(T, S);          reset(T);          if not eof(T) then readln(T, I)        finally        close(T)        end; //      I:=-1;                                       // uncomment to force use of TIOCGSERIAL ioctl check         if I<0 then                                    // we arrive here if we could not read the "type" file        begin          ck:='(ioctl)';        FD:=fpOpen('/dev/'+DeviceName, O_RDWR or O_NONBLOCK or O_NOCTTY);          if FD>0 then          try            if fpIOCtl(FD, TIOCGSERIAL, @SS)<>-1 then I:=SS.typ          finally          fpclose(FD)          end        end;         if I>0 then                                  // 0 -> unknown: no hardware present      begin                                          // -1 -> unable to perform either checks          case I of 0:S:='unknown';                  1:S:='8250 UART';                    2:S:='16450 UART';                  3:S:='16550 UART';                    4:S:='16550A UART'               else S:='other ('+IntToStr(I)+')'          end; {of case}        writeln(DeviceName, ' is type ', S, '    ', DriverPath, '    ', ck)        end      end    end  until FindNext(SR) <> 0;  FindClose(SR)end.addendum: added an 'overrun' area to TSerialStruct as suggested by MarkMLI the code decides between removable and fixed serial ports on the basis of: either (a) the normalized driver path starting with '/sys/bus/serial-base/', or (b) ending in 'serial8250', denotes a fixed port. and if reading from '/sys/class/tty/'+DeviceName+'/type' is not available, then TIOCGSERIAL ioctl is used as a fallback. if a few of those reading this could test out this code, it would be very much appreciated  cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-12-05 17:04 Quote from: robert rozee on December 05, 2024, 05:18:47 pmQuote from: MarkMLl on December 05, 2024, 10:28:12 amhttps://github.com/MarkMLl/serialcomms/blob/main/locateports.pas line 195 it looks like you code is extracting far more information than i need, I was specifically pointing you at a cat() function. I'd presumed that you'd already broadly read the rest of the stuff, otherwise I've been wasting my time for the last couple of weeks. For other reasons I've been looking at TIOCGSERIAL etc. today, be careful if you try using it in anger since the kernel might actually overspill the data strucure by around 10 bytes. MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-12-06 04:31 Quote from: MarkMLl on December 05, 2024, 06:04:53 pm[...] I was specifically pointing you at a cat() function. I'd presumed that you'd already broadly read the rest of the stuff, otherwise I've been wasting my time for the last couple of weeks the 'magic' of the linux cat utility is (as i discovered) that it can take a wildcard at the top of a path and will search all the branches from that wildcard down to find a match. in my case, this was starting at /sys/bus/serial-base/drivers/port and searching through */tty/ttyS0/type. within /sys/bus/serial-base/drivers/port are 32 directory structures, only one of which has a branch ending in ttyS0/type (and the same for each of the other ttySxx ports). the cat utility took all the hard work out of confirming this! but then i realized (after writing the search code) that i could just look in /sys/class/tty/ttyS0/type for the same result - provided permissions allowed. our discussion has yielded a far better understanding of how all the symlinks are used to create the /sys/class tree, and of the complexities of stepping up and down through it. also how to create a 'normalized' version of a path that yields the top-end marker "/sys/bus/serial-base/" that (for now) is a substitute for the previous "serial8250" bottom-end marker in distinguishing between fixed and removable serial ports. Quote from: MarkMLl on December 05, 2024, 06:04:53 pm [...] I've been looking at TIOCGSERIAL etc. today, be careful if you try using it in anger since the kernel might actually overspill the data strucure by around 10 bytes i'll add an overrun area to the end of TSerialStruct to allow for this! cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-12-06 07:51 Quote from: robert rozee on December 06, 2024, 05:31:18 amthe 'magic' of the linux cat utility is (as i discovered) that it can take a wildcard Careful. It won't be cat doing that, it will be the shell. Quotei'll add an overrun area to the end of TSerialStruct to allow for this! Needs ten bytes on an x86_64. I was using the declaration from https:/ /forum.lazarus.freepascal.org/index.php/topic,35302.msg403957.html and on reflection it's most likely that the C compiler is doing something odd with alignment of the last few fields. MarkMLl ---------------------------------------------------------------------- Thaddy · 2024-12-06 10:19 In that case {$packrecords C} should help, at least for GNU, but since it is not part of the language specification this may vary between C compilers. Actually,"Yes, there is a formal specification for how C should handle the alignment of structs. The C standard specifies that each non-bit- field member of a structure or union object is aligned in an implementation-defined manner appropriate to its type. This means that the alignment of a member is based on its own type, and the compiler has the freedom to decide how to align each member." Which basically says, you are on your own, good luck... In GNU C you can use __attribute__((aligned)) for structs, don't know if ms or intel C compilers have similar. This also means you need to have to some extend knowledge and control over the C code. ---------------------------------------------------------------------- MarkMLl · 2024-12-06 11:16 Quote from: Thaddy on December 06, 2024, 11:19:43 amIn that case {$packrecords C} should help, at least for GNU, but since it is not part of the language specification this may vary between C compilers. Thanks for that reminder Thaddy, I'll check (and report back). I'm very uncomfortable with this, but I think it highlights a wider language/OS problem: C doesn't adequately specify alignment/packing, the (Linux) kernel is prepared to clobber a caller's stack writing to something which is inadequately specified, and any other language which thinks it can replicate a kernel data structure adequately is on very thin ice. Mercifully, most systems programming calls also have a length parameter but this isn't the first time I've come across something related to serial comms which lacks one. Updated: no change unfortunately, I've added serialinfo.lpr and .lpi at https://github.com/MarkMLl/serialcomms which demonstrate it. Context is that one needs to delve into that stuff if one wants a thoroughly non-standard Baud rate, however that works without having to get involved with the fields which I suspect have packing/alignment issues. MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-12-06 15:22 Mark, your repository at https://github.com/MarkMLl/serialcomms seems to be missing the file "Locate16550Port.pas" as well as whatever is needed for CDC detection - containing a routine called FindFenrirPort(). i was able to compile and run serialinfo fine, but portscan only found the second of two CP2101 devices (/dev/ttyUSB1). cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-12-06 15:35 Quote from: robert rozee on December 06, 2024, 04:22:20 pmMark, your repository at https://github.com/MarkMLl/serialcomms seems to be missing the file "Locate16550Port.pas" as well as whatever is needed for CDC detection - containing a routine called FindFenrirPort(). i was able to compile and run serialinfo fine, but portscan only found the second of two CP2101 devices (/dev/ttyUSB1). Thanks, should be fixed. "Fenrir" comes from the developer of the HPIB interface I've got here, which is a Prologix clone implemented using a PIC. Apropos CP2101: I'm not sure how many of those I've got for testing and I'm in the middle of other comms stuff so don't want to mess up my adapter population, but the key is to appreciate that each of those units basically contains only an example template. So I think you'll need to look very carefully at lsusb output, the particular thing that causes problems is the product field. MarkMLl ---------------------------------------------------------------------- MarkMLl · 2024-12-08 19:12 OK, I can see what's happening there. Testing with a couple of "FT232"-style devices, one real and one... not so real. If you enable debug in that program (const near top) you'll see that multiple devices are detected and cached correctly. However FindPortByDescription() is specifically coded to return the highest- numbered matching device, since what I wanted (at the time) was to detect a specific instrument and I assumed that the most-recently- hotplugged was the one that's relevant. From the POV of being able to iterate through devices and look at e.g. the driver, the code's OK. I find that much the same info is available using TIOCGSERIAL, but that would require that the port be opened which as you've pointed out would be a problem if it were locked. Updated: I've added more example functions to the stuff on Github. However it's important to appreciate that these are basically examples, and that the device descriptions will normally need to be tailored: that's particularly important where a device contains an EPROM (documented or otherwise) which allows it to be branded with a product description and serial number. I don't know to what extent this addresses the original "Kernel 6.8" issue, but it should be relatively easy to adjust .. vs ../.. decisions based on the version detected at runtime. MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-12-18 15:56 i may have come up with something even shorter - and potentially more reliable going forward... Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program project1; uses SysUtils, BaseUnix, TermIO; var DeviceName:string;          SR:TSearchRec;            FD:longint;//        tios:TermIOS;       n, PT:integer; begin  n:=0;  if FindFirst('/dev/tty*', faAnyFile , SR) = 0 then  repeat    DeviceName:=SR.Name;     if (DeviceName<>'.') and (DeviceName<>'..') then    if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or         // this suffices with FPC prior to 3.20       DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then  // from FPC 3.20 onwards we need this instead    begin      if FileExists('/sys/class/tty/'+DeviceName+'/type') then PT:=1           // port type 1 = fixed serial port                                                        else PT:=2;          // port type 2 = removable/USB       FD:=fpOpen('/dev/'+DeviceName, O_RDWR or O_NONBLOCK or O_NOCTTY);      if FD<=0 then PT:=0 else                                               // couldn't open handle -> failure!    begin        if PT=1 then if IsATTY(FD)=0 then PT:=0                              // IsATTY (and TTYname) use ioctl/TCGETS                                   else inc(n);                              // count number of fixed serial ports//      if PT=1 then if fpIOCtl(FD,TCGETS,@tios)<>0 then PT:=0                 // TCGETS: on success 0 is returned,//                                                else inc(n);               //         on error -1 is returned      fpClose(FD)      end;       case PT of 1:writeln('* ', DeviceName);                                // fixed serial port, verified as real                 2:writeln('  ', DeviceName)                                 // removable (USB) serial port      end    end  until FindNext(SR) <> 0;  if n<>0 then writeln('(', n, ' fixed devices)');  FindClose(SR)end. Code: [Select]user@user-DH61BE:~$ ./project1 * ttyS0 * ttyS4 * ttyS5   ttyUSB0   ttyUSB1   ttyACM0   ttyACM1 (3 fixed devices) user@user-DH61BE:~$ this uses FileExists('/sys/class/tty/'+DeviceName+'/type') to decide between fixed (ttySxx) and removable (USB) serial ports. while the contents of the ".../type" files can only be read by root when running an older kernel, the existence of the file can be checked by non-root users without any problems - verified with a 4.15 kernel using mint 19.3. we perform fpOpen('/dev/'+DeviceName [...]) on both fixed and removable devices to ensure that if the user is not a member of the dialout group then no devices will be returned. sorting out what fixed ports are alive is done with fpIOCtl(FD,TCGETS,@tios)<>0, which unlike TIOCGSERIAL also works on removable/USB devices (although this is not needed here). it is sufficient to just check if the IOctl call succeeds, the data returned does not matter. conveniently, IsATTY(FD) uses TCGETS, so we can just use the inbuilt routine. i'd be interested if anyone can find any situations where the above code misidentified or fails to identify a serial port - with the proviso that the code is intended for Linux. cheers, rob   :-) ---------------------------------------------------------------------- MarkMLl · 2024-12-18 16:20 Quote from: robert rozee on December 18, 2024, 04:56:45 pmwe perform fpOpen('/dev/'+DeviceName [...]) on both fixed and removable devices to ensure that if the user is not a member of the dialout group then no devices will be returned. sorting out what fixed ports are alive is done with fpIOCtl(FD,TCGETS,@tios)<>0, which unlike TIOCGSERIAL also works on removable/USB devices (although this is not needed here). it is sufficient to just check if the IOctl call succeeds, the data returned does not matter. conveniently, IsATTY(FD) uses TCGETS, so we can just use the inbuilt routine. I thought you'd said that you might not be able to open devices because they might already be in use and locked? Apart from that I agree that looking for .../type might have promise for your specific case. MarkMLl ---------------------------------------------------------------------- robert rozee · 2024-12-19 11:11 Quote from: MarkMLl on December 18, 2024, 05:20:42 pmI thought you'd said that you might not be able to open devices because they might already be in use and locked? oops - you are quite right, more or less. the information about not opening a port came from the comment here: https://stackoverflow.com/questions/2530096/how-to-find-all-serial- devices-ttys-ttyusb-on-linux-without-opening-them Quotehrickards suggested to look at the source for "setserial". Its code does exactly what I had in mind. First, it opens a device with: fd = open (path, O_RDWR | O_NONBLOCK) Then it invokes: ioctl (fd, TIOCGSERIAL, &serinfo) If that call returns no error, then it's a serial device, apparently. I found similar code in Serial Programming/termios (https://en.wikibooks.org/wiki/Serial_Programming/termios), which suggested to also add the O_NOCTTY option. There is one problem with this approach, though: When I tested this code on BSD Unix (that is, Mac OS X), it worked as well. However, serial devices that are provided through Bluetooth cause the system (driver) to try to connect to the Bluetooth device, which takes a while before it'll return with a timeout error. This is caused by just opening the device. And I can imagine that similar things can happen on Linux as well - ideally, I should not need to open the device to figure out its type. I wonder if there's also a way to invoke ioctl functions without an open, or open a device in a way that it does not cause connections to be made? Thomas Tempelmann, Mar 27, 2010 at the least, i should not be opening/etc removable/USB devices. i added in that action when i noticed that, if the user was not a member of the dialout group then ttyACM* and ttyUSB* ports were returned as being present, while ttyS* were excluded (as they failed at the fpOpen...). i'll need to find a more subtle way of ensuring no out when the user has no access to serial ports. [half an hour later] looks like i can simply use if fpAccess('/dev/'+DeviceName, R_OK+W_OK)=0 then ... then i can go back to only needing to open/etc fixed ports. i'm moderately happy to blindly assume that ttyS* devices should not be bluetooth. as for in use/locked devices, am not too worried there - as it stands, a version of my applications also runs on windows machines where an in-use serial port is removed from the serial pool by the O/S (the pool is read from a registry key). cheers, rob   :-) ---------------------------------------------------------------------- robert rozee · 2024-12-19 11:35 revised code, making use of fpAccess() to check we have RW access to each device: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program project1; uses SysUtils, BaseUnix, TermIO; var DeviceName:string;          SR:TSearchRec;            FD:longint;//        tios:TermIOS;    n, c, PT:integer; begin  n:=0;  c:=0;//if FindFirst('/dev/tty*', faAnyFile , SR) = 0 then                           // original (only finds tty* devices)  if FindFirst('/sys/class/tty/*', faAnyFile , SR) = 0 then                    // alternative: should ALSO find any rfcomm*   repeat    DeviceName:=SR.Name;     if (DeviceName<>'.') and (DeviceName<>'..') then                           // exclude '.' and '..'    if fpAccess('/dev/'+DeviceName, R_OK+W_OK)=0 then                        // exclude devices we have NO ACCESS to    if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or         // this suffices with FPC prior to 3.20       DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then  // from FPC 3.20 onwards we need this instead    begin      if FileExists('/sys/class/tty/'+DeviceName+'/type') then PT:=1           // port type 1 = fixed serial port                                                        else PT:=2;          // port type 2 = removable/USB      if PT=1 then      begin        FD:=fpOpen('/dev/'+DeviceName, O_RDWR or O_NONBLOCK or O_NOCTTY);      if FD<0 then PT:=0 else                                              // -1, couldn't open /dev entry -> failure!        begin          if IsATTY(FD)=0 then PT:=0                                           // IsATTY (and TTYname) use ioctl/TCGETS                          else inc(n);                                         // count number of fixed serial ports//        if fpIOCtl(FD,TCGETS,@tios)<>0 then PT:=0                          // TCGETS: on success 0 is returned,//                                     else inc(n);                          //         on error -1 is returned          fpClose(FD)        end    end;       if PT<>0 then inc(c);                                                  // count total number of ports      case PT of 1:writeln('* ', DeviceName);                                  // fixed serial port, verified as real                 2:writeln('  ', DeviceName)                                   // removable (USB) serial port      end    end  until FindNext(SR) <> 0;   if n<>0 then writeln('(', n, ' fixed devices)');  if c=0 then writeln('no serial ports found');  FindClose(SR)end. addendum 25-dec-2025: changed search from "/dev/tty*" to "/sys/class/tty/*". in theory this should now also catch /dev/rfcomm* (serial over bluetooth) devices. cheers, rob   :-)