x393sata_control.py 8.99 KB
Newer Older
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
1 2 3 4
#!/usr/bin/env python

from __future__ import print_function
from __future__ import division
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

'''
/**
# Copyright (C) 2017, Elphel.inc.
# Description: switch between internal and external SSDs
# Comments:
#     reset_device (3x times max) 
#       does not matter if error or not - reload driver - wait for 10 seconds + 10
#           if error - reload (repeat up to 5x)
#
# Solved problem:
#     when using overlays deleting existing in the lower layer dirs can cause error (hopefully it gets fixed someday):
#       example: 
#           * /mnt/sda1 exists in lower layer: /tmp/rootfs.ro/tmp
#           * upper layer is mounted to /
#       # rmdir /mnt/sda1
#       # mkdir /mnt/sda1
#       mkdir: cannot create directory '/mnt/sda1': Operation not supported
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http:#www.gnu.org/licenses/>.

@author:     Oleg K Dzhimiev
@copyright:  2017 Elphel, Inc.
@license:    GPLv3.0+
@contact:    oleg@elphel.com
@deffield    updated: unknown
'''

__author__ = "Elphel"
__copyright__ = "Copyright 2017, Elphel, Inc."
__license__ = "GPL"
__version__ = "3.0+"
__maintainer__ = "Oleg K Dzhimiev"
__email__ = "oleg@elphel.com"
__status__ = "Development"

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
52 53
import x393sata
import x393_mem
54 55

import subprocess
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
56
import sys
57 58 59 60
import time
import os
import re

61
from time import sleep
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
62

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
63
LOGFILE = "/var/log/x393sata_control.log"
64
STATEFILE = "/var/state/ssd"
65

66
# constants
67 68 69 70 71 72 73
RESET_LIMIT = 3
DRIVER_RELOAD_LIMIT = 5
DRIVER_WAIT_TIME = 10
DRIVER_UNLOAD_TRIES = 30

#global
DEVICE_CONNECTED = False
74

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
def colorize(string, color, bold):
    color=color.upper()
    attr = []
    if color == 'RED':
        attr.append('31')
    elif color == 'GREEN':    
        attr.append('32')
    elif color == 'YELLOW':    
        attr.append('33')
    elif color == 'BLUE':    
        attr.append('34')
    elif color == 'MAGENTA':    
        attr.append('35')
    elif color == 'CYAN':    
        attr.append('36')
    elif color == 'GRAY':    
        attr.append('37')
    else:
        pass
        # red
    if bold:
        attr.append('1')
    return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string)
98 99


100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
def log_msg(msg, mode=0):
    bold = False
    color = ""
    if mode == 2: #bold red - error
        color = "RED"
        bold = True
    elif mode == 3: # just bold
        bold = True
    elif mode == 4: # just bold
        bold = True
        color = "YELLOW" #warning
            
    with open ('/proc/uptime') as f: 
        t=float(f.read().split()[0])
    with open(LOGFILE,'a') as msg_file:
        print("[%8.2f]  %s"%(t,msg),file=msg_file)
    if bold or color:
        msg = colorize(msg,color,bold)    
118
    print (colorize("[%8.2f] %s: "%(t, sys.argv[0].split('/')[-1].split('.')[0]),'GREEN',0)+msg)
119

120

121 122 123 124 125 126 127 128 129
def shout(cmd):
  #subprocess.call prints to console
  subprocess.call(cmd,shell=True)


def connection_errors():
  
  global DEVICE_CONNECTED
  
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
  result = True
  skip0 = True
  MAXI1_ADDR = 0x80000000
  group_range = "HBA_PORT__PxSERR"
  cached_addr = None
  cached_data = None
  first_line = True
  
  range_defines = sata.register_defines[group_range]
  
  for fld in range_defines:  
    byte_addr = 4 * fld['dword_address'] + MAXI1_ADDR
    if byte_addr != cached_addr:
        cached_addr = byte_addr
        cached_data = mem.read_mem(cached_addr)
    data = cached_data
    fld_value = (data >> fld['start_bit']) & ((1 << fld['num_bits']) - 1)
    if first_line:
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
148
        log_msg("%s: 0x%08x [%08x]"%(group_range, data, byte_addr))
149 150
        if data!=0:
          DEVICE_CONNECTED = True
151 152
        first_line = False
    if fld_value or not skip0:
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
153
        log_msg("%8x : %s (%s)"%(fld_value, fld['name'], fld['description'] ))
154
        # the device is there but fails to establish a correct link
155
        if fld['name']=="DIAG.B" or fld['name']=="DIAG.S" or fld['name']=="ERR.E":
156
          result = False
157
        
158 159
  return result

160

161
def reset_device():
162 163 164
  
  global DEVICE_CONNECTED
  
165
  result = False
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
166
  
167 168
  sleep(0.5)
  
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
169
  for i in range(RESET_LIMIT):
170
    if not connection_errors():        
171 172
      log_msg("connection error ("+str(i)+"), resetting device",4)
      sata.reset_ie()
173 174
      sata.reset_device()
      sleep(0.5)
175 176 177 178
    else:
      if i!=0: 
        log_msg("resetting device: success")
      result = True
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
    
    if i==0:
      DEVICE_CONNECTED = False
    
    if result:
      break
  
  # load driver in any case
  load_driver()
  return result

