2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2001 - 2003 by Thomas Moestl <tmm@FreeBSD.org>.
5 * Copyright (c) 2005 Marius Strobl <marius@FreeBSD.org>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions, and the following disclaimer,
13 * without modification, immediately at the beginning of the file.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
23 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 #include <sys/cdefs.h>
33 __FBSDID("$FreeBSD$");
35 #include "opt_platform.h"
36 #include <sys/param.h>
37 #include <sys/systm.h>
39 #include <sys/errno.h>
40 #include <sys/libkern.h>
42 #include <machine/resource.h>
44 #include <dev/ofw/ofw_bus.h>
45 #include <dev/ofw/ofw_bus_subr.h>
46 #include <dev/ofw/openfirm.h>
48 #include "ofw_bus_if.h"
50 #define OFW_COMPAT_LEN 255
51 #define OFW_STATUS_LEN 16
54 ofw_bus_gen_setup_devinfo(struct ofw_bus_devinfo *obd, phandle_t node)
59 /* The 'name' property is considered mandatory. */
60 if ((OF_getprop_alloc(node, "name", (void **)&obd->obd_name)) == -1)
62 OF_getprop_alloc(node, "compatible", (void **)&obd->obd_compat);
63 OF_getprop_alloc(node, "device_type", (void **)&obd->obd_type);
64 OF_getprop_alloc(node, "model", (void **)&obd->obd_model);
65 OF_getprop_alloc(node, "status", (void **)&obd->obd_status);
71 ofw_bus_gen_destroy_devinfo(struct ofw_bus_devinfo *obd)
76 if (obd->obd_compat != NULL)
77 free(obd->obd_compat, M_OFWPROP);
78 if (obd->obd_model != NULL)
79 free(obd->obd_model, M_OFWPROP);
80 if (obd->obd_name != NULL)
81 free(obd->obd_name, M_OFWPROP);
82 if (obd->obd_type != NULL)
83 free(obd->obd_type, M_OFWPROP);
84 if (obd->obd_status != NULL)
85 free(obd->obd_status, M_OFWPROP);
89 ofw_bus_gen_child_pnpinfo_str(device_t cbdev, device_t child, char *buf,
94 if (!ofw_bus_status_okay(child))
97 if (ofw_bus_get_name(child) != NULL) {
98 strlcat(buf, "name=", buflen);
99 strlcat(buf, ofw_bus_get_name(child), buflen);
102 if (ofw_bus_get_compat(child) != NULL) {
103 strlcat(buf, " compat=", buflen);
104 strlcat(buf, ofw_bus_get_compat(child), buflen);
111 ofw_bus_gen_get_compat(device_t bus, device_t dev)
113 const struct ofw_bus_devinfo *obd;
115 obd = OFW_BUS_GET_DEVINFO(bus, dev);
118 return (obd->obd_compat);
122 ofw_bus_gen_get_model(device_t bus, device_t dev)
124 const struct ofw_bus_devinfo *obd;
126 obd = OFW_BUS_GET_DEVINFO(bus, dev);
129 return (obd->obd_model);
133 ofw_bus_gen_get_name(device_t bus, device_t dev)
135 const struct ofw_bus_devinfo *obd;
137 obd = OFW_BUS_GET_DEVINFO(bus, dev);
140 return (obd->obd_name);
144 ofw_bus_gen_get_node(device_t bus, device_t dev)
146 const struct ofw_bus_devinfo *obd;
148 obd = OFW_BUS_GET_DEVINFO(bus, dev);
151 return (obd->obd_node);
155 ofw_bus_gen_get_type(device_t bus, device_t dev)
157 const struct ofw_bus_devinfo *obd;
159 obd = OFW_BUS_GET_DEVINFO(bus, dev);
162 return (obd->obd_type);
166 ofw_bus_get_status(device_t dev)
168 const struct ofw_bus_devinfo *obd;
170 obd = OFW_BUS_GET_DEVINFO(device_get_parent(dev), dev);
174 return (obd->obd_status);
178 ofw_bus_status_okay(device_t dev)
182 status = ofw_bus_get_status(dev);
183 if (status == NULL || strcmp(status, "okay") == 0 ||
184 strcmp(status, "ok") == 0)
191 ofw_bus_node_status_okay(phandle_t node)
193 char status[OFW_STATUS_LEN];
196 len = OF_getproplen(node, "status");
200 OF_getprop(node, "status", status, OFW_STATUS_LEN);
201 if ((len == 5 && (bcmp(status, "okay", len) == 0)) ||
202 (len == 3 && (bcmp(status, "ok", len))))
209 ofw_bus_node_is_compatible_int(const char *compat, int len,
210 const char *onecompat)
214 onelen = strlen(onecompat);
218 if (strlen(compat) == onelen &&
219 strncasecmp(compat, onecompat, onelen) == 0) {
225 /* Slide to the next sub-string. */
226 l = strlen(compat) + 1;
235 ofw_bus_node_is_compatible(phandle_t node, const char *compatstr)
237 char compat[OFW_COMPAT_LEN];
240 if ((len = OF_getproplen(node, "compatible")) <= 0)
243 bzero(compat, OFW_COMPAT_LEN);
245 if (OF_getprop(node, "compatible", compat, OFW_COMPAT_LEN) < 0)
248 rv = ofw_bus_node_is_compatible_int(compat, len, compatstr);
254 ofw_bus_is_compatible(device_t dev, const char *onecompat)
260 if ((compat = ofw_bus_get_compat(dev)) == NULL)
263 if ((node = ofw_bus_get_node(dev)) == -1)
266 /* Get total 'compatible' prop len */
267 if ((len = OF_getproplen(node, "compatible")) <= 0)
270 return (ofw_bus_node_is_compatible_int(compat, len, onecompat));
274 ofw_bus_is_compatible_strict(device_t dev, const char *compatible)
279 if ((compat = ofw_bus_get_compat(dev)) == NULL)
282 len = strlen(compatible);
283 if (strlen(compat) == len &&
284 strncasecmp(compat, compatible, len) == 0)
290 const struct ofw_compat_data *
291 ofw_bus_search_compatible(device_t dev, const struct ofw_compat_data *compat)
297 for (; compat->ocd_str != NULL; ++compat) {
298 if (ofw_bus_is_compatible(dev, compat->ocd_str))
306 ofw_bus_has_prop(device_t dev, const char *propname)
310 if ((node = ofw_bus_get_node(dev)) == -1)
313 return (OF_hasprop(node, propname));
317 ofw_bus_setup_iinfo(phandle_t node, struct ofw_bus_iinfo *ii, int intrsz)
322 if (OF_getencprop(node, "#address-cells", &addrc, sizeof(addrc)) == -1)
324 ii->opi_addrc = addrc * sizeof(pcell_t);
326 ii->opi_imapsz = OF_getencprop_alloc(node, "interrupt-map",
327 (void **)&ii->opi_imap);
328 if (ii->opi_imapsz > 0) {
329 msksz = OF_getencprop_alloc(node, "interrupt-map-mask",
330 (void **)&ii->opi_imapmsk);
332 * Failure to get the mask is ignored; a full mask is used
333 * then. We barf on bad mask sizes, however.
335 if (msksz != -1 && msksz != ii->opi_addrc + intrsz)
336 panic("ofw_bus_setup_iinfo: bad interrupt-map-mask "
342 ofw_bus_lookup_imap(phandle_t node, struct ofw_bus_iinfo *ii, void *reg,
343 int regsz, void *pintr, int pintrsz, void *mintr, int mintrsz,
346 uint8_t maskbuf[regsz + pintrsz];
349 if (ii->opi_imapsz <= 0)
351 KASSERT(regsz >= ii->opi_addrc,
352 ("ofw_bus_lookup_imap: register size too small: %d < %d",
353 regsz, ii->opi_addrc));
355 rv = OF_getencprop(node, "reg", reg, regsz);
357 panic("ofw_bus_lookup_imap: cannot get reg property");
359 return (ofw_bus_search_intrmap(pintr, pintrsz, reg, ii->opi_addrc,
360 ii->opi_imap, ii->opi_imapsz, ii->opi_imapmsk, maskbuf, mintr,
365 * Map an interrupt using the firmware reg, interrupt-map and
366 * interrupt-map-mask properties.
367 * The interrupt property to be mapped must be of size intrsz, and pointed to
368 * by intr. The regs property of the node for which the mapping is done must
369 * be passed as regs. This property is an array of register specifications;
370 * the size of the address part of such a specification must be passed as
371 * physsz. Only the first element of the property is used.
372 * imap and imapsz hold the interrupt mask and it's size.
373 * imapmsk is a pointer to the interrupt-map-mask property, which must have
374 * a size of physsz + intrsz; it may be NULL, in which case a full mask is
376 * maskbuf must point to a buffer of length physsz + intrsz.
377 * The interrupt is returned in result, which must point to a buffer of length
378 * rintrsz (which gives the expected size of the mapped interrupt).
379 * Returns number of cells in the interrupt if a mapping was found, 0 otherwise.
382 ofw_bus_search_intrmap(void *intr, int intrsz, void *regs, int physsz,
383 void *imap, int imapsz, void *imapmsk, void *maskbuf, void *result,
384 int rintrsz, phandle_t *iparent)
387 uint8_t *ref = maskbuf;
388 uint8_t *uiintr = intr;
389 uint8_t *uiregs = regs;
390 uint8_t *uiimapmsk = imapmsk;
396 if (imapmsk != NULL) {
397 for (i = 0; i < physsz; i++)
398 ref[i] = uiregs[i] & uiimapmsk[i];
399 for (i = 0; i < intrsz; i++)
400 ref[physsz + i] = uiintr[i] & uiimapmsk[physsz + i];
402 bcopy(regs, ref, physsz);
403 bcopy(intr, ref + physsz, intrsz);
410 bcopy(mptr + physsz + intrsz, &parent, sizeof(parent));
411 #ifndef OFW_IMAP_NO_IPARENT_ADDR_CELLS
413 * Find if we need to read the parent address data.
414 * CHRP-derived OF bindings, including ePAPR-compliant FDTs,
415 * use this as an optional part of the specifier.
417 if (OF_getencprop(OF_node_from_xref(parent),
418 "#address-cells", &paddrsz, sizeof(paddrsz)) == -1)
419 paddrsz = 0; /* default */
420 paddrsz *= sizeof(pcell_t);
423 if (OF_searchencprop(OF_node_from_xref(parent),
424 "#interrupt-cells", &pintrsz, sizeof(pintrsz)) == -1)
425 pintrsz = 1; /* default */
426 pintrsz *= sizeof(pcell_t);
428 /* Compute the map stride size. */
429 tsz = physsz + intrsz + sizeof(phandle_t) + paddrsz + pintrsz;
430 KASSERT(i >= tsz, ("ofw_bus_search_intrmap: truncated map"));
432 if (bcmp(ref, mptr, physsz + intrsz) == 0) {
433 bcopy(mptr + physsz + intrsz + sizeof(parent) + paddrsz,
434 result, MIN(rintrsz, pintrsz));
438 return (pintrsz/sizeof(pcell_t));
447 ofw_bus_msimap(phandle_t node, uint16_t pci_rid, phandle_t *msi_parent,
450 pcell_t *map, mask, msi_base, rid_base, rid_length;
455 /* TODO: This should be OF_searchprop_alloc if we had it */
456 len = OF_getencprop_alloc_multi(node, "msi-map", sizeof(*map),
459 if (msi_parent != NULL) {
461 OF_getencprop(node, "msi-parent", msi_parent,
462 sizeof(*msi_parent));
471 OF_getencprop(node, "msi-map-mask", &mask, sizeof(mask));
473 masked_rid = pci_rid & mask;
474 for (i = 0; i < len; i += 4) {
475 rid_base = map[i + 0];
476 rid_length = map[i + 3];
478 if (masked_rid < rid_base ||
479 masked_rid >= (rid_base + rid_length))
482 msi_base = map[i + 2];
484 if (msi_parent != NULL)
485 *msi_parent = map[i + 1];
487 *msi_rid = masked_rid - rid_base + msi_base;
492 free(map, M_OFWPROP);
498 ofw_bus_reg_to_rl_helper(device_t dev, phandle_t node, pcell_t acells, pcell_t scells,
499 struct resource_list *rl, const char *reg_source)
502 ssize_t i, j, rid, nreg, ret;
507 * This may be just redundant when having ofw_bus_devinfo
508 * but makes this routine independent of it.
510 ret = OF_getprop_alloc(node, "name", (void **)&name);
514 ret = OF_getencprop_alloc_multi(node, reg_source, sizeof(*reg),
516 nreg = (ret == -1) ? 0 : ret;
518 if (nreg % (acells + scells) != 0) {
520 device_printf(dev, "Malformed reg property on <%s>\n",
521 (name == NULL) ? "unknown" : name);
525 for (i = 0, rid = 0; i < nreg; i += acells + scells, rid++) {
527 for (j = 0; j < acells; j++) {
531 for (j = 0; j < scells; j++) {
533 size |= reg[i + acells + j];
535 /* Skip the dummy reg property of glue devices like ssm(4). */
537 resource_list_add(rl, SYS_RES_MEMORY, rid,
538 phys, phys + size - 1, size);
540 free(name, M_OFWPROP);
541 free(reg, M_OFWPROP);
547 ofw_bus_reg_to_rl(device_t dev, phandle_t node, pcell_t acells, pcell_t scells,
548 struct resource_list *rl)
551 return (ofw_bus_reg_to_rl_helper(dev, node, acells, scells, rl, "reg"));
555 ofw_bus_assigned_addresses_to_rl(device_t dev, phandle_t node, pcell_t acells,
556 pcell_t scells, struct resource_list *rl)
559 return (ofw_bus_reg_to_rl_helper(dev, node, acells, scells,
560 rl, "assigned-addresses"));
564 * Get interrupt parent for given node.
565 * Returns 0 if interrupt parent doesn't exist.
568 ofw_bus_find_iparent(phandle_t node)
572 if (OF_searchencprop(node, "interrupt-parent", &iparent,
573 sizeof(iparent)) == -1) {
574 for (iparent = node; iparent != 0;
575 iparent = OF_parent(iparent)) {
576 if (OF_hasprop(iparent, "interrupt-controller"))
579 iparent = OF_xref_from_node(iparent);
585 ofw_bus_intr_to_rl(device_t dev, phandle_t node,
586 struct resource_list *rl, int *rlen)
589 uint32_t icells, *intr;
590 int err, i, irqnum, nintr, rid;
593 nintr = OF_getencprop_alloc_multi(node, "interrupts", sizeof(*intr),
596 iparent = ofw_bus_find_iparent(node);
598 device_printf(dev, "No interrupt-parent found, "
599 "assuming direct parent\n");
600 iparent = OF_parent(node);
601 iparent = OF_xref_from_node(iparent);
603 if (OF_searchencprop(OF_node_from_xref(iparent),
604 "#interrupt-cells", &icells, sizeof(icells)) == -1) {
605 device_printf(dev, "Missing #interrupt-cells "
606 "property, assuming <1>\n");
609 if (icells < 1 || icells > nintr) {
610 device_printf(dev, "Invalid #interrupt-cells property "
611 "value <%d>, assuming <1>\n", icells);
616 nintr = OF_getencprop_alloc_multi(node, "interrupts-extended",
617 sizeof(*intr), (void **)&intr);
624 for (i = 0; i < nintr; i += icells) {
627 if (OF_searchencprop(OF_node_from_xref(iparent),
628 "#interrupt-cells", &icells, sizeof(icells)) == -1) {
629 device_printf(dev, "Missing #interrupt-cells "
634 if (icells < 1 || (i + icells) > nintr) {
635 device_printf(dev, "Invalid #interrupt-cells "
636 "property value <%d>\n", icells);
641 irqnum = ofw_bus_map_intr(dev, iparent, icells, &intr[i]);
642 resource_list_add(rl, SYS_RES_IRQ, rid++, irqnum, irqnum, 1);
646 free(intr, M_OFWPROP);
651 ofw_bus_intr_by_rid(device_t dev, phandle_t node, int wanted_rid,
652 phandle_t *producer, int *ncells, pcell_t **cells)
655 uint32_t icells, *intr;
656 int err, i, nintr, rid;
659 nintr = OF_getencprop_alloc_multi(node, "interrupts", sizeof(*intr),
662 iparent = ofw_bus_find_iparent(node);
664 device_printf(dev, "No interrupt-parent found, "
665 "assuming direct parent\n");
666 iparent = OF_parent(node);
667 iparent = OF_xref_from_node(iparent);
669 if (OF_searchencprop(OF_node_from_xref(iparent),
670 "#interrupt-cells", &icells, sizeof(icells)) == -1) {
671 device_printf(dev, "Missing #interrupt-cells "
672 "property, assuming <1>\n");
675 if (icells < 1 || icells > nintr) {
676 device_printf(dev, "Invalid #interrupt-cells property "
677 "value <%d>, assuming <1>\n", icells);
682 nintr = OF_getencprop_alloc_multi(node, "interrupts-extended",
683 sizeof(*intr), (void **)&intr);
690 for (i = 0; i < nintr; i += icells, rid++) {
693 if (OF_searchencprop(OF_node_from_xref(iparent),
694 "#interrupt-cells", &icells, sizeof(icells)) == -1) {
695 device_printf(dev, "Missing #interrupt-cells "
700 if (icells < 1 || (i + icells) > nintr) {
701 device_printf(dev, "Invalid #interrupt-cells "
702 "property value <%d>\n", icells);
707 if (rid == wanted_rid) {
708 *cells = malloc(icells * sizeof(**cells), M_OFWPROP,
712 memcpy(*cells, intr + i, icells * sizeof(**cells));
717 free(intr, M_OFWPROP);
722 ofw_bus_find_child(phandle_t start, const char *child_name)
728 for (child = OF_child(start); child != 0; child = OF_peer(child)) {
729 ret = OF_getprop_alloc(child, "name", (void **)&name);
732 if (strcmp(name, child_name) == 0) {
733 free(name, M_OFWPROP);
737 free(name, M_OFWPROP);
744 ofw_bus_find_compatible(phandle_t node, const char *onecompat)
746 phandle_t child, ret;
749 * Traverse all children of 'start' node, and find first with
750 * matching 'compatible' property.
752 for (child = OF_child(node); child != 0; child = OF_peer(child)) {
753 if (ofw_bus_node_is_compatible(child, onecompat) != 0)
756 ret = ofw_bus_find_compatible(child, onecompat);
764 * @brief Return child of bus whose phandle is node
766 * A direct child of @p will be returned if it its phandle in the
767 * OFW tree is @p node. Otherwise, NULL is returned.
769 * @param bus The bus to examine
770 * @param node The phandle_t to look for.
773 ofw_bus_find_child_device_by_phandle(device_t bus, phandle_t node)
775 device_t *children, retval, child;
779 * Nothing can match the flag value for no node.
785 * Search the children for a match. We microoptimize
786 * a bit by not using ofw_bus_get since we already know
787 * the parent. We do not recurse.
789 if (device_get_children(bus, &children, &nkid) != 0)
792 for (i = 0; i < nkid; i++) {
794 if (OFW_BUS_GET_NODE(bus, child) == node) {
799 free(children, M_TEMP);
805 * Parse property that contain list of xrefs and values
806 * (like standard "clocks" and "resets" properties)
808 * node - consumers device node
809 * list_name - name of parsed list - "clocks"
810 * cells_name - name of size property - "#clock-cells"
811 * idx - the index of the requested list entry, or, if -1, an indication
812 * to return the number of entries in the parsed list.
814 * producer - handle of producer
815 * ncells - number of cells in result or the number of items in the list when
817 * cells - array of decoded cells
820 ofw_bus_parse_xref_list_internal(phandle_t node, const char *list_name,
821 const char *cells_name, int idx, phandle_t *producer, int *ncells,
827 int rv, i, j, nelems, cnt;
830 nelems = OF_getencprop_alloc_multi(node, list_name, sizeof(*elems),
834 rv = (idx == -1) ? 0 : ENOENT;
835 for (i = 0, cnt = 0; i < nelems; i += pcells, cnt++) {
837 if (OF_getencprop(OF_node_from_xref(pnode),
838 cells_name, &pcells, sizeof(pcells)) == -1) {
839 printf("Missing %s property\n", cells_name);
844 if ((i + pcells) > nelems) {
845 printf("Invalid %s property value <%d>\n", cells_name,
851 *cells= malloc(pcells * sizeof(**cells), M_OFWPROP,
855 for (j = 0; j < pcells; j++)
856 (*cells)[j] = elems[i + j];
862 free(elems, M_OFWPROP);
863 if (idx == -1 && rv == 0)
869 * Parse property that contain list of xrefs and values
870 * (like standard "clocks" and "resets" properties)
872 * node - consumers device node
873 * list_name - name of parsed list - "clocks"
874 * cells_name - name of size property - "#clock-cells"
875 * idx - the index of the requested list entry (>= 0)
877 * producer - handle of producer
878 * ncells - number of cells in result
879 * cells - array of decoded cells
882 ofw_bus_parse_xref_list_alloc(phandle_t node, const char *list_name,
883 const char *cells_name, int idx, phandle_t *producer, int *ncells,
888 ("ofw_bus_parse_xref_list_alloc: negative index supplied"));
890 return (ofw_bus_parse_xref_list_internal(node, list_name, cells_name,
891 idx, producer, ncells, cells));
895 * Parse property that contain list of xrefs and values
896 * (like standard "clocks" and "resets" properties)
897 * and determine the number of items in the list
899 * node - consumers device node
900 * list_name - name of parsed list - "clocks"
901 * cells_name - name of size property - "#clock-cells"
903 * count - number of items in list
906 ofw_bus_parse_xref_list_get_length(phandle_t node, const char *list_name,
907 const char *cells_name, int *count)
910 return (ofw_bus_parse_xref_list_internal(node, list_name, cells_name,
911 -1, NULL, count, NULL));
915 * Find index of string in string list property (case sensitive).
918 ofw_bus_find_string_index(phandle_t node, const char *list_name,
919 const char *name, int *idx)
922 int rv, i, cnt, nelems;
925 nelems = OF_getprop_alloc(node, list_name, (void **)&elems);
930 for (i = 0, cnt = 0; i < nelems; cnt++) {
931 if (strcmp(elems + i, name) == 0) {
936 i += strlen(elems + i) + 1;
940 free(elems, M_OFWPROP);
945 * Create zero terminated array of strings from string list property.
948 ofw_bus_string_list_to_array(phandle_t node, const char *list_name,
949 const char ***out_array)
953 int i, cnt, nelems, len;
956 nelems = OF_getprop_alloc(node, list_name, (void **)&elems);
960 /* Count number of strings. */
961 for (i = 0, cnt = 0; i < nelems; cnt++)
962 i += strlen(elems + i) + 1;
964 /* Allocate space for arrays and all strings. */
965 array = malloc((cnt + 1) * sizeof(char *) + nelems, M_OFWPROP,
968 /* Get address of first string. */
969 tptr = (char *)(array + cnt + 1);
972 memcpy(tptr, elems, nelems);
973 free(elems, M_OFWPROP);
975 /* Fill string pointers. */
976 for (i = 0, cnt = 0; i < nelems; cnt++) {
977 len = strlen(tptr) + 1;