#!/usr/bin/python

# Python library for in-band SCom access
# (based on xscom-utils from OPAL firmware)
#
# Copyright 2018 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#	http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os, sys, struct, getopt

class XSCom(object):
	def __init__(self):
		self.name = "xscom"
		self.base = "/sys/kernel/debug/powerpc/scom/"
		self.enabled = False
		self.setup = False
		self.chips = []
		self.dirs = []
		self.key_val_bin = {}
		self.file = "/access"

		if os.path.exists(self.base):
			self.enabled = True

		if not self.scan_chips():
			raise ValueError

	def scan_chips(self):
		if not self.enabled:
			print("Not supported")
			return False

		for i in os.listdir(self.base):
			if os.path.isdir(self.base+i):
				self.dirs.append(i)
				self.chips.append(int(i,16))

		for i in  self.dirs:
			try:
				b = open(self.base+i+self.file, "rb+")
				self.key_val_bin[int(i,16)] = b
			except:
				print("Count not open"+self.base+i+self.file)
				return False

		self.setup = True
		return True

	def is_supported(self):
		return self.enabled

	def get_chip_ids(self):
		return list(self.key_val_bin.keys())

	def mangle_addr(self, addr):
		tmp = (addr & 0xf000000000000000) >> 4
		addr = (addr & 0x00ffffffffffffff)
		addr = addr | tmp
		return (addr << 3)

	def xscom_read(self, chip_id, addr):
		if not isinstance(chip_id, int) or not isinstance(addr, int):
			print("xscom_read: Input paramater type mismatch")
			return -1

		if chip_id not in self.key_val_bin:
			print("Invalid Chip id")
			return -1

		saddr = self.mangle_addr(addr)
		fd = self.key_val_bin.get(chip_id)
		fd.seek(saddr, 0)
		return struct.unpack('Q',fd.read(8))[0]

	def xscom_read_spl(self, chip_id, addr):
		if not isinstance(chip_id, int) or not isinstance(addr, int):
			print("xscom_read: Input paramater type mismatch")
			return -1

		if chip_id not in self.key_val_bin:
			print("Invalid Chip id")
			return -1

		saddr = self.mangle_addr(addr)
		fd = self.key_val_bin.get(chip_id)
		fd.seek(saddr, 0)
		val = struct.unpack('Q',fd.read(8))[0]
		fd.close()
		try:
			b = open(self.key_val_path.get(chip_id), "rb+")
		except:
			print("Reopen failed")
			return val
		self.key_val_bin[chip_id] = b
		return val

	def xscom_write(self, chip_id, addr, val):
		if chip_id not in self.key_val_bin:
			print("Invalid Chip id")
			return -1

		c = struct.pack('Q',val)
		saddr = self.mangle_addr(addr)
		fd = self.key_val_bin.get(chip_id)

		try:
			fd.seek(saddr, 0)
			fd.write(c)
			# write again just to be sure
			fd.seek(saddr, 0)
			fd.write(c)
		except:
			print("Write() error")
			return -1

	def xscom_read_ex(self, ex_target_id, addr):
		if not isinstance(ex_target_id, int) or not isinstance(addr, int):
			print("xscom_read_ex: Input paramater type mismatch")
			return -1

		chip_id = ex_target_id >> 4
		addr |= (ex_target_id & 0xf) << 24;
		return self.xscom_read(chip_id, addr, val);

	def xscom_write_ex(self, ex_target_id, addr, val):
		chip_id = ex_target_id >> 4
		addr |= (ex_target_id & 0xf) << 24;
		return self.xscom_write(chip_id, addr, val)