def load_ahci_elphel_driver():
  
  shout("modprobe ahci_elphel &")
  shout("sleep 2")
  shout("echo 1 > /sys/devices/soc0/amba@0/80000000.elphel-ahci/load_module")
  log_msg("AHCI driver loaded")
  

def unload_ahci_elphel_driver():
  
  for i in range(DRIVER_UNLOAD_TRIES):
    unmount_partitions()
    try:
      output = subprocess.check_output(["rmmod","ahci_elphel"],stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError as e:
      output = [x.strip() for x in e.output.split(":")]
      if output[-1]=="Resource temporarily unavailable":
        log_msg("Tried to unload driver "+str(i)+": "+output[-1])
        if i==(DRIVER_UNLOAD_TRIES-1):
          log_msg("AHCI driver unloading timeout")
        sleep(2)
      else:
        log_msg("AHCI driver is not loaded")
        break
    else:
      log_msg("AHCI driver unloaded")
      break

def unmount_partitions():
  
  with open("/proc/mounts") as f:
    content = f.readlines()
  
  content = [x.strip() for x in content]
  content = [x.split(" ")[0] for x in content]
  
  for mounted_device in content:
    m = re.search(r"sd[a-z][0-9]",mounted_device)
    if m:
      mountpoint = "/mnt/"+m.group(0)
      partname = "/dev/"+m.group(0)
      log_msg("Unmounting "+partname)
      shout("umount "+mountpoint)

def load_driver():

  for i in range(DRIVER_RELOAD_LIMIT):
    log_msg("Loading SATA driver ("+str(i)+")")
    result = reload_driver()
    if result:
      break
    
  if not result:
    log_msg("SATA failed, SSD was not detected: reconnect SSD",2)
    shout("echo 0 > "+STATEFILE)
  else:
    if not DEVICE_CONNECTED:
      log_msg("SSD was not detected, ahci_elphel driver is loaded",4)
    else:
      log_msg("SATA ok, SSD detected after "+str(i)+" tries")
      #automount()
    shout("echo 1 > "+STATEFILE)
  

#def automount():
  #output = subprocess.check_output("blkid")
  #output = output.split("\n")
  
  #for line in output:
    #pars = line.split(" ")
    #m = re.search(r"sd[a-z][0-9]",pars[0])
    #if m:
      #pname = m.group(0)
      #m = re.search(r"TYPE=\"ext",pars[2])
      #if m:
        #mount_partition(pname)

#def mount_partition(dirname):
  #mountpoint = "/mnt/"+dirname
  #partname = "/dev/"+dirname
  
  #if not os.path.exists(mountpoint):
    #shout("mkdir "+mountpoint)
    #shout("mount "+partname+" "+mountpoint)

def check_device():
  with open("/proc/partitions") as f:
    content = f.readlines()
  
  content = [x.strip() for x in content]
  content = [x.split(" ")[-1] for x in content]
  
  result = False
  
  for device in content:
    m = re.search(r"sd[a-z]",device)
    if m:
      result = True
288
      break
289
  
290 291
  return result

292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
def reload_driver():

  unload_ahci_elphel_driver()
  # check once
  sata.reset_ie()
  sata.reset_device()
  sleep(0.5)
  connection_errors()
  
  load_ahci_elphel_driver()
  
  if DEVICE_CONNECTED:
    sleep(DRIVER_WAIT_TIME)
    result = check_device()
    
    # one more try
    if not result:
      log_msg(colorize("SSD was not detected: waiting for another "+str(DRIVER_WAIT_TIME)+" seconds",'YELLOW',True))
      sleep(DRIVER_WAIT_TIME)  
      result = check_device()
  else:
    result = True
  
  return result

def unmount_unload_disconnect():
  unload_ahci_elphel_driver()
  sata.vsc3304.disconnect_all()  
320

321
mem = x393_mem.X393Mem(0,0,1)
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
322 323 324 325 326 327 328
sata = x393sata.x393sata() # 1,0,"10389B")

if len(sys.argv) > 1:
  cmd = sys.argv[1]
else:
  cmd = "donothing"

329

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
330
if   cmd == "set_zynq_ssd":
331
  unmount_unload_disconnect()
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
332
  sata.set_zynq_ssd()
333
  reset_device()
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
334
elif cmd == "set_zynq_esata":
335
  unmount_unload_disconnect()
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
336
  sata.set_zynq_esata()
337
  reset_device()
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
338
elif cmd == "set_zynq_ssata":
339
  unmount_unload_disconnect()
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
340
  sata.set_zynq_ssata()
341
  reset_device()
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
342
elif cmd == "set_esata_ssd":
343
  unmount_unload_disconnect()
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
344
  sata.set_esata_ssd()
345
  # no reset because PC
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
346 347
else:
  print("Usage:")
348 349 350 351
  print("    * camera <-> internal SSD  :            x393sata_eyesis4pi_control.py set_zynq_ssd")
  print("    * camera <-> external disk :            x393sata_eyesis4pi_control.py set_zynq_esata")
  print("    * camera <-> external disk (crossover): x393sata_eyesis4pi_control.py set_zynq_ssata")
  print("    * PC <-> internal SSD)     :            x393sata_eyesis4pi_control.py set_esata_ssd")