diff --git a/Documentation/components/drivers/special/reset.rst b/Documentation/components/drivers/special/reset.rst index 64bf4683e4c..bc679709e7e 100644 --- a/Documentation/components/drivers/special/reset.rst +++ b/Documentation/components/drivers/special/reset.rst @@ -1,3 +1,228 @@ ============ Reset Driver ============ + +Many SoCs hold their peripherals in reset until software releases them, and +share one reset line between several peripherals. The reset framework lets a +driver ask for the line its device needs and assert or release it, without +knowing which register holds the bit or who else shares it. + +A **reset controller** is the hardware block that owns the lines, described by +``struct reset_controller_dev`` and registered by an architecture or board. A +**reset control** is one consumer's handle on one line, obtained by name or by +index and released when the consumer is done. + +``include/nuttx/reset/reset.h`` declares what a consumer uses; +``include/nuttx/reset/reset-controller.h`` declares what a controller +implements. ``CONFIG_RESET`` builds the framework. + +Consumer interface +================== + +A consumer obtains a handle on one line of a named controller, uses it, and +puts it back: + +.. code-block:: c + + FAR struct reset_control *rstc; + + rstc = reset_control_get_exclusive_by_index("mychip-reset", 12); + if (rstc == NULL) + { + return -ENODEV; + } + + reset_control_deassert(rstc); /* let the peripheral run */ + ... + reset_control_put(rstc); + +``reset_control_get()`` takes the controller name, the line's index, and +whether the handle is shared and already acquired. The wrappers name the +usual combinations, and are what a driver should normally call: + + ========================================== ============================ + Function Handle + ========================================== ============================ + ``reset_control_get_exclusive()`` exclusive, acquired + ``reset_control_get_exclusive_released()`` exclusive, not yet acquired + ``reset_control_get_shared()`` shared + ``reset_control_get_exclusive_by_index()`` exclusive, acquired, by index + ``reset_control_get_shared_by_index()`` shared, by index + ========================================== ============================ + +``reset_control_array_get()`` takes several lines of one controller as a +single handle, so that one call asserts or releases all of them. + +The operations are: + +``reset_control_assert()`` + Put the line into reset and leave it there. + +``reset_control_deassert()`` + Take the line out of reset. + +``reset_control_reset()`` + Pulse a self deasserting reset: the controller asserts and releases the line + itself. For a line that resets automatically this is the only correct + operation; asserting such a line by hand is not meaningful. + +``reset_control_status()`` + Report the line's state. Returns 1 while the line is asserted, 0 once it is + released, or a negated errno. ``-ENOTSUP`` means the controller cannot read + the line back. + +``reset_control_acquire()`` and ``reset_control_release()`` + Take and give back the right to drive an exclusive line. A handle from + ``reset_control_get_exclusive()`` arrives already acquired; one from + ``reset_control_get_exclusive_released()`` does not, and asserting it before + acquiring returns ``-EPERM``. This lets two drivers share a line in turn + without either holding it permanently. + +``reset_control_put()`` + Give the handle back. + +``reset_control_device_reset()`` + Pulse the reset of a named device without holding a handle across the call. + +Shared and exclusive lines +-------------------------- + +One line often resets several peripherals at once, and a **shared** handle is +how a driver says it is not the only user. Shared handles are counted: +``reset_control_deassert()`` releases the line on the first call and only +counts on later ones, and ``reset_control_assert()`` asserts it only when the +last consumer has asserted. A driver therefore gets its peripheral running +without deciding on behalf of the others when it may stop. + +An **exclusive** handle assumes a single owner and drives the line +immediately. Mixing the two on one line defeats the counting, so ask for what +the hardware really is: an exclusive handle on a shared line lets one driver +reset a peripheral another is still using. + +Shared handles must not be pulsed and asserted by the same consumer: +``reset_control_reset()`` and ``reset_control_assert()`` each refuse with +``-EINVAL`` if the other has been used on that handle. + +Controller interface +==================== + +A controller fills in ``struct reset_controller_dev`` and calls +``reset_controller_register()``: + +.. code-block:: c + + static struct reset_controller_dev g_mychip_rcdev = + { + .name = "mychip-reset", + .ops = &g_mychip_reset_ops, + .nlines = MYCHIP_NRESETS, + }; + + reset_controller_register(&g_mychip_rcdev); + +``name`` identifies the controller to ``reset_control_get()`` and heads its +section in ``/proc/reset``. ``nlines`` bounds the ids ``get_line()`` is asked +about, which are 0 to ``nlines - 1``; since controllers commonly leave gaps it +is an id space rather than a count of real lines. Leave it zero if the +controller does not implement ``get_line()``. The structure must outlive registration, since the +framework stores the pointer rather than copying. + +``struct reset_control_ops`` is the call table. Every member is optional; a +consumer calling an operation the controller does not provide gets an error +rather than silence, though which error depends on the path: + + ============== ========================================================= + Method Purpose + ============== ========================================================= + ``reset`` Pulse a self deasserting reset on one line + ``assert`` Put one line into reset + ``deassert`` Take one line out of reset + ``status`` Report whether one line is asserted; 1 asserted, 0 not + ``acquire`` Claim a shared line + ``release`` Give a shared line back + ``get_line`` Describe one line for ``/proc/reset`` + ============== ========================================================= + +Each takes the line's ``id``, whose meaning is the controller's own: usually an +index into its own register layout, and often with gaps where an id inside the +range names no line. + +``reset_controller_unregister()`` removes a controller. Consumers must have +put their handles back first. + +Reading the lines back +====================== + +``status()`` answers for one line at a time, and only for a caller that already +knows the id. Nothing else in the interface says how many lines a controller +has or what any of them is for, so the framework cannot enumerate them on a +controller's behalf. + +``get_line`` supplies exactly what is missing. It is **optional**: a controller +without it is still listed in ``/proc/reset``, with a note. + +.. code-block:: c + + struct reset_lineinfo_s + { + char name[RESET_NAME_MAX]; /* What this line resets */ + char extra[RESET_EXTRA_MAX]; /* Controller specific key:value text */ + }; + +Both members are optional too. An empty name reports the line by its id alone. +``extra`` carries anything the structure has no member for, in the same +``key:value`` form, and is appended to the line's entry. + +The state is **not** in the structure: the framework calls ``status()`` for it, +so a controller does not report the same fact twice. + +Return ``-ENODEV`` for an id that names no line. That is how a gap in the +numbering is reported, and the renderer skips it: + +.. code-block:: c + + static int mychip_getline(FAR struct reset_controller_dev *rcdev, + unsigned int id, + FAR struct reset_lineinfo_s *info) + { + if (id >= nitems(g_mychip_lines) || g_mychip_lines[id].name == NULL) + { + return -ENODEV; /* a gap in the numbering */ + } + + strlcpy(info->name, g_mychip_lines[id].name, sizeof(info->name)); + + /* Anything the structure has no member for */ + + snprintf(info->extra, sizeof(info->extra), "reg:0x%03x bit:%u", + MYCHIP_RESET_REG(id), MYCHIP_RESET_BIT(id)); + return OK; + } + +/proc/reset +=========== + +``CONFIG_RESET_PROCFS`` adds ``/proc/reset``, which lists every registered +controller and, for those implementing ``get_line``, one line per reset line. +Every line carries the same tokens in the same order, so the file can be +parsed: + +.. code-block:: text + + eic7700x-reset: + 0 noc_nsp state:released reg:0x400 bit:0 + 9 gpio0 state:held reg:0x400 bit:9 + +A controller with no ``get_line`` appears as its name and a note. ``state:-`` +means the controller has no ``status()`` or the call failed. + +The option depends on ``FS_PROCFS_REGISTER`` and is off by default. + +Remote controllers +================== + +``CONFIG_RESET_RPMSG`` lets a consumer on one core drive a reset controller +owned by another, over rpmsg. The consumer side calls ``reset_rpmsg_get()`` +for a handle to the remote controller; the core that owns the hardware runs +``reset_rpmsg_server_init()``. Consumers use the ordinary interface either +way. diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index e4f3f117089..284497bd9d3 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -15,3 +15,17 @@ config RESET_RPMSG default n ---help--- This selection enables rpmsg support for reset. + +config RESET_PROCFS + bool "Reset procfs entry" + depends on RESET && FS_PROCFS && FS_PROCFS_REGISTER + default n + ---help--- + Create /proc/reset, listing every registered reset controller and, + for those that supply a get_line method, one line per reset line + in key:value form. + + status() reads one line at a time, but nothing else in the + interface says how many lines a controller has or what any of them + is called, so without this the only way to see which peripherals + are held is a program that knows the numbering already. diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 2495009ed07..d58ecf9edaf 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -33,6 +33,15 @@ #include #include +#ifdef CONFIG_RESET_PROCFS +# include +# include +# include +# include +# include +# include +#endif + /**************************************************************************** * Private Data ****************************************************************************/ @@ -419,6 +428,328 @@ reset_controller_get_by_name(FAR const char *name) return NULL; } +#ifdef CONFIG_RESET_PROCFS + +/**************************************************************************** + * Name: reset_procfs_open + * + * Description: + * Open /proc/reset. The entry is read only, and holds no state of + * its own beyond the position accounting procfs does for every + * file. + * + * Input Parameters: + * filep - The file structure to attach the open file to + * relpath - The path below /proc being opened + * oflags - Open flags; anything but read only is refused + * mode - Ignored, the entry cannot be created + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +static int reset_procfs_open(FAR struct file *filep, + FAR const char *relpath, + int oflags, mode_t mode) +{ + FAR struct procfs_file_s *priv; + + if ((oflags & O_ACCMODE) != O_RDONLY) + { + return -EACCES; + } + + priv = kmm_zalloc(sizeof(struct procfs_file_s)); + if (priv == NULL) + { + return -ENOMEM; + } + + filep->f_priv = priv; + return OK; +} + +/**************************************************************************** + * Name: reset_procfs_close + * + * Description: + * Close /proc/reset and free what open() allocated. + * + * Input Parameters: + * filep - The open file + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +static int reset_procfs_close(FAR struct file *filep) +{ + kmm_free(filep->f_priv); + filep->f_priv = NULL; + return OK; +} + +/**************************************************************************** + * Name: reset_procfs_append + * + * Description: + * Append formatted text at offset n, clamping to the buffer. snprintf + * returns the length it wanted, so an unclamped sum would carry the + * offset past the buffer and wrap the remaining size. The offset + * returned never exceeds len - 1. + * + * Input Parameters: + * line - The line being built + * len - Size of line + * n - Offset to append at + * fmt - Format string, followed by its arguments + * + * Returned Value: + * The offset after the text, never more than len - 1. + * + ****************************************************************************/ + +static size_t reset_procfs_append(FAR char *line, size_t len, size_t n, + FAR const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + n += vsnprintf(line + n, len - n, fmt, ap); + va_end(ap); + + return n < len ? n : len - 1; +} + +/**************************************************************************** + * Name: reset_procfs_line + * + * Description: + * Render one reset line as key:value tokens, every line the same tokens + * in the same order, then the controller's extra fields. The state + * comes from status(), which a controller need not implement either; it + * renders as - when absent or when the call fails. + * + * Input Parameters: + * line - Where to render the line + * len - Size of line + * rcdev - The controller owning the line + * id - The line's id within that controller + * info - What the controller reported for it + * + * Returned Value: + * The length of the rendered line. + * + ****************************************************************************/ + +static size_t reset_procfs_line(FAR char *line, size_t len, + FAR struct reset_controller_dev *rcdev, + unsigned int id, + FAR const struct reset_lineinfo_s *info) +{ + FAR const char *state = "-"; + size_t n; + int ret; + + if (rcdev->ops->status != NULL) + { + ret = rcdev->ops->status(rcdev, id); + if (ret >= 0) + { + state = ret > 0 ? "asserted" : "released"; + } + } + + n = reset_procfs_append(line, len, 0, "%-4u %-24s state:%-8s", id, + info->name[0] != '\0' ? info->name : "-", state); + + if (info->extra[0] != '\0') + { + n = reset_procfs_append(line, len, n, " %s", info->extra); + } + + n = reset_procfs_append(line, len, n, "\n"); + + /* A truncated line still has to end the record */ + + if (line[n - 1] != '\n') + { + line[n - 1] = '\n'; + } + + return n; +} + +/**************************************************************************** + * Name: reset_procfs_read + * + * Description: + * Describe every registered controller's lines, in registration order. + * A controller with no get_line method contributes its name and a note. + * + * Input Parameters: + * filep - The open file, carrying the offset reached so far + * buffer - Where to return the text + * buflen - Size of buffer + * + * Returned Value: + * The number of bytes returned, zero at end of file, or a negated errno + * on failure. + * + ****************************************************************************/ + +static ssize_t reset_procfs_read(FAR struct file *filep, + FAR char *buffer, size_t buflen) +{ + FAR struct reset_controller_dev *rcdev; + struct reset_lineinfo_s info; + size_t remaining = buflen; + FAR char *dest = buffer; + off_t pos = filep->f_pos; + char line[96]; + unsigned int id; + size_t n; + int ret; + + ret = nxmutex_lock(&g_reset_list_mutex); + if (ret < 0) + { + return ret; + } + + list_for_every_entry(&g_reset_controller_list, rcdev, + struct reset_controller_dev, list) + { + if (remaining == 0) + { + break; + } + + /* The name alone: nlines is an id space rather than a count of + * real lines, so reporting it here would overstate what follows. + */ + + n = snprintf(line, sizeof(line), "%s:\n", rcdev->name); + n = procfs_memcpy(line, n, dest, remaining, &pos); + dest += n; + remaining -= n; + + if (rcdev->ops->get_line == NULL) + { + n = snprintf(line, sizeof(line), + " no detail, lines are not enumerable\n"); + n = procfs_memcpy(line, n, dest, remaining, &pos); + dest += n; + remaining -= n; + continue; + } + + for (id = 0; id < rcdev->nlines && remaining > 0; id++) + { + memset(&info, 0, sizeof(info)); + if (rcdev->ops->get_line(rcdev, id, &info) < 0) + { + continue; + } + + n = reset_procfs_line(line, sizeof(line), rcdev, id, &info); + n = procfs_memcpy(line, n, dest, remaining, &pos); + dest += n; + remaining -= n; + } + } + + nxmutex_unlock(&g_reset_list_mutex); + + filep->f_pos += (dest - buffer); + return dest - buffer; +} + +/**************************************************************************** + * Name: reset_procfs_dup + * + * Description: + * Duplicate an open /proc/reset, copying the position reached so + * that the new file continues where the old one had got to. + * + * Input Parameters: + * oldp - The open file being duplicated + * newp - The file structure to attach the duplicate to + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +static int reset_procfs_dup(FAR const struct file *oldp, + FAR struct file *newp) +{ + FAR struct procfs_file_s *priv; + + priv = kmm_zalloc(sizeof(struct procfs_file_s)); + if (priv == NULL) + { + return -ENOMEM; + } + + memcpy(priv, oldp->f_priv, sizeof(struct procfs_file_s)); + newp->f_priv = priv; + return OK; +} + +/**************************************************************************** + * Name: reset_procfs_stat + * + * Description: + * Report /proc/reset as a read only regular file. + * + * Input Parameters: + * relpath - The path below /proc being queried + * buf - Where to return the status + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +static int reset_procfs_stat(FAR const char *relpath, FAR struct stat *buf) +{ + buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR; + buf->st_size = 0; + buf->st_blksize = 0; + buf->st_blocks = 0; + return OK; +} + +static const struct procfs_operations g_reset_procfs_ops = +{ + reset_procfs_open, /* open */ + reset_procfs_close, /* close */ + reset_procfs_read, /* read */ + NULL, /* write */ + NULL, /* poll */ + + reset_procfs_dup, /* dup */ + + NULL, /* opendir */ + NULL, /* closedir */ + NULL, /* readdir */ + NULL, /* rewinddir */ + + reset_procfs_stat, /* stat */ +}; + +static const struct procfs_entry_s g_reset_procfs = +{ + "reset", &g_reset_procfs_ops, PROCFS_FILE_TYPE +}; + +static bool g_reset_procfs_added; + +#endif /* CONFIG_RESET_PROCFS */ + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -1005,6 +1336,21 @@ int reset_controller_register(FAR struct reset_controller_dev *rcdev) list_initialize(&rcdev->reset_control_head); nxmutex_lock(&g_reset_list_mutex); + +#ifdef CONFIG_RESET_PROCFS + /* procfs_register() wants to run before procfs is mounted, which holds: + * controllers register during board or architecture start up. It + * appends without checking for a duplicate, so the entry is claimed once + * for the lifetime of the system rather than whenever the list is empty. + */ + + if (!g_reset_procfs_added) + { + procfs_register(&g_reset_procfs); + g_reset_procfs_added = true; + } +#endif + list_add_after(&g_reset_controller_list, &rcdev->list); nxmutex_unlock(&g_reset_list_mutex); diff --git a/include/nuttx/reset/reset-controller.h b/include/nuttx/reset/reset-controller.h index 301b5bd66f0..54f3d2917d5 100644 --- a/include/nuttx/reset/reset-controller.h +++ b/include/nuttx/reset/reset-controller.h @@ -32,12 +32,32 @@ #include #include +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define RESET_NAME_MAX 24 +#define RESET_EXTRA_MAX 32 + /**************************************************************************** * Public Types ****************************************************************************/ struct reset_controller_dev; +/* What a controller can say about one reset line, beyond the asserted + * state that status() already reports. Both members are optional; an + * empty name is reported as - and the line by its id alone. + */ + +struct reset_lineinfo_s +{ + char name[RESET_NAME_MAX]; /* What this line resets */ + char extra[RESET_EXTRA_MAX]; /* Controller specific key:value fields, + * appended to the line's /proc/reset + * entry */ +}; + /* struct reset_control_ops - reset controller driver operations * reset: for self-deasserting resets, does all necessary * things to reset the device @@ -60,6 +80,21 @@ struct reset_control_ops unsigned int id); CODE int (*status)(FAR struct reset_controller_dev *rcdev, unsigned int id); + + /* Describe one reset line, for /proc/reset. + * + * Optional; a controller without it is listed by name alone. status() + * already reports whether a line is asserted, so this supplies only what + * the framework cannot derive: the line's name, and anything else the + * controller wants shown. Zero the structure and fill what applies. + * + * Returns OK, or -ENODEV for an id that names no line, which is how a + * gap in the numbering is reported. + */ + + CODE int (*get_line)(FAR struct reset_controller_dev *rcdev, + unsigned int id, + FAR struct reset_lineinfo_s *info); }; /* struct reset_controller_dev - reset controller entity that might @@ -68,6 +103,10 @@ struct reset_control_ops * ops: a pointer to device specific struct reset_control_ops * list: internal list of reset controller devices * reset_control_head: head of internal list of requested reset controls + * nlines: the id space, so get_line() is asked about 0 to nlines - 1. + * Controllers commonly leave gaps, which get_line() reports, so + * this is a bound rather than a count of real lines. Zero if the + * controller has no get_line(). */ struct reset_controller_dev @@ -76,6 +115,7 @@ struct reset_controller_dev FAR const struct reset_control_ops *ops; struct list_node list; struct list_node reset_control_head; + unsigned int nlines; }; /****************************************************************************