Recent

Author Topic: kernel 6.8 and checking serial ports - failure to match driver name  (Read 6203 times)

MarkMLl

  • Hero Member
  • *****
  • Posts: 8138
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #30 on: December 03, 2024, 02:13:41 pm »
No, you need to look at the /sys tree to get driver etc. details.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

robert rozee

  • Full Member
  • ***
  • Posts: 205
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #31 on: December 05, 2024, 10:02:25 am »
using the driver paths provided by the below code:

Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. uses SysUtils, BaseUnix, StrUtils;
  4.  
  5.  
  6. function normalize(S:string):string;           // expands any symlink at end of S
  7. var T:string;
  8.     n:integer;
  9. begin
  10.   T:=fpReadLink(S);                            // try to follow a symlink (returns '' if fails)
  11.   if T='' then result:=S else                  // if T is empty, was not a symbolic link
  12.   begin
  13.     n:=0;
  14. // to aviod '//' from appearing in result, probably should have in here: if pos('/', T)=1 then delete(T, 1, 1);
  15.     while pos('../', T)=1 do                   // strip off as many leading "../" from T as possible
  16.     begin
  17.       delete(T,1,3);
  18.       inc(n)                                   // keep count of number of steps
  19.     end;
  20.  
  21.     inc(n);                                    // +1 to account for old filename at end of S
  22.     while n<>0 do                              // strip off filename and directories from end of S
  23.     begin
  24.       setlength(S, length(S)-1);               // trim one character at a time
  25.       if S[length(S)]='/' then dec(n)          // count down for each "/" removed (leaving a trailing "/")
  26.     end;
  27.     result:=S+T                                // return the two trimmed strings, concatenated together
  28.   end
  29. end;
  30.  
  31. var DeviceName, DriverName, S:string;
  32.                            SR:TSearchRec;
  33.  
  34. begin
  35.   if FindFirst('/dev/tty*', faAnyFile , SR) = 0 then
  36.   repeat
  37.     DeviceName:=SR.Name;
  38.     if (DeviceName<>'.') and (DeviceName<>'..') then
  39.     if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or              // this suffices with FPC prior to 3.20
  40.        DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then       // from FPC 3.20 onwards we need this instead
  41.     begin
  42.       S:='/sys';
  43.       S:=normalize(S+'/class');
  44.       S:=normalize(S+'/tty');
  45.       S:=normalize(S+'/'+DeviceName);
  46.       S:=normalize(S+'/device');
  47.       S:=normalize(S+'/driver');
  48.  
  49.       DriverName:=S;
  50.       writeln(PadRight(DeviceName, 9),'-->  ', DriverName);
  51.  
  52.     end;
  53.  
  54.   until FindNext(SR) <> 0;
  55.   FindClose(SR)
  56. 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   :-)
« Last Edit: December 05, 2024, 10:04:45 am by robert rozee »

MarkMLl

  • Hero Member
  • *****
  • Posts: 8138
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

