/* * hostfile.c - use files from the host to simalute barebox devices * * Copyright (c) 2007 Sascha Hauer , Pengutronix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include struct hf_priv { union { struct block_device blk; struct cdev cdev; }; const char *filename; int fd; }; static ssize_t hf_read(struct hf_priv *priv, void *buf, size_t count, loff_t offset, ulong flags) { int fd = priv->fd; if (linux_lseek(fd, offset) != offset) return -EINVAL; return linux_read(fd, buf, count); } static ssize_t hf_write(struct hf_priv *priv, const void *buf, size_t count, loff_t offset, ulong flags) { int fd = priv->fd; if (linux_lseek(fd, offset) != offset) return -EINVAL; return linux_write(fd, buf, count); } static ssize_t hf_cdev_read(struct cdev *cdev, void *buf, size_t count, loff_t offset, ulong flags) { return hf_read(cdev->priv, buf, count, offset, flags); } static ssize_t hf_cdev_write(struct cdev *cdev, const void *buf, size_t count, loff_t offset, ulong flags) { return hf_write(cdev->priv, buf, count, offset, flags); } static struct cdev_operations hf_cdev_ops = { .read = hf_cdev_read, .write = hf_cdev_write, }; static int hf_blk_read(struct block_device *blk, void *buf, sector_t block, blkcnt_t num_blocks) { ssize_t ret = hf_read(container_of(blk, struct hf_priv, blk), buf, num_blocks << SECTOR_SHIFT, block << SECTOR_SHIFT, 0); return ret > 0 ? 0 : ret; } static int hf_blk_write(struct block_device *blk, const void *buf, sector_t block, blkcnt_t num_blocks) { ssize_t ret = hf_write(container_of(blk, struct hf_priv, blk), buf, num_blocks << SECTOR_SHIFT, block << SECTOR_SHIFT, 0); return ret > 0 ? 0 : ret; } static struct block_device_ops hf_blk_ops = { .read = hf_blk_read, .write = hf_blk_write, }; static void hf_info(struct device_d *dev) { struct hf_priv *priv = dev->priv; printf("file: %s\n", priv->filename); } static int hf_probe(struct device_d *dev) { struct device_node *np = dev->device_node; struct hf_priv *priv = xzalloc(sizeof(*priv)); struct cdev *cdev; bool is_blockdev; u64 reg[2]; int err; if (!np) return -ENODEV; err = of_property_read_u64_array(np, "reg", reg, ARRAY_SIZE(reg)); if (err) return err; of_property_read_u32(np, "barebox,fd", &priv->fd); err = of_property_read_string(np, "barebox,filename", &priv->filename); if (err) return err; if (priv->fd < 0) return priv->fd; dev->info = hf_info; dev->priv = priv; is_blockdev = of_property_read_bool(np, "barebox,blockdev"); cdev = is_blockdev ? &priv->blk.cdev : &priv->cdev; cdev->device_node = np; if (is_blockdev) { cdev->name = np->name; priv->blk.dev = dev; priv->blk.ops = &hf_blk_ops; priv->blk.blockbits = SECTOR_SHIFT; priv->blk.num_blocks = reg[1] / SECTOR_SIZE; err = blockdevice_register(&priv->blk); if (err) return err; err = parse_partition_table(&priv->blk); if (err) dev_warn(dev, "No partition table found\n"); dev_info(dev, "registered as block device\n"); } else { cdev->name = np->name; cdev->dev = dev; cdev->ops = &hf_cdev_ops; cdev->size = reg[1]; cdev->priv = priv; err = devfs_create(cdev); if (err) return err; dev_info(dev, "registered as character device\n"); } of_parse_partitions(cdev, np); of_partitions_register_fixup(cdev); return 0; } static __maybe_unused struct of_device_id hostfile_dt_ids[] = { { .compatible = "barebox,hostfile", }, { /* sentinel */ } }; static struct driver_d hf_drv = { .name = "hostfile", .of_compatible = DRV_OF_COMPAT(hostfile_dt_ids), .probe = hf_probe, }; postcore_platform_driver(hf_drv); static int of_hostfile_fixup(struct device_node *root, void *ctx) { struct hf_info *hf = ctx; struct device_node *node; bool name_only = false; int ret; node = of_get_child_by_name(root, hf->devname); if (node) name_only = true; else node = of_new_node(root, hf->devname); ret = of_property_write_string(node, "barebox,filename", hf->filename); if (ret) return ret; if (name_only) return 0; ret = of_property_write_string(node, "compatible", hostfile_dt_ids->compatible); if (ret) return ret; ret = of_property_write_bool(node, "barebox,blockdev", hf->is_blockdev); if (ret) return ret; ret = of_property_write_bool(node, "barebox,cdev", hf->is_cdev); if (ret) return ret; return of_property_write_bool(node, "barebox,read-only", hf->is_readonly); } int barebox_register_filedev(struct hf_info *hf) { return of_register_fixup(of_hostfile_fixup, hf); } static int of_hostfile_map_fixup(struct device_node *root, void *ctx) { struct device_node *node; int ret; for_each_compatible_node_from(node, root, NULL, hostfile_dt_ids->compatible) { struct hf_info hf = {}; uint64_t reg[2] = {}; bool no_filename; hf.devname = node->name; ret = of_property_read_string(node, "barebox,filename", &hf.filename); no_filename = ret; hf.is_blockdev = of_property_read_bool(node, "barebox,blockdev"); hf.is_cdev = of_property_read_bool(node, "barebox,cdev"); hf.is_readonly = of_property_read_bool(node, "barebox,read-only"); of_property_read_u64_array(node, "reg", reg, ARRAY_SIZE(reg)); hf.base = reg[0]; hf.size = reg[1]; ret = linux_open_hostfile(&hf); if (ret) goto out; reg[0] = hf.base; reg[1] = hf.size; ret = of_property_write_u64_array(node, "reg", reg, ARRAY_SIZE(reg)); if (ret) goto out; ret = of_property_write_bool(node, "barebox,blockdev", hf.is_blockdev); if (ret) goto out; if (no_filename) { ret = of_property_write_string(node, "barebox,filename", hf.filename); if (ret) goto out; } ret = of_property_write_u32(node, "barebox,fd", hf.fd); out: if (ret) pr_err("error fixing up %s: %pe\n", hf.devname, ERR_PTR(ret)); } return 0; } static int barebox_fixup_filedevs(void) { return of_register_fixup(of_hostfile_map_fixup, NULL); } pure_initcall(barebox_fixup_filedevs);