/* * Copyright (c) 2019-2021, Arm Limited. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include "dev.h" #define NR_MOUNT_POINTS 4 struct mount_point { chan_t *new; chan_t *old; }; /* This array contains all the available channels of the filesystem. * A file descriptor is the index of a specific channel in this array. */ static chan_t fdset[NR_CHANS]; /* This array contains all the available mount points of the filesystem. */ static struct mount_point mount_points[NR_MOUNT_POINTS]; /* This variable stores the channel associated to the root directory. */ static chan_t slash_channel; /* This function creates a channel from a device index and registers * it to fdset. */ static chan_t *create_new_channel(unsigned char index) { chan_t *channel = NULL; int i; for (i = 0; i < NR_CHANS; i++) { if (fdset[i].index == NODEV) { channel = &fdset[i]; channel->index = index; break; } } return channel; } /******************************************************************************* * This function returns a pointer to an existing channel in fdset from a file * descriptor. ******************************************************************************/ static chan_t *fd_to_channel(int fd) { if ((fd < 0) || (fd >= NR_CHANS) || (fdset[fd].index == NODEV)) { return NULL; } return &fdset[fd]; } /******************************************************************************* * This function returns a file descriptor from a channel. * The caller must be sure that the channel is registered in fdset. ******************************************************************************/ static int channel_to_fd(chan_t *channel) { return (channel == NULL) ? -1 : (channel - fdset); } /******************************************************************************* * This function checks the validity of a mode. ******************************************************************************/ static bool is_valid_mode(int mode) { if ((mode & O_READ) && (mode & (O_WRITE | O_RDWR))) { return false; } if ((mode & O_WRITE) && (mode & (O_READ | O_RDWR))) { return false; } if ((mode & O_RDWR) && (mode & (O_READ | O_WRITE))) { return false; } return true; } /******************************************************************************* * This function extracts the next part of the given path contained and puts it * in token. It returns a pointer to the remainder of the path. ******************************************************************************/ static const char *next(const char *path, char *token) { int index; const char *cursor; while (*path == '/') { ++path; } index = 0; cursor = path; if (*path != '\0') { while (*cursor != '/' && *cursor != '\0') { if (index == NAMELEN) { return NULL; } token[index++] = *cursor++; } } token[index] = '\0'; return cursor; } /******************************************************************************* * This function returns the driver index in devtab of the driver * identified by id. ******************************************************************************/ static int get_device_index(int id) { int index; dev_t * const *dp; for (index = 0, dp = devtab; *dp && (*dp)->id != id; ++dp) { index++; } if (*dp == NULL) { return -1; } return index; } /******************************************************************************* * This function clears a given channel fields ******************************************************************************/ static void channel_clear(chan_t *channel) { channel->offset = 0; channel->qid = 0; channel->index = NODEV; channel->dev = 0; channel->mode = 0; } /******************************************************************************* * This function closes the channel pointed to by c. ******************************************************************************/ void channel_close(chan_t *channel) { if (channel != NULL) { channel_clear(channel); } } /******************************************************************************* * This function copies data from src to dst after applying the offset of the * channel c. nbytes bytes are expected to be copied unless the data goes over * dst + len. * It returns the actual number of bytes that were copied. ******************************************************************************/ int buf_to_channel(chan_t *channel, void *dst, void *src, int nbytes, long len) { const char *addr = src; if ((channel == NULL) || (dst == NULL) || (src == NULL)) { return 0; } if (channel->offset >= len) { return 0; } if ((channel->offset + nbytes) > len) { nbytes = len - channel->offset; } memcpy(dst, addr + channel->offset, nbytes); channel->offset += nbytes; return nbytes; } /******************************************************************************* * This function checks whether a channel (identified by its device index and * qid) is registered as a mount point. * Returns a pointer to the channel it is mounted to when found, NULL otherwise. ******************************************************************************/ static chan_t *mount_point_to_channel(int index, qid_t qid) { chan_t *channel; struct mount_point *mp; for (mp = mount_points; mp < &mount_points[NR_MOUNT_POINTS]; mp++) { channel = mp->new; if (channel == NULL) { continue; } if ((channel->index == index) && (channel->qid == qid)) { return mp->old; } } return NULL; } /******************************************************************************* * This function calls the attach function of the driver identified by id. ******************************************************************************/ chan_t *attach(int id, int dev) { /* Get the devtab index for the driver identified by id */ int index = get_device_index(id); if (index < 0) { return NULL; } return devtab[index]->attach(id, dev); } /******************************************************************************* * This function is the default implementation of the driver attach function. * It creates a new channel and returns a pointer to it. ******************************************************************************/ chan_t *devattach(int id, int dev) { chan_t *channel; int index; index = get_device_index(id); if (index < 0) { return NULL; } channel = create_new_channel(index); if (channel == NULL) { return NULL; } channel->dev = dev; channel->qid = CHDIR; return channel; } /******************************************************************************* * This function returns a channel given a path. * It goes through the filesystem, from the root namespace ('/') or from a * device namespace ('#'), switching channel on mount points. ******************************************************************************/ chan_t *path_to_channel(const char *path, int mode) { int i, n; const char *path_next; chan_t *mnt, *channel; char elem[NAMELEN]; if (path == NULL) { return NULL; } switch (path[0]) { case '/': channel = clone(&slash_channel, NULL); path_next = path; break; case '#': path_next = next(path + 1, elem); if (path_next == NULL) { goto noent; } n = 0; for (i = 1; (elem[i] >= '0') && (elem[i] <= '9'); i++) { n += elem[i] - '0'; } if (elem[i] != '\0') { goto noent; } channel = attach(elem[0], n); break; default: return NULL; } if (channel == NULL) { return NULL; } for (path_next = next(path_next, elem); *elem; path_next = next(path_next, elem)) { if ((channel->qid & CHDIR) == 0) { goto notfound; } if (devtab[channel->index]->walk(channel, elem) < 0) { channel_close(channel); goto notfound; } mnt = mount_point_to_channel(channel->index, channel->qid); if (mnt != NULL) { clone(mnt, channel); } } if (path_next == NULL) { goto notfound; } /* TODO: check mode */ return channel; notfound: channel_close(channel); noent: return NULL; } /******************************************************************************* * This function calls the clone function of the driver associated to the * channel c. ******************************************************************************/ chan_t *clone(chan_t *c, chan_t *nc) { if (c->index == NODEV) { return NULL; } return devtab[c->index]->clone(c, nc); } /******************************************************************************* * This function is the default implementation of the driver clone function. * It creates a new channel and returns a pointer to it. * It clones channel into new_channel. ******************************************************************************/ chan_t *devclone(chan_t *channel, chan_t *new_channel) { if (channel == NULL) { return NULL; } if (new_channel == NULL) { new_channel = create_new_channel(channel->index); if (new_channel == NULL) { return NULL; } } new_channel->qid = channel->qid; new_channel->dev = channel->dev; new_channel->mode = channel->mode; new_channel->offset = channel->offset; new_channel->index = channel->index; return new_channel; } /******************************************************************************* * This function is the default implementation of the driver walk function. * It goes through all the elements of tab using the gen function until a match * is found with name. * If a match is found, it copies the qid of the new directory. ******************************************************************************/ int devwalk(chan_t *channel, const char *name, const dirtab_t *tab, int ntab, devgen_t *gen) { int i; dir_t dir; if ((channel == NULL) || (name == NULL) || (gen == NULL)) { return -1; } if ((name[0] == '.') && (name[1] == '\0')) { return 1; } for (i = 0; ; i++) { switch ((*gen)(channel, tab, ntab, i, &dir)) { case 0: /* Intentional fall-through */ case -1: return -1; case 1: if (strncmp(name, dir.name, NAMELEN) != 0) { continue; } channel->qid = dir.qid; return 1; } } } /******************************************************************************* * This is a helper function which exposes the content of a directory, element * by element. It is meant to be called until the end of the directory is * reached or an error occurs. * It returns -1 on error, 0 on end of directory and 1 when a new file is found. ******************************************************************************/ int dirread(chan_t *channel, dir_t *dir, const dirtab_t *tab, int ntab, devgen_t *gen) { int i, ret; if ((channel == NULL) || (dir == NULL) || (gen == NULL)) { return -1; } i = channel->offset/sizeof(dir_t); ret = (*gen)(channel, tab, ntab, i, dir); if (ret == 1) { channel->offset += sizeof(dir_t); } return ret; } /******************************************************************************* * This function sets the elements of dir. ******************************************************************************/ void make_dir_entry(chan_t *channel, dir_t *dir, const char *name, long length, qid_t qid, unsigned int mode) { if ((channel == NULL) || (dir == NULL) || (name == NULL)) { return; } strlcpy(dir->name, name, sizeof(dir->name)); dir->length = length; dir->qid = qid; dir->mode = mode; if ((qid & CHDIR) != 0) { dir->mode |= O_DIR; } dir->index = channel->index; dir->dev = channel->dev; } /******************************************************************************* * This function is the default implementation of the internal driver gen * function. * It copies and formats the information of the nth element of tab into dir. ******************************************************************************/ int devgen(chan_t *channel, const dirtab_t *tab, int ntab, int n, dir_t *dir) { const dirtab_t *dp; if ((channel == NULL) || (dir == NULL) || (tab == NULL) || (n >= ntab)) { return 0; } dp = &tab[n]; make_dir_entry(channel, dir, dp->name, dp->length, dp->qid, dp->perm); return 1; } /******************************************************************************* * This function returns a file descriptor identifying the channel associated to * the given path. ******************************************************************************/ int open(const char *path, int mode) { chan_t *channel; if (path == NULL) { return -1; } if (is_valid_mode(mode) == false) { return -1; } channel = path_to_channel(path, mode); return channel_to_fd(channel); } /******************************************************************************* * This function closes the channel identified by the file descriptor fd. ******************************************************************************/ int close(int fd) { chan_t *channel; channel = fd_to_channel(fd); if (channel == NULL) { return -1; } channel_close(channel); return 0; } /******************************************************************************* * This function is the default implementation of the driver stat function. * It goes through all the elements of tab using the gen function until a match * is found with file. * If a match is found, dir contains the information file. ******************************************************************************/ int devstat(chan_t *dirc, const char *file, dir_t *dir, const dirtab_t *tab, int ntab, devgen_t *gen) { int i, r = 0; chan_t *c, *mnt; if ((dirc == NULL) || (dir == NULL) || (gen == NULL)) { return -1; } c = path_to_channel(file, O_STAT); if (c == NULL) { return -1; } for (i = 0; ; i++) { switch ((*gen)(dirc, tab, ntab, i, dir)) { case 0: /* Intentional fall-through */ case -1: r = -1; goto leave; case 1: mnt = mount_point_to_channel(dir->index, dir->qid); if (mnt != NULL) { dir->qid = mnt->qid; dir->index = mnt->index; } if ((dir->qid != c->qid) || (dir->index != c->index)) { continue; } goto leave; } } leave: channel_close(c); return r; } /******************************************************************************* * This function calls the stat function of the driver associated to the parent * directory of the file in path. * The result is stored in dir. ******************************************************************************/ int stat(const char *path, dir_t *dir) { int r; size_t len; chan_t *channel; char *p, dirname[PATHLEN]; if ((path == NULL) || (dir == NULL)) { return -1; } len = strlen(path); if ((len + 1) > sizeof(dirname)) { return -1; } memcpy(dirname, path, len); for (p = dirname + len; p > dirname; --p) { if (*p != '/') { break; } } p = memrchr(dirname, '/', p - dirname); if (p == NULL) { return -1; } dirname[p - dirname + 1] = '\0'; channel = path_to_channel(dirname, O_STAT); if (channel == NULL) { return -1; } r = devtab[channel->index]->stat(channel, path, dir); channel_close(channel); return r; } /******************************************************************************* * This function calls the read function of the driver associated to fd. * It fills buf with at most n bytes. * It returns the number of bytes that were actually read. ******************************************************************************/ int read(int fd, void *buf, int n) { chan_t *channel; if (buf == NULL) { return -1; } channel = fd_to_channel(fd); if (channel == NULL) { return -1; } if (((channel->qid & CHDIR) != 0) && (n < sizeof(dir_t))) { return -1; } return devtab[channel->index]->read(channel, buf, n); } /******************************************************************************* * This function calls the write function of the driver associated to fd. * It writes at most n bytes of buf. * It returns the number of bytes that were actually written. ******************************************************************************/ int write(int fd, void *buf, int n) { chan_t *channel; if (buf == NULL) { return -1; } channel = fd_to_channel(fd); if (channel == NULL) { return -1; } if ((channel->qid & CHDIR) != 0) { return -1; } return devtab[channel->index]->write(channel, buf, n); } /******************************************************************************* * This function calls the seek function of the driver associated to fd. * It applies the offset off according to the strategy whence. ******************************************************************************/ int seek(int fd, long off, int whence) { chan_t *channel; channel = fd_to_channel(fd); if (channel == NULL) { return -1; } if ((channel->qid & CHDIR) != 0) { return -1; } return devtab[channel->index]->seek(channel, off, whence); } /******************************************************************************* * This function is the default error implementation of the driver mount * function. ******************************************************************************/ chan_t *deverrmount(chan_t *channel, const char *spec) { return NULL; } /******************************************************************************* * This function is the default error implementation of the driver write * function. ******************************************************************************/ int deverrwrite(chan_t *channel, void *buf, int n) { return -1; } /******************************************************************************* * This function is the default error implementation of the driver seek * function. ******************************************************************************/ int deverrseek(chan_t *channel, long off, int whence) { return -1; } /******************************************************************************* * This function is the default implementation of the driver seek function. * It applies the offset off according to the strategy whence to the channel c. ******************************************************************************/ int devseek(chan_t *channel, long off, int whence) { switch (whence) { case KSEEK_SET: channel->offset = off; break; case KSEEK_CUR: channel->offset += off; break; case KSEEK_END: /* Not implemented */ return -1; } return 0; } /******************************************************************************* * This function registers the channel associated to the path new as a mount * point for the channel c. ******************************************************************************/ static int add_mount_point(chan_t *channel, const char *new) { int i; chan_t *cn; struct mount_point *mp; if (new == NULL) { goto err0; } cn = path_to_channel(new, O_READ); if (cn == NULL) { goto err0; } if ((cn->qid & CHDIR) == 0) { goto err1; } for (i = NR_MOUNT_POINTS - 1; i >= 0; i--) { mp = &mount_points[i]; if (mp->new == NULL) { break; } } if (i < 0) { goto err1; } mp->new = cn; mp->old = channel; return 0; err1: channel_close(cn); err0: return -1; } /******************************************************************************* * This function registers the path new as a mount point for the path old. ******************************************************************************/ int bind(const char *old, const char *new) { chan_t *channel; channel = path_to_channel(old, O_BIND); if (channel == NULL) { return -1; } if (add_mount_point(channel, new) < 0) { channel_close(channel); return -1; } return 0; } /******************************************************************************* * This function calls the mount function of the driver associated to the path * srv. * It mounts the path srv on the path where. ******************************************************************************/ int mount(const char *srv, const char *where, const char *spec) { chan_t *channel, *mount_point_chan; int ret; channel = path_to_channel(srv, O_RDWR); if (channel == NULL) { goto err0; } mount_point_chan = devtab[channel->index]->mount(channel, spec); if (mount_point_chan == NULL) { goto err1; } ret = add_mount_point(mount_point_chan, where); if (ret < 0) { goto err2; } channel_close(channel); return 0; err2: channel_close(mount_point_chan); err1: channel_close(channel); err0: return -1; } /******************************************************************************* * This function initializes the device environment. * It creates the '/' channel. * It links the device drivers to the physical drivers. ******************************************************************************/ void debugfs_init(void) { chan_t *channel, *cloned_channel; for (channel = fdset; channel < &fdset[NR_CHANS]; channel++) { channel_clear(channel); } channel = devattach('/', 0); if (channel == NULL) { panic(); } cloned_channel = clone(channel, &slash_channel); if (cloned_channel == NULL) { panic(); } channel_close(channel); devlink(); } __dead2 void devpanic(const char *cause) { panic(); }