#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# Copyright 2018, Data61
# Commonwealth Scientific and Industrial Research Organisation (CSIRO)
# ABN 41 687 119 230.
#
# This software may be distributed and modified according to the terms of
# the BSD 2-Clause license. Note that NO WARRANTY is provided.
# See "LICENSE_BSD2.txt" for details.
#
# @TAG(DATA61_BSD)
#

from __future__ import absolute_import, division, print_function, unicode_literals

from camkes.parser import ParseError, DtbMatchQuery
from camkes.internal.tests.utils import CAmkESTest
import os
import sys
import unittest

ME = os.path.abspath(__file__)

# Make CAmkES importable
sys.path.append(os.path.join(os.path.dirname(ME), '../../..'))


class TestDTBMatchQuery(CAmkESTest):
    '''
    As part of the changes brought by the new seL4DTBHardware connector,
    the DTB dictionary now includes keys 'this-address-cells' and 'this-size-cells'.
    The keys describe the number of cells required to express those particular fields in
    the 'reg' property of the DTB.

    As part of the changes brought by exposing the DTB to CAmkES userland, the DTB dictionary
    now includes a 'dtb_size' key. This key-value pair describes the size of the DTB, in bytes.
    '''

    def setUp(self):
        super(TestDTBMatchQuery, self).setUp()
        self.dtbQuery = DtbMatchQuery()
        self.dtbQuery.parse_args(['--dtb', os.path.join(os.path.dirname(ME), "test.dtb")])
        self.dtbQuery.check_options()
        self.dtbSize = os.path.getsize(os.path.join(os.path.dirname(ME), "test.dtb"))

    def test_aliases(self):
        node = self.dtbQuery.resolve([{'aliases': 'usbphy0'}])
        self.assertIsInstance(node, dict)

        expected = {
            'compatible': ["fsl,imx6q-usbphy", "fsl,imx23-usbphy"],
            'reg': [0x20c9000, 0x1000],
            'interrupts': [0x0, 0x2c, 0x4],
            'clocks': [0x4, 0xb6],
            'fsl,anatop': [0x2],
            'phandle': [0x2c],
            'this-address-cells': [0x1],
            'this-size-cells': [0x1],
            'this-node-path': "/soc/aips-bus@2000000/usbphy@20c9000",
        }
        self.assertIn('query', node)
        self.assertIn('dtb-size', node)
        self.assertEquals(len(node['query']), 1)
        self.assertEquals(node['query'][0], expected)
        self.assertEquals(node['dtb-size'], [self.dtbSize])

        node = self.dtbQuery.resolve([{'aliases': 'spi4'}])
        expected = {
            '#address-cells': [0x1],
            '#size-cells': [0x0],
            'compatible': ["fsl,imx6q-ecspi", "fsl,imx51-ecspi"],
            'reg': [0x2018000, 0x4000],
            'interrupts': [0x0, 0x23, 0x4],
            'clocks': [0x4, 0x74, 0x4, 0x74],
            'clock-names': ["ipg", "per"],
            'dmas': [0x17, 0xb, 0x8, 0x1, 0x17, 0xc, 0x8, 0x2],
            'dma-names': ["rx", "tx"],
            'status': ["disabled"],
            'this-address-cells': [0x1],
            'this-size-cells': [0x1],
            'this-node-path': "/soc/aips-bus@2000000/spba-bus@2000000/ecspi@2018000",
        }

        self.assertIn('query', node)
        self.assertEquals(node['query'][0], expected)
        self.assertEquals(len(node['query']), 1)
        self.assertEquals(node['dtb-size'], [self.dtbSize])

    def test_paths(self):
        node = self.dtbQuery.resolve([{'path': "temp"}])
        expected = {
            'compatible': ["fsl,imx6q-tempmon"],
            'interrupt-parent': [0x1],
            'interrupts': [0x0, 0x31, 0x4],
            'fsl,tempmon': [0x2],
            'fsl,tempmon-data': [0x3],
            'clocks': [0x4, 0xac],
            'this-address-cells': [0x1],
            'this-size-cells': [0x1],
            'this-node-path': "/tempmon",
        }
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        self.assertEquals(node['query'][0], expected)

        node = self.dtbQuery.resolve([{'path': '.*serial.*'}])
        expected = {
            'compatible': ["fsl,imx6q-uart", "fsl,imx21-uart"],
            'reg': [0x2020000, 0x4000],
            'interrupts': [0x0, 0x1a, 0x4],
            'clocks': [0x4, 0xa0, 0x4, 0xa1],
            'clock-names': ["ipg", "per"],
            'dmas': [0x17, 0x19, 0x4, 0x0, 0x17, 0x1a, 0x4, 0x0],
            'dma-names': ["rx", "tx"],
            'status': ["okay"],
            'pinctrl-names': ["default"],
            'pinctrl-0': [0x1a],
            'this-address-cells': [0x1],
            'this-size-cells': [0x1],
            'this-node-path': "/soc/aips-bus@2000000/spba-bus@2000000/serial@2020000",
        }
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        self.assertEquals(node['query'][0], expected)
        self.assertEquals(node['dtb-size'], [self.dtbSize])

        node = self.dtbQuery.resolve([{'path': '.*sgtl5000.*'}])
        expected = {
            'compatible': ["fsl,sgtl5000"],
            'reg': [0x0a],
            'clocks': [0x04, 0xc9],
            'VDDA-supply': [0x39],
            'VDDIO-supply': [0x35],
            'phandle': [0x78],
            'this-address-cells': [0x01],
            'this-size-cells': [0x00],
            'this-node-path': "/soc/aips-bus@2100000/i2c@21a0000/sgtl5000@a",
        }
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        self.assertEquals(node['query'][0], expected)
        self.assertEquals(node['dtb-size'], [self.dtbSize])

    def test_multiple_queries(self):
        node = self.dtbQuery.resolve([{'path': "temp"}, {'aliases': 'spi4'}])
        expected = [
            {
                'compatible': ["fsl,imx6q-tempmon"],
                'interrupt-parent': [0x1],
                'interrupts': [0x0, 0x31, 0x4],
                'fsl,tempmon': [0x2],
                'fsl,tempmon-data': [0x3],
                'clocks': [0x4, 0xac],
                'this-address-cells': [0x1],
                'this-size-cells': [0x1],
                'this-node-path': "/tempmon",
            },
            {
                '#address-cells': [0x1],
                '#size-cells': [0x0],
                'compatible': ["fsl,imx6q-ecspi", "fsl,imx51-ecspi"],
                'reg': [0x2018000, 0x4000],
                'interrupts': [0x0, 0x23, 0x4],
                'clocks': [0x4, 0x74, 0x4, 0x74],
                'clock-names': ["ipg", "per"],
                'dmas': [0x17, 0xb, 0x8, 0x1, 0x17, 0xc, 0x8, 0x2],
                'dma-names': ["rx", "tx"],
                'status': ["disabled"],
                'this-address-cells': [0x1],
                'this-size-cells': [0x1],
                'this-node-path': "/soc/aips-bus@2000000/spba-bus@2000000/ecspi@2018000",
            }
        ]
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 2)
        self.assertEquals(node['query'][0], expected[0])
        self.assertEquals(node['query'][1], expected[1])

        node = self.dtbQuery.resolve(
            [{'path': '.*sgtl5000.*'}, {'properties': {'reg[0]': 0x2020000}}])
        expected = [
            {
                'compatible': ["fsl,sgtl5000"],
                'reg': [0x0a],
                'clocks': [0x04, 0xc9],
                'VDDA-supply': [0x39],
                'VDDIO-supply': [0x35],
                'phandle': [0x78],
                'this-address-cells': [0x01],
                'this-size-cells': [0x00],
                'this-node-path': "/soc/aips-bus@2100000/i2c@21a0000/sgtl5000@a",
            },
            {
                'dma-names': ['rx', 'tx'],
                'status': ['okay'],
                'clock-names': ['ipg', 'per'],
                'interrupts': [0, 26, 4],
                'pinctrl-names': ['default'],
                'compatible': ['fsl,imx6q-uart', 'fsl,imx21-uart'],
                'dmas': [23, 25, 4, 0, 23, 26, 4, 0],
                'clocks': [4, 160, 4, 161],
                'pinctrl-0': [26],
                'reg': [33685504, 16384],
                'this-address-cells': [0x1],
                'this-size-cells': [0x1],
                'this-node-path': "/soc/aips-bus@2000000/spba-bus@2000000/serial@2020000",
            }
        ]
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 2)
        self.assertEquals(node['query'][0], expected[0])
        self.assertEquals(node['query'][1], expected[1])
        self.assertEquals(node['dtb-size'], [self.dtbSize])

    def test_blank(self):
        self.assertRaises(ParseError, self.dtbQuery.resolve, [])

    def test_blank_query(self):
        node = self.dtbQuery.resolve([{}])
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        self.assertEquals(node['query'][0], {})

    def test_properties_lvalue_index(self):
        node = self.dtbQuery.resolve([{'properties': {'reg[0]': 0x2020000}}])
        expected = {
            'dma-names': ['rx', 'tx'],
            'status': ['okay'],
            'clock-names': ['ipg', 'per'],
            'interrupts': [0, 26, 4],
            'pinctrl-names': ['default'],
            'compatible': ['fsl,imx6q-uart', 'fsl,imx21-uart'],
            'dmas': [23, 25, 4, 0, 23, 26, 4, 0],
            'clocks': [4, 160, 4, 161],
            'pinctrl-0': [26],
            'reg': [33685504, 16384],
            'this-address-cells': [0x1],
            'this-size-cells': [0x1],
            'this-node-path': "/soc/aips-bus@2000000/spba-bus@2000000/serial@2020000",
        }
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        self.assertEquals(node['query'][0], expected)
        self.assertEquals(node['dtb-size'], [self.dtbSize])

        node = self.dtbQuery.resolve([{'properties': {'compatible[0]': 'fsl,imx6q-pwm'}}])
        expected = {
            'status': ['okay'],
            'pinctrl-0': [29],
            'clock-names': ['ipg', 'per'],
            'interrupts': [0, 83, 4],
            '#pwm-cells': [2],
            'pinctrl-names': ['default'],
            'compatible': ['fsl,imx6q-pwm', 'fsl,imx27-pwm'],
            'clocks': [4, 62, 4, 145],
            'phandle': [121],
            'reg': [34078720, 16384],
            'this-address-cells': [0x1],
            'this-size-cells': [0x1],
            'this-node-path': "/soc/aips-bus@2000000/pwm@2080000",
        }
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        self.assertEquals(node['query'][0], expected)
        self.assertEquals(node['dtb-size'], [self.dtbSize])

        node = self.dtbQuery.resolve([{'properties': {'compatible[0]': 'fsl,sec-v4.0-mon-rtc-lp'}}])
        expected = {
            'compatible': ['fsl,sec-v4.0-mon-rtc-lp'],
            'regmap': [0x23],
            'offset': [0x34],
            'interrupts': [0x00, 0x13, 0x04, 0x00, 0x14, 0x04],
            'this-address-cells': [0x02],
            'this-size-cells': [0x01],
            'this-node-path': "/soc/aips-bus@2000000/snvs@20cc000/snvs-rtc-lp",
        }
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        self.assertEquals(node['query'][0], expected)
        self.assertEquals(node['dtb-size'], [self.dtbSize])

    def test_properties_star_string(self):
        node = self.dtbQuery.resolve([{
            'properties':  {
                "clock-names[*]": ["di0_pll", "di1_pll", "di0_sel", "di1_sel", "di2_sel", "di3_sel", "di0", "di1"]
            }
        }])
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        query = node['query'][0]

        self.assertIn('#address-cells', query)
        self.assertEquals(query['#address-cells'], [0x1])

        self.assertIn('#size-cells', query)
        self.assertEquals(query['#size-cells'], [0x0])

        self.assertIn('compatible', query)
        self.assertEquals(query['compatible'], ["fsl,imx6q-ldb", "fsl,imx53-ldb"])

        self.assertIn('gpr', query)
        self.assertEquals(query['gpr'], [0x5])

        self.assertIn('status', query)
        self.assertEquals(query['status'], ["okay"])

        self.assertIn('clocks', query)
        self.assertEquals(query['clocks'], [0x4, 0x21, 0x4, 0x22, 0x4, 0x27, 0x4, 0x28, 0x4, 0x29, 0x4, 0x2a, 0x4, 0x87,
                                            0x4, 0x88])

        self.assertIn('clock-names', query)
        self.assertEquals(query['clock-names'], ["di0_pll", "di1_pll", "di0_sel", "di1_sel", "di2_sel", "di3_sel",
                                                 "di0", "di1"])

        self.assertIn('this-address-cells', query)
        self.assertEquals(query['this-address-cells'], [0x1])

        self.assertIn('this-size-cells', query)
        self.assertEquals(query['this-size-cells'], [0x1])

        self.assertIn('dtb-size', node)
        self.assertEquals(node['dtb-size'], [self.dtbSize])

        node = self.dtbQuery.resolve([{
            'properties': {
                "interrupt-names[*]": ["gpmi0", "gpmi1", "gpmi2", "gpmi3"]
            }
        }])
        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        query = node['query'][0]

        expected = {
            'compatible': ["fsl,imx6q-dma-apbh", "fsl,imx28-dma-apbh"],
            'reg': [0x110000, 0x2000],
            'interrupts': [0x0, 0xd, 0x4, 0x0, 0xd, 0x4, 0x0, 0xd, 0x4, 0x0, 0xd, 0x4],
            'interrupt-names': ["gpmi0", "gpmi1", "gpmi2", "gpmi3"],
            '#dma-cells': [0x1],
            'dma-channels': [0x4],
            'clocks': [0x4, 0x6a],
            'phandle': [0xf],
            'this-address-cells': [0x1],
            'this-size-cells': [0x1],
            'this-node-path': "/soc/dma-apbh@110000",
        }
        self.assertEquals(query, expected)
        self.assertIn('dtb-size', node)
        self.assertEquals(node['dtb-size'], [self.dtbSize])

        node = self.dtbQuery.resolve([{
            'properties': {
                "compatible[*]": ["fsl,imx6q-pcie", "snps,dw-pcie"]
            }
        }])

        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        query = node['query'][0]
        expected = {
            'compatible': ["fsl,imx6q-pcie", "snps,dw-pcie"],
            'reg': [0x1ffc000, 0x4000, 0x1f00000, 0x80000],
            'reg-names': ["dbi", "config"],
            '#address-cells': [0x3],
            '#size-cells': [0x2],
            'device_type': ["pci"],
            'bus-range': [0x0, 0xff],
            'ranges': [0x81000000, 0x0, 0x0, 0x1f80000, 0x0, 0x10000, 0x82000000, 0x0, 0x1000000, 0x1000000, 0x0,
                       0xf00000],
            'num-lanes': [0x1],
            'interrupts': [0x0, 0x78, 0x4],
            'interrupt-names': ["msi"],
            '#interrupt-cells': [0x1],
            'interrupt-map-mask': [0x0, 0x0, 0x0, 0x7],
            'interrupt-map': [0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x7b, 0x4, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x7a, 0x4, 0x0,
                              0x0, 0x0, 0x3, 0x1, 0x0, 0x79, 0x4, 0x0, 0x0, 0x0, 0x4, 0x1, 0x0, 0x78, 0x4],
            'clocks': [0x4, 0x90, 0x4, 0xce, 0x4, 0xbd],
            'clock-names': ["pcie", "pcie_bus", "pcie_phy"],
            'status': ["okay"],
            'this-address-cells': [0x1],
            'this-size-cells': [0x1],
            'this-node-path': "/soc/pcie@1ffc000",
        }
        self.assertEquals(query, expected)
        self.assertIn('dtb-size', node)
        self.assertEquals(node['dtb-size'], [self.dtbSize])

    def test_properties_star_word_and_byte(self):
        node = self.dtbQuery.resolve([{
            'properties': {
                "clocks[*]": [0x4, 0x98, 0x4, 0x99, 0x4, 0x97, 0x4, 0x96, 0x4, 0x95]
            }
        }])

        self.assertIn('query', node)
        self.assertEquals(len(node['query']), 1)
        query = node['query'][0]
        expected = {
            'compatible': ["fsl,imx6q-gpmi-nand"],
            '#address-cells': [0x1],
            '#size-cells': [0x1],
            'reg': [0x112000, 0x2000, 0x114000, 0x2000],
            'reg-names': ["gpmi-nand", "bch"],
            'interrupts': [0x0, 0xf, 0x4],
            'interrupt-names': ["bch"],
            'clocks': [0x4, 0x98, 0x4, 0x99, 0x4, 0x97, 0x4, 0x96, 0x4, 0x95],
            'clock-names': ["gpmi_io", "gpmi_apb", "gpmi_bch", "gpmi_bch_apb", "per1_bch"],
            'dmas': [0xf, 0x0],
            'dma-names': ["rx-tx"],
            'status': ["disabled"],
            'this-address-cells': [0x1],
            'this-size-cells': [0x1],
            'this-node-path': "/soc/gpmi-nand@112000",
        }
        self.assertEquals(query, expected)
        self.assertIn('dtb-size', node)
        self.assertEquals(node['dtb-size'], [self.dtbSize])


if __name__ == '__main__':
    unittest.main()