robert rozee

  • Full Member
  • ***
  • Posts: 205
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #33 on: December 05, 2024, 05:18:47 pm »
https://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][+][-]
  1. program project1;
  2.  
  3. uses SysUtils, BaseUnix, TermIO;
  4.  
  5.  
  6. function normalize(S:string):string;           // expands any symlink at the end of S
  7. var T:string;
  8.     n:integer;
  9. begin
  10.   T:=fpReadLink(S);                            // try to follow a symlink (returns '' if fails)
  11.  
  12.   if T='' then result:=S else                  // if T is empty, there was no symbolic link present
  13.   begin
  14.     n:=0;
  15.     while pos('../', T)=1 do                   // strip off as many leading "../" from T as possible
  16.     begin
  17.       delete(T,1,3);
  18.       inc(n)                                   // keep count of number of steps
  19.     end;
  20.  
  21.     inc(n);                                    // +1 to account for removing (old) filename at end of S
  22.  
  23.     while n<>0 do                              // strip off (old) filename and directories from end of S
  24.     begin
  25.       setlength(S, length(S)-1);               // trim one character at a time
  26.       if S[length(S)]='/' then dec(n)          // count down for each "/" removed
  27.     end;                                       // when the loop exits it leaves a trailing "/"
  28.  
  29.     T:=S+T;                                    // concatenate the two trimmed strings
  30.     repeat
  31.       n:=pos('//', T);                         // fixup for any double "//" where S and T are joined
  32.       if n<>0 then delete(T, n, 1)             // (in the present application this should never happen)
  33.     until n=0;
  34.  
  35.     result:=T                                  // return an absolute path
  36.   end
  37. end;
  38.  
  39.  
  40. type
  41.    TSerialStruct = packed record
  42.           typ: cint;
  43.           line: cint;
  44.           port: cuint;
  45.           irq:  cint;
  46.           flags: cint;
  47.           xmit_fifo_size: cint;
  48.           custom_divisor: cint;
  49.           baud_base: cint;
  50.           close_delay: cushort;
  51.           io_type: cchar;
  52.           reserved_char:  pcchar;
  53.           hub6: cint;
  54.           closing_wait: cushort; // time to wait before closing
  55.           closing_wait2: cushort; // no longer used...
  56.           iomem_base: pcchar;
  57.           iomem_reg_shift: cushort;
  58.           port_high: clong; // cookie passed into ioremap
  59.           overrun: array [1..64] of byte
  60.    end;
  61.  
  62.  
  63. var DeviceName, DriverPath, S:string;
  64.                            SR:TSearchRec;
  65.                             T:text;
  66.                             I:integer;
  67.                            FD:longint;
  68.                            SS:TSerialStruct;
  69.                            ck:string;
  70.  
  71.  
  72. begin
  73.   if FindFirst('/dev/tty*', faAnyFile , SR)=0 then
  74.   repeat
  75.     DeviceName:=SR.Name;
  76.  
  77.     if (DeviceName<>'.') and (DeviceName<>'..') then
  78.     if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or           // this suffices with FPC prior to 3.20
  79.        DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then    // from FPC 3.20 onwards we need this instead
  80.     begin
  81.       S:='/sys';
  82.       S:=normalize(S+'/class');                                                // need all this palaver just so we can
  83.       S:=normalize(S+'/tty');                                                  // check that DriverPath does not begin
  84.       S:=normalize(S+'/'+DeviceName);                                          // with "/sys/bus/serial-base", as this
  85.       S:=normalize(S+'/device');                                               // would indicate a real ttySxx under
  86.       S:=normalize(S+'/driver');                                               // kernel 6.8+
  87.  
  88.       DriverPath:=S;
  89.  
  90.       if (pos('/sys/bus/serial-base/', DriverPath)<>1)
  91.          and (ExtractFileName(DriverPath)<>'serial8250') then writeln(DeviceName, ' is a removable device') else
  92.       begin
  93.         S:='/sys/class/tty/'+DeviceName+'/type';       // MUCH easier to find the "type" file here!
  94.  
  95.         try                                            // access may be blocked with pre-6.8 kernels
  96.           ck:='(/type)';
  97.           I:=-1;
  98.           assign(T, S);
  99.           reset(T);
  100.           if not eof(T) then readln(T, I)
  101.         finally
  102.           close(T)
  103.         end;
  104.  
  105. //      I:=-1;                                         // uncomment to force use of TIOCGSERIAL ioctl check
  106.  
  107.         if I<0 then                                    // we arrive here if we could not read the "type" file
  108.         begin
  109.           ck:='(ioctl)';
  110.           FD:=fpOpen('/dev/'+DeviceName, O_RDWR or O_NONBLOCK or O_NOCTTY);
  111.           if FD>0 then
  112.           try
  113.             if fpIOCtl(FD, TIOCGSERIAL, @SS)<>-1 then I:=SS.typ
  114.           finally
  115.             fpclose(FD)
  116.           end
  117.         end;
  118.  
  119.         if I>0 then                                    // 0 -> unknown: no hardware present
  120.         begin                                          // -1 -> unable to perform either checks
  121.           case I of 0:S:='unknown';
  122.                     1:S:='8250 UART';
  123.                     2:S:='16450 UART';
  124.                     3:S:='16550 UART';
  125.                     4:S:='16550A UART'
  126.                  else S:='other ('+IntToStr(I)+')'
  127.           end; {of case}
  128.           writeln(DeviceName, ' is type ', S, '    ', DriverPath, '    ', ck)
  129.         end
  130.       end
  131.     end
  132.   until FindNext(SR) <> 0;
  133.   FindClose(SR)
  134. 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   :-)
