#!/bin/env python3
import io
import mmap

GPIO_INPUT_VAL      = 0x00 // 4
GPIO_INPUT_EN       = 0x04 // 4
GPIO_OUTPUT_EN      = 0x08 // 4
GPIO_OUTPUT_VAL     = 0x0c // 4
GPIO_PULL_UP_ENABLE = 0x10 // 4
GPIO_DRIVE_STRENGTH = 0x14 // 4
GPIO_RISE_IE        = 0x18 // 4
GPIO_RISE_IP        = 0x1c // 4
GPIO_FALL_IE        = 0x20 // 4
GPIO_FALL_IP        = 0x24 // 4
GPIO_HIGH_IE        = 0x28 // 4
GPIO_HIGH_IP        = 0x2c // 4
GPIO_LOW_IE         = 0x30 // 4
GPIO_LOW_IP         = 0x34 // 4
GPIO_IOF_EN         = 0x38 // 4
GPIO_IOF_SEL        = 0x3c // 4
GPIO_OUT_XOR        = 0x40 // 4

PLIC_SOURCE_PRIORITY_BASE = 0x00_0000 // 4 # + source index, 0-based
PLIC_PENDING_BASE         = 0x00_1000 // 4 # 3-words (12 bytes) bit-field
PLIC_HART1_S_IE_BASE      = 0x00_2100 // 4 # 3-words (12 bytes) bit-field
PLIC_HART2_S_IE_BASE      = 0x00_2200 // 4 # 3-words (12 bytes) bit-field
PLIC_HART3_S_IE_BASE      = 0x00_2300 // 4 # 3-words (12 bytes) bit-field
PLIC_HART4_S_IE_BASE      = 0x00_2400 // 4 # 3-words (12 bytes) bit-field
PLIC_HART1_S_PRIORITY     = 0x20_2000 // 4
PLIC_HART1_S_COMPLETE     = 0x20_2008 // 4
PLIC_HART2_S_PRIORITY     = 0x20_4000 // 4
PLIC_HART2_S_COMPLETE     = 0x20_4008 // 4
PLIC_HART3_S_PRIORITY     = 0x20_6000 // 4
PLIC_HART3_S_COMPLETE     = 0x20_6008 // 4
PLIC_HART4_S_PRIORITY     = 0x20_8000 // 4
PLIC_HART4_S_COMPLETE     = 0x20_8008 // 4

PLIC_IRQ_GPIO_BASE = 23

def get3wBitField(plic, base):
  return (
    (plic[base + 0] <<  0) |
    (plic[base + 1] << 32) |
    (plic[base + 2] << 64)
  )

def uint32le_memoryview(value):
  return memoryview(value).cast('I')

def main(argv):
  gpio_pin = int(argv[1])
  assert 0 <= gpio_pin <= 15
  gpio_mask = 1 << gpio_pin
  plic_irq = PLIC_IRQ_GPIO_BASE + gpio_pin
  plic_mask = 1 << plic_irq
  print('gpio_pin=%i gpio_mask=%s plic_irq=%i plic_mask=%s' % (
    gpio_pin,
    bin(gpio_mask),
    plic_irq,
    bin(plic_mask),
  ))
  # Sanity check: must be on the correct board
  with open('/sys/firmware/devicetree/base/compatible', 'rb') as board_compatible:
    compatible = board_compatible.read().strip()
    assert compatible == b'sifive,hifive-unmatched-a00\x00sifive,fu740-c000\x00sifive,fu740\x00', repr(compatible)
  with io.open('/dev/mem', mode='rb', buffering=0) as dev_mem:
    dev_mem_fileno = dev_mem.fileno()
    with (
      mmap.mmap(dev_mem_fileno, length=0x30_0000, offset=0x0c00_0000, prot=mmap.PROT_READ) as plic,
      mmap.mmap(dev_mem_fileno, length=0x00_0044, offset=0x1006_0000, prot=mmap.PROT_READ) as gpio
    ):
      plic = uint32le_memoryview(plic)
      gpio = uint32le_memoryview(gpio)
      try:
        print('GPIO %i: dir=%s in=%i out=%i irq_en=%s irq_pending=%s' % (
          gpio_pin,
          {
            (False, False): 'off',
            (False,  True): 'out',
            (True,  False): 'in ',
          }[(
            bool(gpio[GPIO_INPUT_EN] & gpio_mask),
            bool(gpio[GPIO_OUTPUT_EN] & gpio_mask),
          )],
          bool(gpio[GPIO_INPUT_VAL] & gpio_mask),
          bool(gpio[GPIO_OUTPUT_VAL] & gpio_mask),
          ','.join(
            x
            for x, y in (
              ('rise', GPIO_RISE_IE),
              ('fall', GPIO_FALL_IE),
              ('high', GPIO_HIGH_IE),
              ('low',  GPIO_LOW_IE),
            )
            if gpio[y] & gpio_mask
          ),
          ','.join(
            x
            for x, y in (
              ('rise', GPIO_RISE_IP),
              ('fall', GPIO_FALL_IP),
              ('high', GPIO_HIGH_IP),
              ('low',  GPIO_LOW_IP),
            )
            if gpio[y] & gpio_mask
          ),
        ))
        print('PLIC %i: priority=%i irq_en=%s irq_pending=%s' % (
          plic_irq,
          plic[PLIC_SOURCE_PRIORITY_BASE + plic_irq],
          ', '.join(
            x
            for x, y in (
              ('hart1', PLIC_HART1_S_IE_BASE),
              ('hart2', PLIC_HART2_S_IE_BASE),
              ('hart3', PLIC_HART3_S_IE_BASE),
              ('hart4', PLIC_HART4_S_IE_BASE),
            )
            if get3wBitField(plic, y) & plic_mask
          ),
          bool(get3wBitField(plic, PLIC_PENDING_BASE) & plic_mask),
        ))
        print('hart min prio: %s' % (
          ', '.join(
            '%i=%s' % (x, plic[y])
            for x, y in enumerate(
              (
                PLIC_HART1_S_PRIORITY,
                PLIC_HART2_S_PRIORITY,
                PLIC_HART3_S_PRIORITY,
                PLIC_HART4_S_PRIORITY,
              ),
              1,
            )
          )
        ))
      finally:
        del gpio, plic

if __name__ == '__main__':
  import sys
  main(sys.argv)
