#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: firmload.c,v 1.24 2022/10/26 23:39:10 riastradh Exp $");
#include <sys/param.h>
#include <sys/fcntl.h>
#include <sys/filedesc.h>
#include <sys/kmem.h>
#include <sys/namei.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/vnode.h>
#include <sys/kauth.h>
#include <sys/lwp.h>
#include <dev/firmload.h>
struct firmware_handle {
struct vnode *fh_vp;
off_t fh_size;
};
static firmware_handle_t
firmware_handle_alloc(void)
{
return (kmem_alloc(sizeof(struct firmware_handle), KM_SLEEP));
}
static void
firmware_handle_free(firmware_handle_t fh)
{
kmem_free(fh, sizeof(*fh));
}
#if !defined(FIRMWARE_PATHS)
#define FIRMWARE_PATHS \
"/libdata/firmware:/usr/libdata/firmware:/usr/pkg/libdata/firmware:/usr/pkg/libdata"
#endif
static char firmware_paths[PATH_MAX+1] = FIRMWARE_PATHS;
static int
sysctl_hw_firmware_path(SYSCTLFN_ARGS)
{
int error, i;
char newpath[PATH_MAX+1];
struct sysctlnode node;
char expected_char;
node = *rnode;
node.sysctl_data = &newpath[0];
memcpy(node.sysctl_data, rnode->sysctl_data, PATH_MAX+1);
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return (error);
expected_char = '/';
for (i = 0; i < PATH_MAX+1; i++) {
if (expected_char != 0 && newpath[i] != expected_char)
return (EINVAL);
if (newpath[i] == '\0')
break;
else if (newpath[i] == ':')
expected_char = '/';
else
expected_char = 0;
}
memcpy(rnode->sysctl_data, node.sysctl_data, PATH_MAX+1);
return (0);
}
SYSCTL_SETUP(sysctl_hw_firmware_setup, "sysctl hw.firmware subtree setup")
{
const struct sysctlnode *firmware_node;
if (sysctl_createv(clog, 0, NULL, &firmware_node,
CTLFLAG_PERMANENT,
CTLTYPE_NODE, "firmware", NULL,
NULL, 0, NULL, 0,
CTL_HW, CTL_CREATE, CTL_EOL) != 0)
return;
sysctl_createv(clog, 0, NULL, NULL,
CTLFLAG_READWRITE,
CTLTYPE_STRING, "path",
SYSCTL_DESCR("Device firmware loading path list"),
sysctl_hw_firmware_path, 0, firmware_paths, PATH_MAX+1,
CTL_HW, firmware_node->sysctl_num, CTL_CREATE, CTL_EOL);
}
static char *
firmware_path_next(const char *drvname, const char *imgname, char *pnbuf,
char **prefixp)
{
char *prefix = *prefixp;
size_t maxprefix, i;
if (prefix == NULL
|| *prefix != '/') {
*prefixp = NULL;
return (NULL);
}
maxprefix = MAXPATHLEN -
(1
+ strlen(drvname)
+ 1
+ strlen(imgname)
+ 1 );
if (maxprefix > MAXPATHLEN) {
*prefixp = NULL;
return (NULL);
}
for (i = 0; i < maxprefix; i++) {
if (*prefix == ':' || *prefix == '\0')
break;
pnbuf[i] = *prefix++;
}
if (*prefix != ':' && *prefix != '\0') {
*prefixp = NULL;
return (NULL);
}
if (*prefix != '\0')
prefix++;
*prefixp = prefix;
KASSERT(MAXPATHLEN >= i);
snprintf(pnbuf + i, MAXPATHLEN - i, "/%s/%s", drvname, imgname);
return (pnbuf);
}
static char *
firmware_path_first(const char *drvname, const char *imgname, char *pnbuf,
char **prefixp)
{
*prefixp = firmware_paths;
return (firmware_path_next(drvname, imgname, pnbuf, prefixp));
}
int
firmware_open(const char *drvname, const char *imgname, firmware_handle_t *fhp)
{
struct pathbuf *pb;
struct vattr va;
char *pnbuf, *path, *prefix;
firmware_handle_t fh;
struct vnode *vp;
int error;
if (drvname == NULL || imgname == NULL)
return (EINVAL);
if (cwdi0.cwdi_cdir == NULL) {
printf("firmware_open(%s/%s) called too early.\n",
drvname, imgname);
return ENOENT;
}
pnbuf = PNBUF_GET();
KASSERT(pnbuf != NULL);
fh = firmware_handle_alloc();
KASSERT(fh != NULL);
error = 0;
for (path = firmware_path_first(drvname, imgname, pnbuf, &prefix);
path != NULL;
path = firmware_path_next(drvname, imgname, pnbuf, &prefix)) {
pb = pathbuf_create(path);
if (pb == NULL) {
error = ENOMEM;
break;
}
error = vn_open(NULL, pb, NOCHROOT, FREAD, 0, &vp, NULL, NULL);
pathbuf_destroy(pb);
if (error == ENOENT) {
continue;
}
break;
}
PNBUF_PUT(pnbuf);
if (error) {
firmware_handle_free(fh);
return (error);
}
error = VOP_GETATTR(vp, &va, kauth_cred_get());
if (error) {
VOP_UNLOCK(vp);
(void)vn_close(vp, FREAD, kauth_cred_get());
firmware_handle_free(fh);
return (error);
}
if (va.va_type != VREG) {
VOP_UNLOCK(vp);
(void)vn_close(vp, FREAD, kauth_cred_get());
firmware_handle_free(fh);
return (EINVAL);
}
fh->fh_vp = vp;
fh->fh_size = va.va_size;
VOP_UNLOCK(vp);
*fhp = fh;
return (0);
}
int
firmware_close(firmware_handle_t fh)
{
int error;
error = vn_close(fh->fh_vp, FREAD, kauth_cred_get());
firmware_handle_free(fh);
return (error);
}
off_t
firmware_get_size(firmware_handle_t fh)
{
return (fh->fh_size);
}
int
firmware_read(firmware_handle_t fh, off_t offset, void *buf, size_t len)
{
return (vn_rdwr(UIO_READ, fh->fh_vp, buf, len, offset,
UIO_SYSSPACE, 0, kauth_cred_get(), NULL, curlwp));
}
void *
firmware_malloc(size_t size)
{
return (kmem_alloc(size, KM_SLEEP));
}
void
firmware_free(void *v, size_t size)
{
kmem_free(v, size);
}