« Last Edit: December 06, 2024, 05:41:08 am by robert rozee »

MarkMLl

  • Hero Member
  • *****
  • Posts: 8138
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #34 on: December 05, 2024, 06:04:53 pm »
https://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
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

robert rozee

  • Full Member
  • ***
  • Posts: 205
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #35 on: December 06, 2024, 05:31:18 am »
[...] 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.

[...] 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

  • Hero Member
  • *****
  • Posts: 8138
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #36 on: December 06, 2024, 08:51:22 am »
the '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.

Quote
i'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
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Thaddy

  • Hero Member
  • *****
  • Posts: 16423
  • Censorship about opinions does not belong here.
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #37 on: December 06, 2024, 11:19:43 am »
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.
« Last Edit: December 06, 2024, 11:33:01 am by Thaddy »
There is nothing wrong with being blunt. At a minimum it is also honest.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8138
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #38 on: December 06, 2024, 12:16:23 pm »
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.

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
« Last Edit: December 06, 2024, 02:55:53 pm by MarkMLl »
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

robert rozee

  • Full Member
  • ***
  • Posts: 205
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #39 on: December 06, 2024, 04:22:20 pm »
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

  • Hero Member
  • *****
  • Posts: 8138
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #40 on: December 06, 2024, 04:35:50 pm »
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).

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
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

MarkMLl

  • Hero Member
  • *****
  • Posts: 8138
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #41 on: December 08, 2024, 08:12:08 pm »
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
« Last Edit: December 09, 2024, 11:04:42 am by MarkMLl »
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

robert rozee

  • Full Member
  • ***
  • Posts: 205
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #42 on: December 18, 2024, 04:56:45 pm »
i may have come up with something even shorter - and potentially more reliable going forward...

Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. uses SysUtils, BaseUnix, TermIO;
  4.  
  5. var DeviceName:string;
  6.             SR:TSearchRec;
  7.             FD:longint;
  8. //        tios:TermIOS;
  9.          n, PT:integer;
  10.  
  11. begin
  12.   n:=0;
  13.   if FindFirst('/dev/tty*', faAnyFile , SR) = 0 then
  14.   repeat
  15.     DeviceName:=SR.Name;
  16.  
  17.     if (DeviceName<>'.') and (DeviceName<>'..') then
  18.     if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or           // this suffices with FPC prior to 3.20
  19.        DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then    // from FPC 3.20 onwards we need this instead
  20.     begin
  21.       if FileExists('/sys/class/tty/'+DeviceName+'/type') then PT:=1           // port type 1 = fixed serial port
  22.                                                           else PT:=2;          // port type 2 = removable/USB
  23.  
  24.       FD:=fpOpen('/dev/'+DeviceName, O_RDWR or O_NONBLOCK or O_NOCTTY);
  25.       if FD<=0 then PT:=0 else                                                 // couldn't open handle -> failure!
  26.       begin
  27.         if PT=1 then if IsATTY(FD)=0 then PT:=0                                // IsATTY (and TTYname) use ioctl/TCGETS
  28.                                      else inc(n);                              // count number of fixed serial ports
  29. //      if PT=1 then if fpIOCtl(FD,TCGETS,@tios)<>0 then PT:=0                 // TCGETS: on success 0 is returned,
  30. //                                                  else inc(n);               //         on error -1 is returned
  31.         fpClose(FD)
  32.       end;
  33.  
  34.       case PT of 1:writeln('* ', DeviceName);                                  // fixed serial port, verified as real
  35.                  2:writeln('  ', DeviceName)                                   // removable (USB) serial port
  36.       end
  37.     end
  38.   until FindNext(SR) <> 0;
  39.   if n<>0 then writeln('(', n, ' fixed devices)');
  40.   FindClose(SR)
  41. 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   :-)
« Last Edit: December 18, 2024, 05:02:43 pm by robert rozee »

MarkMLl

  • Hero Member
  • *****
  • Posts: 8138
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #43 on: December 18, 2024, 05:20:42 pm »
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 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
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

robert rozee

  • Full Member
  • ***
  • Posts: 205
Re: kernel 6.8 and checking serial ports - failure to match driver name
« Reply #44 on: December 19, 2024, 12:11:32 pm »
I 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
Quote
hrickards 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   :-)

 

TinyPortal © 2005-2018