class GetSCom(object):
	def __init__(self):
		self.name = "getscom"
		self.backend = XSCom()
		self.listchip = False
		self.chip_id = 0
		self.chips = False
		self.addr = 0
		self.flg_addr = False

		if not self.backend.is_supported():
			print("In-Band SCom not supported Exiting....")
			raise ValueError

	def set_chip(self, chip_id):
		self.chip_id = chip_id
		self.chips = True

	def set_addr(self, scom_addr):
		self.addr = scom_addr
		self.flg_addr = True

	def print_usage(self):
		print("usage: getscom [-c|--chip chip-id] addr")
		print("       getscom -l|--list-chips")
		print("       getscom -h|--help")
		sys.exit(0)


	def chip_info(self, chip_id):
		val = self.backend.xscom_read(chip_id, 0xf000f)
		if val < 0:
			print("Error in scom read")
			raise ValueError

		c_id = val >> 44
		id = c_id & 0xff
		if id == 0xf9:
			name = "P7 processor"
		elif id == 0xe8:
			name = "P7+ processor"
		elif id == 0xef:
			name = "P8E (Murano) processor"
		elif id == 0xea:
			name = "P8 (Venice) processor"
		elif id == 0xd3:
			name = "P8NVL (Naples) processor"
		elif id == 0xd1:
			name = "P9 (Nimbus) processor"
		elif id == 0xd4:
			name = "P9 (Cumulus) processor"
		elif id == 0xe9:
			name = "Centaur memory buffer"
		else:
			name = "Unknown ID 0x%x"%id

		print(("%08x | DD%s.%s | %s"%(chip_id, ((c_id >> 16) & 0xf), ((c_id >> 8) & 0xf), name)))

	def parse_args(self):
		try:
			optlist, sys.argv = getopt.getopt(sys.argv[1:], "lhc:", ["chip", "list-chips", "help"])
		except getopt.GetoptError as err:
			print(str(err))
			self.print_usage()
			sys.exit(0)

		if len(optlist) == 0:
			self.print_usage()
			sys.exit(0)

		for opt, arg in optlist:
			if opt in [ "-h", "--help"]:
				self.print_usage()
				sys.exit(0)

			elif opt in [ "-l", "--list-chips"]:
				self.listchip = True

			elif opt in ["-c", "--chip"]:
				self.chip_id = int(arg, 16)
				self.chips = True

		if sys.argv:
			self.addr =  int(sys.argv.pop(), 16)
			self.flg_addr = True

		if self.listchip:
			print("Chip ID  | Rev   | Chip type")
			print("---------|-------|-----------")
			for i in self.backend.get_chip_ids():
				self.chip_info(i)

			sys.exit(0)

	def run_command(self):
		if self.chips and self.flg_addr:
			print(hex(self.backend.xscom_read(self.chip_id, self.addr)))

	def list_chips(self):
		print("Chip ID  | Rev   | Chip type")
		print("---------|-------|-----------")
		for i in self.backend.get_chip_ids():
			self.chip_info(i)

		raise ValueError

	def execute(self, chip_id, addr):
		return self.backend.xscom_read(chip_id, addr)

	def execute_spl(self, chip_id, addr):
		return self.backend.xscom_read_spl(chip_id, addr)

class PutSCom(object):
	def __init__(self):
		self.name = "putscom"
		self.backend = XSCom()
		self.chip_id = 0
		self.chips = False
		self.addr = 0
		self.value = 0

		if not self.backend.is_supported():
			print("In-Band SCom not supported Exiting....")
			raise ValueError

	def set_addr(self, addr):
		self.addr = addr

	def set_value(self, value):
		self.value = value

	def print_usage(self):
		print("usage: putscom [-c|--chip chip-id] addr value")
		print("       putscom -h|--help")
		sys.exit(0)

	def parse_args(self):
		try:
			optlist, sys.argv = getopt.getopt(sys.argv[1:], "hc:", ["chip", "help"])
		except getopt.GetoptError as err:
			print(str(err))
			self.print_usage()
			sys.exit(0)

		if len(optlist) == 0:
			self.print_usage()
			sys.exit(0)

		for opt, arg in optlist:
			if opt in [ "-h", "--help"]:
				self.print_usage()
				sys.exit(0)

			elif opt in ["-c", "--chip"]:
				self.chip_id = int(arg, 16)
				self.chips = True

		if sys.argv:
			self.value =  int(sys.argv.pop(), 16)
			self.addr =  int(sys.argv.pop(), 16)

		if self.chips:
			self.backend.xscom_write(self.chip_id, self.addr, self.value)

	def run_command(self):
		if self.chips:
			self.backend.xscom_write(self.chip_id, self.addr, self.value)

	def execute(self, chip_id, addr, value):
		self.backend.xscom_write(chip_id, addr, value)

