/* * Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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 /* printk() */ #include /* kmalloc() */ #include /* error codes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct vmtd_dev { struct vs_config_info config; uint64_t size; /* Device size in bytes */ uint32_t ivc_id; uint32_t ivm_id; struct tegra_hv_ivc_cookie *ivck; struct tegra_hv_ivm_cookie *ivmk; struct device *device; void *shared_buffer; struct mutex lock; struct completion msg_complete; void *cmd_frame; struct mtd_info mtd; bool is_setup; }; #define IVC_RESET_RETRIES 30 static inline struct vmtd_dev *mtd_to_vmtd(struct mtd_info *mtd) { return container_of(mtd, struct vmtd_dev, mtd); } static irqreturn_t ivc_irq_handler(int irq, void *data) { struct vmtd_dev *vmtddev = (struct vmtd_dev *)data; complete(&vmtddev->msg_complete); return IRQ_HANDLED; } static int vmtd_send_cmd(struct vmtd_dev *vmtddev, struct vs_request *vs_req) { /* This while loop exits as long as the remote endpoint cooperates. */ while (tegra_hv_ivc_channel_notified(vmtddev->ivck) != 0) { wait_for_completion(&vmtddev->msg_complete); } while (!tegra_hv_ivc_can_write(vmtddev->ivck)) { wait_for_completion(&vmtddev->msg_complete); } if (tegra_hv_ivc_write(vmtddev->ivck, vs_req, sizeof(struct vs_request)) != sizeof(struct vs_request)) { dev_err(vmtddev->device, "Request sending failed!\n"); return -EIO; } return 0; } static int vmtd_get_resp(struct vmtd_dev *vmtddev, struct vs_request *vs_req) { /* This while loop exits as long as the remote endpoint cooperates. */ while (tegra_hv_ivc_channel_notified(vmtddev->ivck) != 0) { wait_for_completion(&vmtddev->msg_complete); } while (!tegra_hv_ivc_can_read(vmtddev->ivck)) { wait_for_completion(&vmtddev->msg_complete); } if (tegra_hv_ivc_read(vmtddev->ivck, vs_req, sizeof(struct vs_request)) != sizeof(struct vs_request)) { dev_err(vmtddev->device, "Response fetching failed!\n"); return -EIO; } return 0; } static int vmtd_process_request(struct vmtd_dev *vmtddev, struct vs_request *vs_req) { uint32_t num_bytes = vs_req->mtddev_req.mtd_req.size; loff_t offset = vs_req->mtddev_req.mtd_req.offset; int32_t ret = 0; ret = vmtd_send_cmd(vmtddev, vs_req); if (ret != 0) { dev_err(vmtddev->device, "Sending %d failed!\n", vs_req->mtddev_req.req_op); goto fail; } vs_req = (struct vs_request *)vmtddev->cmd_frame; ret = vmtd_get_resp(vmtddev, vs_req); if (ret != 0) { dev_err(vmtddev->device, "fetching response failed!\n"); goto fail; } if ((vs_req->status != 0) || (vs_req->mtddev_resp.mtd_resp.status != 0)) { dev_err(vmtddev->device, "Response status for offset %llx size %x failed!\n", offset, num_bytes); ret = -EIO; goto fail; } if (vs_req->mtddev_resp.mtd_resp.size != num_bytes) { dev_err(vmtddev->device, "size mismatch for offset %llx size %x returned %x!\n", offset, num_bytes, vs_req->mtddev_resp.mtd_resp.size); ret = -EIO; goto fail; } fail: return ret; } static int vmtd_get_configinfo(struct vmtd_dev *vmtddev, struct vs_config_info *config) { struct vs_request *vs_req = (struct vs_request *)vmtddev->cmd_frame; /* This while loop exits as long as the remote endpoint cooperates. */ while (!tegra_hv_ivc_can_read(vmtddev->ivck)) { wait_for_completion(&vmtddev->msg_complete); } if (!tegra_hv_ivc_read(vmtddev->ivck, vs_req, sizeof(struct vs_request))) { dev_err(vmtddev->device, "config fetching failed!\n"); return -EIO; } if (vs_req->status != 0) { dev_err(vmtddev->device, "Config fetch request failed!\n"); return -EINVAL; } *config = vs_req->config_info; return 0; } /* * Read an address range from the flash chip. The address range * may be any size provided it is within the physical boundaries. */ static int vmtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct vmtd_dev *vmtddev = mtd_to_vmtd(mtd); struct vs_request *vs_req; size_t remaining_size = len; size_t read_size; u_char *buf_addr = buf; loff_t offset = from; int32_t ret = 0; dev_dbg(vmtddev->device, "%s from 0x%llx, len %zd\n", __func__, offset, remaining_size); if (((offset + remaining_size) < offset) || ((offset + remaining_size) > vmtddev->mtd.size)) { dev_err(vmtddev->device, "from %llx len %lx out of range!\n", offset, remaining_size); return -EPERM; } mutex_lock(&vmtddev->lock); while (remaining_size) { read_size = min( (size_t)vmtddev->config.mtd_config.max_read_bytes_per_io, remaining_size); vs_req = (struct vs_request *)vmtddev->cmd_frame; vs_req->type = VS_DATA_REQ; vs_req->mtddev_req.req_op = VS_MTD_READ; vs_req->mtddev_req.mtd_req.offset = offset; vs_req->mtddev_req.mtd_req.size = read_size; vs_req->mtddev_req.mtd_req.data_offset = 0; vs_req->req_id = 0; ret = vmtd_process_request(vmtddev, vs_req); if (ret != 0) { dev_err(vmtddev->device, "Read for offset %llx size %lx failed!\n", offset, read_size); goto fail; } memcpy(buf_addr, vmtddev->shared_buffer, read_size); buf_addr += read_size; offset += read_size; remaining_size -= read_size; } *retlen = len; fail: mutex_unlock(&vmtddev->lock); return ret; } /* * Write an address range from the flash chip. The address range * may be any size provided it is within the physical boundaries. */ static int vmtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { struct vmtd_dev *vmtddev = mtd_to_vmtd(mtd); struct vs_request *vs_req; size_t remaining_size = len; size_t write_size; const u_char *buf_addr = buf; loff_t offset = to; int32_t ret = 0; dev_dbg(vmtddev->device, "%s from 0x%08x, len %zd\n", __func__, (u32)offset, remaining_size); if (((offset + remaining_size) < offset) || ((offset + remaining_size) > vmtddev->mtd.size)) { dev_err(vmtddev->device, "to %llx len %lx out of range!\n", offset, remaining_size); return -EPERM; } mutex_lock(&vmtddev->lock); while (remaining_size) { write_size = min( (size_t)vmtddev->config.mtd_config.max_write_bytes_per_io, remaining_size); vs_req = (struct vs_request *)vmtddev->cmd_frame; vs_req->type = VS_DATA_REQ; vs_req->mtddev_req.req_op = VS_MTD_WRITE; vs_req->mtddev_req.mtd_req.offset = offset; vs_req->mtddev_req.mtd_req.size = write_size; vs_req->mtddev_req.mtd_req.data_offset = 0; vs_req->req_id = 0; memcpy(vmtddev->shared_buffer, buf_addr, write_size); ret = vmtd_process_request(vmtddev, vs_req); if (ret != 0) { dev_err(vmtddev->device, "write for offset %llx size %lx failed!\n", offset, write_size); goto fail; } buf_addr += write_size; offset += write_size; remaining_size -= write_size; } *retlen = len; fail: mutex_unlock(&vmtddev->lock); return ret; } /* * Erase an address range from the flash chip. The address range * may be any size provided it is within the physical boundaries. */ static int vmtd_erase(struct mtd_info *mtd, struct erase_info *instr) { struct vmtd_dev * vmtddev = mtd_to_vmtd(mtd); struct vs_request *vs_req; int32_t ret = 0; dev_dbg(vmtddev->device, "%s from 0x%08x, len %llx\n", __func__, (u32)instr->addr, instr->len); if (((instr->addr + instr->len) < instr->addr) || ((instr->addr + instr->len) > vmtddev->mtd.size)) { dev_err(vmtddev->device, "addr %llx len %llx out of range!\n", instr->addr, instr->len); return -EPERM; } mutex_lock(&vmtddev->lock); vs_req = (struct vs_request *)vmtddev->cmd_frame; vs_req->type = VS_DATA_REQ; vs_req->mtddev_req.req_op = VS_MTD_ERASE; vs_req->mtddev_req.mtd_req.offset = instr->addr; vs_req->mtddev_req.mtd_req.size = instr->len; vs_req->mtddev_req.mtd_req.data_offset = 0; vs_req->req_id = 0; ret = vmtd_process_request(vmtddev, vs_req); if (ret != 0) { dev_err(vmtddev->device, "Erase for offset %llx size %llx failed!\n", instr->addr, instr->len); mutex_unlock(&vmtddev->lock); goto fail; } mutex_unlock(&vmtddev->lock); mtd_erase_callback(instr); fail: if (ret != 0) instr->state = MTD_ERASE_FAILED; else instr->state = MTD_ERASE_DONE; return ret; } #ifdef CONFIG_PM_SLEEP static int tegra_virt_mtd_suspend(struct device *dev) { struct vmtd_dev *vmtddev = dev_get_drvdata(dev); if (vmtddev->is_setup) { mutex_lock(&vmtddev->lock); disable_irq(vmtddev->ivck->irq); /* Reset the channel */ tegra_hv_ivc_channel_reset(vmtddev->ivck); } return 0; } static int tegra_virt_mtd_resume(struct device *dev) { struct vmtd_dev *vmtddev = dev_get_drvdata(dev); if (vmtddev->is_setup) { enable_irq(vmtddev->ivck->irq); mutex_unlock(&vmtddev->lock); } return 0; } static const struct dev_pm_ops tegra_hv_vmtd_pm_ops = { .suspend = tegra_virt_mtd_suspend, .resume = tegra_virt_mtd_resume, }; #endif /* CONFIG_PM_SLEEP */ static int vmtd_setup_device(struct vmtd_dev *vmtddev) { mutex_init(&vmtddev->lock); vmtddev->mtd.name = "virt_mtd"; vmtddev->mtd.type = MTD_NORFLASH; vmtddev->mtd.writesize = 1; vmtddev->mtd.flags = MTD_CAP_NORFLASH; vmtddev->mtd.size = vmtddev->config.mtd_config.size; dev_info(vmtddev->device, "size %lld!\n", vmtddev->config.mtd_config.size); vmtddev->mtd._erase = vmtd_erase; vmtddev->mtd._read = vmtd_read; vmtddev->mtd._write = vmtd_write; vmtddev->mtd.erasesize = vmtddev->config.mtd_config.erase_size; if (vmtddev->ivmk->size < vmtddev->config.mtd_config.max_read_bytes_per_io) { dev_info(vmtddev->device, "Consider increasing mempool size to %d!\n", vmtddev->config.mtd_config.max_read_bytes_per_io); vmtddev->config.mtd_config.max_read_bytes_per_io = vmtddev->ivmk->size; } if (vmtddev->ivmk->size < vmtddev->config.mtd_config.max_write_bytes_per_io) { dev_info(vmtddev->device, "Consider increasing mempool size to %d!\n", vmtddev->config.mtd_config.max_write_bytes_per_io); vmtddev->config.mtd_config.max_write_bytes_per_io = vmtddev->ivmk->size; } vmtddev->mtd.dev.parent = vmtddev->device; vmtddev->mtd.writebufsize = 1; mtd_set_of_node(&vmtddev->mtd, vmtddev->device->of_node); return mtd_device_parse_register(&vmtddev->mtd, NULL, NULL, NULL, 0); } static int32_t vmtd_init_device(struct vmtd_dev *vmtddev) { struct vs_request *vs_req = (struct vs_request *)vmtddev->cmd_frame; uint32_t i = 0; int32_t ret = 0; /* This while loop exits as long as the remote endpoint cooperates. */ pr_notice("vmtd: send_config wait for ivc channel notified\n"); while (tegra_hv_ivc_channel_notified(vmtddev->ivck) != 0) { if (i++ > IVC_RESET_RETRIES) { dev_err(vmtddev->device, "ivc reset timeout\n"); return -ENOMEDIUM; } set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(msecs_to_jiffies(1)); } vs_req->type = VS_CONFIGINFO_REQ; dev_info(vmtddev->device, "send config cmd to ivc #%d\n", vmtddev->ivc_id); ret = vmtd_send_cmd(vmtddev, vs_req); if (ret != 0) { dev_err(vmtddev->device, "Sending %d failed!\n", vs_req->type); return ret; } ret = vmtd_get_configinfo(vmtddev, &vmtddev->config); if (ret != 0) { dev_err(vmtddev->device, "fetching configinfo failed!\n"); return ret; } if (vmtddev->config.type != VS_MTD_DEV) { dev_err(vmtddev->device, "Non mtd Config not supported - unexpected response!\n"); return -EINVAL; } if (vmtddev->config.mtd_config.size == 0) { dev_err(vmtddev->device, "virtual storage device size 0!\n"); return -EINVAL; } ret = vmtd_setup_device(vmtddev); if (ret != 0) { dev_err(vmtddev->device, "Setting up vmtd devices failed!\n"); return ret; } vmtddev->is_setup = true; return ret; } static int tegra_virt_mtd_probe(struct platform_device *pdev) { struct device_node __maybe_unused *np; struct device *dev = &pdev->dev; struct vmtd_dev *vmtddev; struct tegra_hv_ivm_cookie *ivmk; int ret; if (!is_tegra_hypervisor_mode()) { dev_err(dev, "Not running on Drive Hypervisor!\n"); return -ENODEV; } np = dev->of_node; if (np == NULL) { dev_err(dev, "No of_node data\n"); return -ENODEV; } vmtddev = devm_kzalloc(dev, sizeof(struct vmtd_dev), GFP_KERNEL); if (vmtddev == NULL) return -ENOMEM; platform_set_drvdata(pdev, vmtddev); vmtddev->device = dev; if (of_property_read_u32_index(np, "ivc", 1, &(vmtddev->ivc_id))) { dev_err(dev, "Failed to read ivc property\n"); return -ENODEV; } if (of_property_read_u32_index(np, "mempool", 0, &(vmtddev->ivm_id))) { dev_err(dev, "Failed to read mempool property\n"); return -ENODEV; } vmtddev->ivck = tegra_hv_ivc_reserve(NULL, vmtddev->ivc_id, NULL); if (IS_ERR_OR_NULL(vmtddev->ivck)) { dev_err(dev, "Failed to reserve IVC channel %d\n", vmtddev->ivc_id); vmtddev->ivck = NULL; return -ENODEV; } ivmk = tegra_hv_mempool_reserve(vmtddev->ivm_id); if (IS_ERR_OR_NULL(ivmk)) { dev_err(dev, "Failed to reserve IVM channel %d\n", vmtddev->ivm_id); ivmk = NULL; ret = -ENODEV; goto free_ivc; } vmtddev->ivmk = ivmk; vmtddev->shared_buffer = devm_memremap(vmtddev->device, ivmk->ipa, ivmk->size, MEMREMAP_WB); if (IS_ERR_OR_NULL(vmtddev->shared_buffer)) { dev_err(dev, "Failed to map mempool area %d\n", vmtddev->ivm_id); ret = -ENOMEM; goto free_mempool; } if ((vmtddev->ivck->frame_size < sizeof(struct vs_request))) { dev_err(dev, "Frame size %d less than ivc_req %ld!\n", vmtddev->ivck->frame_size, sizeof(struct vs_request)); ret = -ENOMEM; goto free_mempool; } vmtddev->cmd_frame = devm_kmalloc(vmtddev->device, vmtddev->ivck->frame_size, GFP_KERNEL); if (vmtddev->cmd_frame == NULL) { ret = -ENOMEM; goto free_mempool; } init_completion(&vmtddev->msg_complete); if (devm_request_irq(vmtddev->device, vmtddev->ivck->irq, ivc_irq_handler, 0, "vmtd", vmtddev)) { dev_err(dev, "Failed to request irq %d\n", vmtddev->ivck->irq); ret = -EINVAL; goto free_mempool; } tegra_hv_ivc_channel_reset(vmtddev->ivck); if (vmtd_init_device(vmtddev) != 0) { dev_err(dev, "Failed to initialize mtd device\n"); ret = -EINVAL; goto free_mempool; } return 0; free_mempool: tegra_hv_mempool_unreserve(vmtddev->ivmk); free_ivc: tegra_hv_ivc_unreserve(vmtddev->ivck); return ret; } static int tegra_virt_mtd_remove(struct platform_device *pdev) { struct vmtd_dev *vmtddev = platform_get_drvdata(pdev); tegra_hv_ivc_unreserve(vmtddev->ivck); tegra_hv_mempool_unreserve(vmtddev->ivmk); return 0; } #ifdef CONFIG_OF static struct of_device_id tegra_virt_mtd_match[] = { { .compatible = "nvidia,tegra-virt-mtd-storage", }, {}, }; MODULE_DEVICE_TABLE(of, tegra_virt_mtd_match); #endif /* CONFIG_OF */ static struct platform_driver tegra_virt_mtd_driver = { .probe = tegra_virt_mtd_probe, .remove = tegra_virt_mtd_remove, .driver = { .name = "Virtual MTD device", .owner = THIS_MODULE, .of_match_table = of_match_ptr(tegra_virt_mtd_match), #ifdef CONFIG_PM_SLEEP .pm = &tegra_hv_vmtd_pm_ops, #endif }, }; module_platform_driver(tegra_virt_mtd_driver); MODULE_AUTHOR("Vishal Annapurve "); MODULE_DESCRIPTION("VIRT MTD driver"); MODULE_LICENSE("GPL v2");