1# Copyright (c) 2021, Linaro Limited.
2# Copyright (c) 2022, NXP
3#
4# SPDX-License-Identifier: Apache-2.0
5
6"""
7Implements a configuration file parser for kinetis MCUs, which can generate
8pinctrl definitions for Zephyr
9"""
10
11import xml.etree.ElementTree as ET
12import re
13import os
14import collections
15import logging
16import pathlib
17import __main__
18
19# layout/index of pins tuple
20PIN = collections.namedtuple('PIN', ['PERIPH', 'NAME_PART', 'SIGNAL', 'PORT',
21                                     'PIN', 'CH', 'MUX_FUNC'])
22
23NAMESPACES = {'mex': 'http://mcuxpresso.nxp.com/XSD/mex_configuration_14'}
24
25# Pin controller types
26PORT_KINETIS = 1
27PORT_N9X = 2
28PORT_A15X = 3
29
30class MUXOption:
31    """
32    Internal class representing a mux option on the SOC
33    """
34    def __init__(self, connection, port_type):
35        """
36        Initializes a mux option
37        @param connection XML connection option from signal_configuration.xml
38        """
39        self._name = connection.attrib.get('name_part')
40        logging.debug("\t\t %s", self._name)
41        if self._name is None:
42            self._name = ''
43            return
44        # Get MUX settings
45        self._port = None
46        for periph in connection.iter('peripheral_signal_ref'):
47            self._periph = periph.attrib.get('peripheral')
48            self._signal = periph.attrib.get('signal')
49            self._channel = periph.attrib.get('channel')
50        for assign in connection.iter('assign'):
51            reg = assign.attrib.get('register')
52            val = assign.attrib.get('bit_field_value')
53            logging.debug('\t\t\t [ASSIGN] %s %s', reg, val)
54            # Only process PCR registers
55            if port_type == PORT_KINETIS:
56                match = re.match(r'PORT([A-Z])_PCR(\d+)', reg)
57            elif port_type == PORT_N9X or port_type == PORT_A15X:
58                match = re.match(r'PORT(\d)_PCR(\d+)', reg)
59            if match and (assign.attrib.get('bit_field') == "MUX"):
60                # For muxes like PTC5 (or PIO1_8 on N9X),
61                # do not append peripheral name
62                if port_type == PORT_KINETIS:
63                    if re.match(r'PT[A-Z]\d+', self._name) is None:
64                        self._name += f"_PT{match.group(1)}{match.group(2)}"
65                elif port_type == PORT_N9X:
66                    if re.match(r'PIO\d_\d+', self._name) is None:
67                        self._name += f"_PIO{match.group(1)}_{match.group(2)}"
68                elif port_type == PORT_A15X:
69                    if re.match(r'P\d_\d+', self._name) is None:
70                        self._name += f"_P{match.group(1)}_{match.group(2)}"
71                self._port = match.group(1)
72                self._pin = int(match.group(2))
73                self._mux = int(val, 16)
74        if self._port is None:
75            # Not a valid port mapping. Clear name
76            self._name = ''
77
78    def __repr__(self):
79        """
80        String representation of object
81        """
82        return "MUXOption(%s)" % (self._name)
83
84    def get_name(self):
85        """
86        Get mux option name
87        """
88        return self._name
89
90    def get_mux_name(self):
91        """
92        Get name of the mux option, without pin name
93        """
94        if self._channel:
95            return f"{self._periph}_{self._signal}, {self._channel}"
96        return f"{self._periph}_{self._signal}"
97
98    def get_port(self):
99        """
100        Get mux port
101        """
102        return self._port
103
104    def get_signal(self):
105        """
106        Get mux signal name
107        """
108        return self._signal
109
110    def get_pin(self):
111        """
112        Get mux pin
113        """
114        return self._pin
115
116    def get_mux(self):
117        """
118        Get mux register write value
119        """
120        return self._mux
121
122    def get_periph(self):
123        """
124        Get peripheral name
125        """
126        return self._periph
127
128    def get_channel(self):
129        """
130        Get channel number
131        """
132        return self._channel
133
134    def __hash__(self):
135        """
136        Override hash method to return pin name as hash
137        """
138        return hash(self._name)
139
140    def __eq__(self, obj):
141        """
142        Like the hash method, we override the eq method to return true if two
143        objects have the same pin name
144        """
145        return isinstance(obj, SignalPin) and self._name == obj._name
146
147    def __lt__(self, obj):
148        """
149        Compare objects based on name
150        """
151        if not isinstance(obj, SignalPin):
152            return True
153        return self._name < obj._name
154
155
156class SignalPin:
157    """
158    Internal class representing a signal on the SOC
159    """
160    def __init__(self, pin):
161        """
162        Initializes a SignalPin object
163        @param pin: pin XML object from signal_configuration.xml
164        """
165        # Kinetis pin names are formatted as [PT[Port][Pin]],
166        # N9X pin names use the PIO[Port]_[Pin] format. Try both.
167        if re.search(r'PT([A-Z])(\d+)', pin.attrib['name']):
168            # Kinetis part.
169            pin_regex = re.search(r'PT([A-Z])(\d+)', pin.attrib['name'])
170            self._type = PORT_KINETIS
171        elif re.search(r'PIO(\d)_(\d+)', pin.attrib['name']):
172            # This may be an N9X part. Try that pin pattern
173            pin_regex = re.search(r'PIO(\d)_(\d+)', pin.attrib['name'])
174            self._type = PORT_N9X
175        elif re.search(r'P(\d)_(\d+)', pin.attrib['name']):
176            # This may be an A15X part. Try that pin pattern
177            pin_regex = re.search(r'P(\d)_(\d+)', pin.attrib['name'])
178            self._type = PORT_A15X
179        else:
180            logging.debug('Could not match pin name %s', pin.attrib['name'])
181            self._name = ''
182            return
183        self._name = pin.attrib['name']
184        self._port = pin_regex.group(1)
185        self._pin = pin_regex.group(2)
186        self._properties = self._get_pin_properties(pin.find('functional_properties'))
187        self._mux_options = {}
188        for connections in pin.findall('connections'):
189            mux_opt = MUXOption(connections, self._type)
190            # Only append mux options with a valid name
191            if mux_opt.get_name() != '':
192                self._mux_options[mux_opt.get_mux_name()] = mux_opt
193
194    def __repr__(self):
195        """
196        String representation of object
197        """
198        return "SignalPin(%s)" % (self._name)
199
200    def __hash__(self):
201        """
202        Override hash method to return pin name as hash
203        """
204        return hash(self._name)
205
206    def __eq__(self, obj):
207        """
208        Like the hash method, we override the eq method to return true if two
209        objects have the same pin name
210        """
211        return isinstance(obj, SignalPin) and self._name == obj._name
212
213    def __lt__(self, obj):
214        """
215        Compare objects based on name
216        """
217        if not isinstance(obj, SignalPin):
218            return True
219        return self._name < obj._name
220
221    def get_name(self):
222        """
223        Get name of pin
224        """
225        return self._name
226
227    def get_port(self):
228        """
229        Get PORT this signal is defined for
230        """
231        return self._port
232
233    def get_pin(self):
234        """
235        Get pin this signal is defined for
236        """
237        return self._pin
238
239    def get_mux_connection(self, signal):
240        """
241        Gets an MUXOption object for the relevant signal name
242        @param signal: Signal name on pin to get mux option for
243        """
244        if signal in self._mux_options:
245            return self._mux_options[signal]
246        return None
247
248    def get_mux_options(self):
249        """
250        Gets all unique settings for IOMUX on the specific pin
251        """
252        return set(self._mux_options.values())
253
254    def get_pin_properties(self):
255        """
256        Gets array of pin property names
257        """
258        return self._properties.keys()
259
260    def get_pin_property_default(self, prop):
261        """
262        Gets name of default pin property
263        @param prop: name of pin property
264        """
265        return self._properties[prop]['default']
266
267    def get_pin_defaults(self):
268        """
269        Gets mapping of all pin property names to default value names
270        """
271        pin_defaults = {}
272        for prop in self.get_pin_properties():
273            pin_default = self.get_pin_property_default(prop)
274            pin_defaults[prop] = pin_default
275        return pin_defaults
276
277    def get_pin_property_value(self, prop, selection):
278        """
279        Gets bit value for pin property
280        @param prop: name of pin property
281        @param selection: name of option selected for property
282        """
283        return self._properties[prop][selection]
284
285    def _get_pin_properties(self, props):
286        """
287        Builds dictionary with all pin properties
288        @param props: pin function_properties XML object in signal_configuration.xml
289        """
290        prop_mapping = {}
291        for prop in props.findall('functional_property'):
292            prop_id = prop.attrib['id']
293            if not 'default' in prop.attrib:
294                # No default property. Skip
295                continue
296            prop_mapping[prop_id] = {}
297            prop_mapping[prop_id]['default'] = prop.attrib['default']
298            for state in prop.findall('state'):
299                reg_assign = state.find('configuration/assign')
300                if reg_assign:
301                    bit_value = int(reg_assign.attrib['bit_field_value'], 0)
302                else:
303                    # Assume writing zero to register will select default
304                    bit_value = 0
305                prop_mapping[prop_id][state.attrib['id']] = bit_value
306        return prop_mapping
307
308class PinGroup:
309    """
310    Internal class representing pin group
311    """
312    def __init__(self, function, signal_map):
313        """
314        Creates a pin group
315        @param function: function xml structure from MEX configuration file
316        @param signal_map: Signal mapping, maps signal names to signal pins
317        """
318        self._name = function.attrib.get('name')
319        pins = function.find('mex:pins', NAMESPACES)
320        description = function.find('mex:description', NAMESPACES)
321        if description is not None and description.text is not None:
322            # Replace <br> html tag with newline
323            self._description = description.text.replace("&lt;br/&gt;", "\n")
324        else:
325            self._description = ""
326        # Build dictionary mapping pin properties to pins. This allows us to
327        # group pins based on shared configuration
328        self._pin_groups = collections.defaultdict(lambda: [])
329        for pin in pins:
330            # find signal defintion for this pin
331            signal_name = pin.attrib.get('pin_signal')
332            signal = signal_map[signal_name]
333            if not signal:
334                logging.warning('Signal name %s not present in mapping', signal_name)
335                # No way to find mux option
336                continue
337            # Get mux option for this signal
338            mux_option = f"{pin.attrib.get('peripheral')}_{pin.attrib.get('signal')}"
339            mux = signal.get_mux_connection(mux_option)
340            if mux is None:
341                logging.warning('Signal name %s has no mux', mux_option)
342                # Do not add pinmux option to group
343                continue
344            # Get pin defaults for this pin
345            defaults = signal.get_pin_defaults()
346            # Get pin overrides
347            features = pin.find('mex:pin_features', NAMESPACES)
348            pin_overrides = {}
349            if features is not None:
350                for feature in pin.find('mex:pin_features', NAMESPACES):
351                    pin_overrides[feature.attrib.get('name')] = feature.attrib.get('value')
352            pin_props = self._props_to_dts(pin_overrides, defaults)
353            self._pin_groups[pin_props].append(mux)
354
355    def __repr__(self):
356        """
357        Get string representation of the object
358        """
359        return "PinGroup(%s)" % (self._name)
360
361    def __eq__(self, obj):
362        """
363        return true if two objects have the same pin group name
364        """
365        return isinstance(obj, PinGroup) and self._name == obj._name
366
367    def __lt__(self, obj):
368        """
369        Compare objects based on name
370        """
371        if not isinstance(obj, PinGroup):
372            return True
373        return self._name < obj._name
374
375    def get_pin_props(self):
376        """
377        Get all unique pin properties
378        """
379        return self._pin_groups.keys()
380
381    def get_pins(self, props):
382        """
383        Get all pins with a provided set of properties
384        @param props: property set
385        """
386        return self._pin_groups[props]
387
388    def get_description(self):
389        """
390        Get description of the pin group, if present. If no description present,
391        description will be ""
392        """
393        return self._description
394
395    def get_name(self):
396        """
397        Get pin group name
398        """
399        return self._name
400
401    def _props_to_dts(self, props, defaults):
402        """
403        Remap dictionary of property names from NXP defined values to
404        Zephyr ones
405        @param props: Dictionary of NXP property names and values
406        @param defaults: Dictionary of NXP property names and default pin values
407        @return array of strings suitable for writing to DTS
408        """
409        zephyr_props = []
410        prop_mapping = {
411            'fast': 'fast',
412            'slow': 'slow',
413            'low': 'low',
414            'high': 'high',
415        }
416        # Lambda to convert property names to zephyr formatted strings
417        sanitize = lambda x: "\"" + prop_mapping[x] + "\"" if (x in prop_mapping) else ""
418        # Lambda to get property value or fallback on default
419        prop_val = lambda x: props[x] if x in props else defaults[x]
420        # Check pin defaults and overrides to see if the pin will have a pull
421        pull_enable = prop_val('pull_enable') == 'enable'
422        # For each property, append the provided override or the default
423        zephyr_props.append(f"drive-strength = {sanitize(prop_val('drive_strength'))}")
424        if prop_val('open_drain') == 'enable':
425            zephyr_props.append('drive-open-drain')
426        if pull_enable:
427            # If pull is enabled, select pull up or pull down
428            if prop_val('pull_select') == 'up':
429                zephyr_props.append('bias-pull-up')
430            else:
431                zephyr_props.append('bias-pull-down')
432        zephyr_props.append(f"slew-rate = {sanitize(prop_val('slew_rate'))}")
433        if prop_val('passive_filter') == 'enable':
434            zephyr_props.append("nxp,passive-filter")
435        return tuple(zephyr_props)
436
437
438class NXPSdkUtil:
439    """
440    Class for kinetis configuration file parser
441    """
442    def __init__(self, cfg_root, copyright_header = "", log_level = logging.ERROR):
443        """
444        Initialize SDK utilities.
445        Providing a signal file will enable this class to parse MEX files,
446        and generate output DTS
447        @param cfg_root processor configuration folder root
448        @param copyright_header: copyright string to add to any generated file header
449        @param log_level: log level for SDK utility
450        """
451        # Load the signal XML data
452
453        self._logger = logging.getLogger('')
454        self._logger.setLevel(log_level)
455        self._parse_signal_xml(pathlib.Path(cfg_root)/'signal_configuration.xml')
456        self._copyright = copyright_header
457        logging.info("Loaded %d configurable pin defs", len(self._pins))
458
459    def _parse_signal_xml(self, signal_fn):
460        """
461        Parses signal XML configuration file. Builds a list of pins, which can
462        be used to generate soc level DTSI file.
463        @param signal_fn: signal_configuration.xml file to parse
464        """
465        self._pins = {}
466        try:
467            signal_tree = ET.parse(signal_fn)
468        except ET.ParseError:
469            logging.error("Could not parse provided signal file: %s", signal_fn)
470            return
471
472        signal_root = signal_tree.getroot()
473
474        self._part_num = signal_root.find("./part_information/part_number").get('id')
475
476        logging.info("Loaded XML for %s", self._part_num)
477
478        periphs_node = signal_root.find("peripherals")
479        periphs = []
480        for pin in periphs_node:
481            pin_id = pin.attrib.get("id")
482            name = pin.attrib.get("name")
483
484            if pin_id != name:
485                logging.warning("id and name don't match")
486
487            periphs.append(pin_id)
488
489        pins_node = signal_root.find("pins")
490        for pin in pins_node:
491            signal = SignalPin(pin)
492            # Only add valid signal pins to list
493            if signal.get_name() != '':
494                self._pins[signal.get_name()] = signal
495
496    def _write_pins(self, which_port, pins, prefix, file):
497        """
498        Writes all pin mux nodes for a specific pin port to soc pinctrl dtsi
499        file.
500        @param which_port: pin port to define
501        @param pins: list of pin mux options to write
502        @param prefix: prefix to use for pin macros
503        @param file: output file to write to
504        """
505        port_pins = list(filter(lambda p: (p.get_port().lower() == which_port), pins))
506
507        if (len(port_pins)) == 0:
508            return
509
510        port_pins.sort(key=lambda p: (p.get_pin(), p.get_mux()))
511
512        seen_nodes = []
513
514
515        for pin_data in port_pins:
516            label = pin_data.get_name()
517            port = pin_data.get_port()
518            pin = pin_data.get_pin()
519            mux = pin_data.get_mux()
520
521            if label in seen_nodes:
522                continue
523            seen_nodes.append(label)
524
525            file.write(f"#define {label} {prefix}('{port}',{pin},{mux}) /* PT{port}_{pin} */\n")
526
527    def get_part_num(self):
528        """
529        Return the part number this class is instantiated for
530        """
531        return self._part_num
532
533    def write_pinctrl_defs(self, outputfile):
534        """
535        Writes all pin mux options into pinctrl DTSI file. Board level pin groups
536        can include this pinctrl dtsi file to access pin control defintions.
537        @param outputfile: file to write output pinctrl defs to
538        """
539        # Create list of all pin mux options
540        pinmux_opts = []
541        # Check pin setting to see if we should write a Kinetis style pinctrl
542        # header, or a N9X style header
543        if list(self._pins.values())[0]._type == PORT_N9X:
544            n9x_mode = True
545        else:
546            n9x_mode = False
547        if list(self._pins.values())[0]._type == PORT_A15X:
548            a15x_mode = True
549        else:
550            a15x_mode = False
551        for pin in self._pins.values():
552            pinmux_opts.extend(pin.get_mux_options())
553        pcr_pins = list(filter(lambda p: (p.get_periph() not in ["FB", "EZPORT"]), pinmux_opts))
554        file_header = ("/*\n"
555            f" * NOTE: Autogenerated file by {os.path.basename(__main__.__file__)}\n"
556            f" * for {self._part_num}/signal_configuration.xml\n"
557            " *\n"
558            f" * {self._copyright}\n"
559            " */\n"
560            "\n")
561
562        # Notes on the below macro:
563        # Port values range from 'A'-'E', so we store them with 4 bits,
564        # with port A being 0, B=1,...
565        # N9X uses Port values between 0-5, and these are stored as integers
566        # Pin values range from 0-31, so we give 6 bits for future expansion
567        # Mux values range from 0-15, so we give 4 bits
568        # shift the port and pin values to the MSBs of the mux value, so they
569        # don't conflict with pin configuration settings
570        # Store the mux value at the offset it will actually be written to the
571        # configuration register
572        if n9x_mode:
573            mux_macro = ("#define N9X_MUX(port, pin, mux)\t\t\\\n"
574                    "\t(((((port) - '0') & 0xF) << 28) |\t\\\n"
575                    "\t(((pin) & 0x3F) << 22) |\t\t\\\n"
576                    "\t(((mux) & 0xF) << 8))\n\n")
577        elif a15x_mode:
578            mux_macro = ("#define A15X_MUX(port, pin, mux)\t\t\\\n"
579                    "\t(((((port) - '0') & 0xF) << 28) |\t\\\n"
580                    "\t(((pin) & 0x3F) << 22) |\t\t\\\n"
581                    "\t(((mux) & 0xF) << 8))\n\n")
582        else:
583            mux_macro = ("#define KINETIS_MUX(port, pin, mux)\t\t\\\n"
584                    "\t(((((port) - 'A') & 0xF) << 28) |\t\\\n"
585                    "\t(((pin) & 0x3F) << 22) |\t\t\\\n"
586                    "\t(((mux) & 0x7) << 8))\n\n")
587        with open(outputfile, "w", encoding="utf8") as file:
588            file.write(file_header)
589            # ifdef guard
590            file.write(f"#ifndef _ZEPHYR_DTS_BINDING_{self._part_num.upper()}_\n")
591            file.write(f"#define _ZEPHYR_DTS_BINDING_{self._part_num.upper()}_\n\n")
592            # Write macro to make port name
593            file.write(mux_macro)
594            if n9x_mode:
595                self._write_pins('0', pcr_pins, 'N9X_MUX', file)
596                self._write_pins('1', pcr_pins, 'N9X_MUX', file)
597                self._write_pins('2', pcr_pins, 'N9X_MUX', file)
598                self._write_pins('3', pcr_pins, 'N9X_MUX', file)
599                self._write_pins('4', pcr_pins, 'N9X_MUX', file)
600                self._write_pins('5', pcr_pins, 'N9X_MUX', file)
601            elif a15x_mode:
602                self._write_pins('0', pcr_pins, 'A15X_MUX', file)
603                self._write_pins('1', pcr_pins, 'A15X_MUX', file)
604                self._write_pins('2', pcr_pins, 'A15X_MUX', file)
605                self._write_pins('3', pcr_pins, 'A15X_MUX', file)
606                self._write_pins('4', pcr_pins, 'A15X_MUX', file)
607                self._write_pins('5', pcr_pins, 'A15X_MUX', file)
608            else:
609                self._write_pins('a', pcr_pins, 'KINETIS_MUX', file)
610                self._write_pins('b', pcr_pins, 'KINETIS_MUX', file)
611                self._write_pins('c', pcr_pins, 'KINETIS_MUX', file)
612                self._write_pins('d', pcr_pins, 'KINETIS_MUX', file)
613                self._write_pins('e', pcr_pins, 'KINETIS_MUX', file)
614            file.write("#endif\n")
615
616    def _parse_mex_cfg(self, mexfile):
617        """
618        Parses mex configuration into pin groups.
619        @param mexfile: mex configuration file to parse
620        @return parsed pin groups
621        """
622        pin_groups = {}
623        try:
624            mex_xml = ET.parse(mexfile)
625            for function in mex_xml.findall(
626                'mex:tools/mex:pins/mex:functions_list/mex:function', NAMESPACES):
627                group = PinGroup(function, self._pins)
628                pin_groups[group.get_name()] = group
629            return pin_groups
630        except ET.ParseError:
631            logging.error("Could not parse mex file %s", mex_xml)
632            return None
633
634    def write_pinctrl_groups(self, mexfile, outputfile):
635        """
636        Write pinctrl groups to disk as a parsed DTS file. Intended for use
637        with the output of @ref write_pinctrl_defs
638        @param mexfile: mex file to parse
639        @param outputfile: DTS pinctrl file to write pin groups to
640        """
641        file_header = ("/*\n"
642            f" * NOTE: Autogenerated file by {os.path.basename(__main__.__file__)}\n"
643            f" * for {self._part_num}/signal_configuration.xml\n"
644            " *\n"
645            f" * {self._copyright}\n"
646            " */\n"
647            "\n")
648        pin_groups = self._parse_mex_cfg(mexfile)
649        with open(outputfile, "w", encoding="utf8") as file:
650            file.write(file_header)
651            file.write(f"\n#include <nxp/kinetis/{get_package_name(mexfile)}-pinctrl.h>\n\n")
652            file.write("&pinctrl {\n")
653            # Write pin groups back out to disk
654            for group in pin_groups.values():
655                pin_props = group.get_pin_props()
656                if len(pin_props) == 0:
657                    # Do not write to disk
658                    continue
659                logging.info("Writing pin group %s to disk", group.get_name())
660                # Write description as comment if group has one
661                description = group.get_description()
662                if description != "":
663                    description_lines = description.split("\n")
664                    if len(description_lines) == 1:
665                        file.write(f"\t/* {description} */\n")
666                    else:
667                        file.write("\t/*\n")
668                        for line in description_lines:
669                            file.write(f"\t * {line}\n")
670                        file.write("\t */\n")
671                file.write(f"\t{group.get_name().lower()}: {group.get_name().lower()} {{\n")
672                idx = 0
673                for pin_prop in sorted(pin_props):
674                    group_str = f"\t\tgroup{idx} {{\n"
675                    # Write all pin names
676                    group_str += "\t\t\tpinmux = "
677                    for pin in group.get_pins(pin_prop):
678                        group_str += f"<{pin.get_name()}>,\n\t\t\t\t"
679                    # Strip out last 3 tabs and close pin name list
680                    group_str = re.sub(r',\n\t\t\t\t$', ';\n', group_str)
681                    idx += 1
682                    # Write all pin props
683                    for prop in pin_prop:
684                        group_str += f"\t\t\t{prop};\n"
685                    group_str += "\t\t};\n"
686                    file.write(group_str)
687                file.write("\t};\n\n")
688            file.write("};\n")
689
690"""
691Utility functions used to get details about board/processor from MEX file
692"""
693
694def get_board_name(mexfile):
695    """
696    Extracts board name from a mex file
697    @param mexfile: mex file to parse for board name
698    """
699    try:
700        config_tree = ET.parse(mexfile)
701        return config_tree.getroot().find('mex:common/mex:board',
702            NAMESPACES).text
703    except ET.ParseError:
704        print(f"Malformed XML tree {mexfile}")
705        return None
706    except IOError:
707        print(f"File {mexfile} could not be opened")
708        return None
709
710def get_processor_name(mexfile):
711    """
712    Extracts processor name from a mex file
713    @param mexfile: mex file to parse for processor name
714    """
715    try:
716        config_tree = ET.parse(mexfile)
717        processor = config_tree.getroot().find('mex:common/mex:processor',
718            NAMESPACES)
719        if processor is None:
720            raise RuntimeError("Cannot locate processor name in MEX file. "
721                "Are you using v12 of the MCUXpresso configuration tools?")
722        return processor.text
723    except ET.ParseError:
724        print(f"Malformed XML tree {mexfile}")
725        return None
726    except IOError:
727        print(f"File {mexfile} could not be opened")
728        return None
729
730def get_package_name(mexfile):
731    """
732    Extracts package name from a mex file
733    @param mexfile: mex file to parse for package name
734    """
735    try:
736        config_tree = ET.parse(mexfile)
737        return config_tree.getroot().find('mex:common/mex:package',
738            NAMESPACES).text
739    except ET.ParseError:
740        print(f"Malformed XML tree {mexfile}")
741        return None
742    except IOError:
743        print(f"File {mexfile} could not be opened")
744        return None
745