Linux下多串口錯亂的一般解決方案

 某些還在服役的有多個串口的老舊工控設備,在Linux系統下會出現串口錯亂導致無法使用的情況。比較流行的一種解決方式就是給系統中的串口設備重命名。但不同的Linux發行版以及不同的串口擴展芯片,其錯亂的情況是有差異的。這裏就不再多說了。其實導致串口無法使用的軟件方面的原因,是這些系統中的串口設備的地址和中斷資源與硬件設備不匹配。可以通過兩種方式來解決這個問題:

  1. 修改BIOS中的串口設置來匹配系統的設備屬性
  2. 修改系統中的設備屬性來匹配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))

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章