某些還在服役的有多個串口的老舊工控設備,在Linux系統下會出現串口錯亂導致無法使用的情況。比較流行的一種解決方式就是給系統中的串口設備重命名。但不同的Linux發行版以及不同的串口擴展芯片,其錯亂的情況是有差異的。這裏就不再多說了。其實導致串口無法使用的軟件方面的原因,是這些系統中的串口設備的地址和中斷資源與硬件設備不匹配。可以通過兩種方式來解決這個問題:
- 修改BIOS中的串口設置來匹配系統的設備屬性
- 修改系統中的設備屬性來匹配BIOS中的串口設置
第一種方式的好處是,只要修改一次,以後只要不更改系統,不主板電池耗盡,一勞永逸。但是並非所有的主機都提供這個功能,比如,我手上的這臺機器的BIOS只能修改有限的選項,不是你想把中斷改成什麼都行。第二種方式的壞處是,每次系統啓動都需要再次修改,但好處是可以自動化運行(就是執行一個命令),而且大部分主機都可以進入BIOS來查看硬件設置。比如這臺工控主機:
根據目前瞭解的信息,我們可以使用任何Linux下的串口工具來配置系統中串口的地址和中斷資源,如setserial。爲了增加工作中的娛樂性,我使用python實現了一個根據串口硬件配置更改系統串口設備屬性的程序。開始爲了偷懶用了python,後來發現如果操作c的結構體,python真不在行。
程序的配置文件爲Json格式,默認爲:
{
"coms":
[
{"dev": "/dev/ttyS0", "type": "16550A", "addr": "0x03f8", "irq": 4 },
{"dev": "/dev/ttyS1", "type": "16550A", "addr": "0x02f8", "irq": 3 },
{"dev": "/dev/ttyS2", "type": "16550A", "addr": "0x0210", "irq": 11},
{"dev": "/dev/ttyS3", "type": "16550A", "addr": "0x0218", "irq": 11},
{"dev": "/dev/ttyS4", "type": "16550A", "addr": "0x0220", "irq": 11},
{"dev": "/dev/ttyS5", "type": "16550A", "addr": "0x0228", "irq": 11},
{"dev": "/dev/ttyS6", "type": "16550A", "addr": "0x0300", "irq": 10},
{"dev": "/dev/ttyS7", "type": "16550A", "addr": "0x0308", "irq": 10},
{"dev": "/dev/ttyS8", "type": "16550A", "addr": "0x0310", "irq": 10},
{"dev": "/dev/ttyS9", "type": "16550A", "addr": "0x0318", "irq": 10}
]
}
在32位系統下的程序實現如下(如果使用64位的系統,可能得修改其中對c結構體的pack和unpack):
#!/usr/bin/python3
import termios
import os
import fcntl
import json
import struct
import sys
import subprocess
import argparse
import datetime
uart_table = { "8250": 1, "16450":2, "16550":3, "16550A":4 }
coms_json = ' {"coms": [\
{"dev": "/dev/ttyS0", "type": "16550A", "addr": "0x03f8", "irq": 4 },\
{"dev": "/dev/ttyS1", "type": "16550A", "addr": "0x02f8", "irq": 3 },\
{"dev": "/dev/ttyS2", "type": "16550A", "addr": "0x0210", "irq": 11},\
{"dev": "/dev/ttyS3", "type": "16550A", "addr": "0x0218", "irq": 11},\
{"dev": "/dev/ttyS4", "type": "16550A", "addr": "0x0220", "irq": 11},\
{"dev": "/dev/ttyS5", "type": "16550A", "addr": "0x0228", "irq": 11},\
{"dev": "/dev/ttyS6", "type": "16550A", "addr": "0x0300", "irq": 10},\
{"dev": "/dev/ttyS7", "type": "16550A", "addr": "0x0308", "irq": 10},\
{"dev": "/dev/ttyS8", "type": "16550A", "addr": "0x0310", "irq": 10},\
{"dev": "/dev/ttyS9", "type": "16550A", "addr": "0x0318", "irq": 10}\
] }'
def find_com_dev(port, irq):
result = subprocess.run(['ls /dev/ttyS*'],shell=True, stdout=subprocess.PIPE)
devs = result.stdout.decode("utf-8").split('\n')
for dev in devs:
try:
fd = os.open(dev, os.O_RDWR | os.O_NONBLOCK)
termios_attr = bytearray([0]*56)
fcntl.ioctl(fd, termios.TIOCGSERIAL, termios_attr)
termios_attr_unpacked = struct.unpack('iiIiiiiiHbbiHHBHIL', termios_attr)
os.close(fd)
except FileNotFoundError:
continue
if termios_attr_unpacked[2] == port and termios_attr_unpacked[3] == irq:
return dev
return ""
def rename_com_dev(old_name, new_name):
os.rename(new_name, new_name+"11")
os.rename(old_name, new_name)
os.rename(new_name+"11", old_name)
def set_serial(opt):
try:
port_new = int(opt["addr"], 16)
type_new = uart_table[opt["type"]]
irq_new = opt["irq"]
fd = os.open(opt["dev"], os.O_RDWR | os.O_NONBLOCK)
termios_attr = bytearray([0]*56)
fcntl.ioctl(fd, termios.TIOCGSERIAL, termios_attr)
type_old = struct.unpack("i", termios_attr[0:4])
if type_new != type_old:
termios_attr[0:4] = type_new.to_bytes(4, sys.byteorder)
port_old = struct.unpack("I", termios_attr[8:12])
if port_new != port_old:
termios_attr[8:12] = port_new.to_bytes(4, sys.byteorder)
irq_old = struct.unpack("i", termios_attr[12:16])
if(irq_new != irq_old):
termios_attr[12:16] = irq_new.to_bytes(4, sys.byteorder)
fcntl.ioctl(fd, termios.TIOCSSERIAL, termios_attr)
os.close(fd)
return True
except OSError as e:
print(e.strerror)
except Exception as e:
print(str(e))
return False
def print_com_config(config):
print("dev:{3} type:{0} port:{1} irq:{2}"
.format(config["type"], config["addr"], config["irq"], config["dev"]))
if __name__ == "__main__":
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("-c", "--config" , help="config file path")
arg_parser.add_argument("-v", "--version", action='store_true')
args = arg_parser.parse_args()
if args.version:
print("0.90")
sys.exit(0)
if args.config:
try:
config_file = os.open(sys.argv[1], os.O_RDONLY)
coms_json = os.read(config_file, 1024).decode("utf-8")
os.close(config_file)
except OSError as e:
print("{0} open failed because :\n{1}!".format(sys.argv[1], e.strerror))
print("setserial will use the default settings.")
try:
jstr = json.loads(coms_json)
print("you will config com ports with below configs:")
for com_config in jstr["coms"]:
print_com_config(com_config)
chose = input("enter Y/y to continue, others to exit:")
if chose != 'Y' and chose != 'y':
sys.exit(0)
for com_config in jstr["coms"]:
dev = find_com_dev(int(com_config["addr"], 16), int(com_config["irq"]))
if dev == com_config["dev"]:
print("com dev: {0} has correct settings!".format(dev))
continue
if not dev:
if set_serial(com_config):
print("success set: {}".format(com_config["dev"]))
else:
print("failed set: {}".format(com_config["dev"]))
else:
rename_com_dev(dev, com_config["dev"])
print("success rename:{} to {}".format(dev, com_config["dev"]))
except json.JSONDecodeError as e:
print(e.strerror)
except Exception as e:
print(str(e))