/* * Copyright (C) IBM Corporation 2017 * * 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 * MERGCHANTABILITY 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 #include #include #include #include #include #define CREATE_TRACE_POINTS #include /* * The SBEFIFO is a pipe-like FSI device for communicating with * the self boot engine on POWER processors. */ #define DEVICE_NAME "sbefifo" #define FSI_ENGID_SBE 0x22 /* * Register layout */ /* Register banks */ #define SBEFIFO_UP 0x00 /* FSI -> Host */ #define SBEFIFO_DOWN 0x40 /* Host -> FSI */ /* Per-bank registers */ #define SBEFIFO_FIFO 0x00 /* The FIFO itself */ #define SBEFIFO_STS 0x04 /* Status register */ #define SBEFIFO_STS_PARITY_ERR 0x20000000 #define SBEFIFO_STS_RESET_REQ 0x02000000 #define SBEFIFO_STS_GOT_EOT 0x00800000 #define SBEFIFO_STS_MAX_XFER_LIMIT 0x00400000 #define SBEFIFO_STS_FULL 0x00200000 #define SBEFIFO_STS_EMPTY 0x00100000 #define SBEFIFO_STS_ECNT_MASK 0x000f0000 #define SBEFIFO_STS_ECNT_SHIFT 16 #define SBEFIFO_STS_VALID_MASK 0x0000ff00 #define SBEFIFO_STS_VALID_SHIFT 8 #define SBEFIFO_STS_EOT_MASK 0x000000ff #define SBEFIFO_STS_EOT_SHIFT 0 #define SBEFIFO_EOT_RAISE 0x08 /* (Up only) Set End Of Transfer */ #define SBEFIFO_REQ_RESET 0x0C /* (Up only) Reset Request */ #define SBEFIFO_PERFORM_RESET 0x10 /* (Down only) Perform Reset */ #define SBEFIFO_EOT_ACK 0x14 /* (Down only) Acknowledge EOT */ #define SBEFIFO_DOWN_MAX 0x18 /* (Down only) Max transfer */ /* FIFO depth */ #define SBEFIFO_FIFO_DEPTH 8 /* Helpers */ #define sbefifo_empty(sts) ((sts) & SBEFIFO_STS_EMPTY) #define sbefifo_full(sts) ((sts) & SBEFIFO_STS_FULL) #define sbefifo_parity_err(sts) ((sts) & SBEFIFO_STS_PARITY_ERR) #define sbefifo_populated(sts) (((sts) & SBEFIFO_STS_ECNT_MASK) >> SBEFIFO_STS_ECNT_SHIFT) #define sbefifo_vacant(sts) (SBEFIFO_FIFO_DEPTH - sbefifo_populated(sts)) #define sbefifo_eot_set(sts) (((sts) & SBEFIFO_STS_EOT_MASK) >> SBEFIFO_STS_EOT_SHIFT) /* Reset request timeout in ms */ #define SBEFIFO_RESET_TIMEOUT 10000 /* Range of sleep while trying to read/write the FIFO */ /* XXX Adjust these */ #define SBEFIFO_WAIT_RANGE_MIN 1 #define SBEFIFO_WAIT_RANGE_MAX 10000 /* Timeouts for commands in ms */ #define SBEFIFO_TIMEOUT_START_CMD 10000 #define SBEFIFO_TIMEOUT_IN_CMD 1000 #define SBEFIFO_TIMEOUT_START_RSP 10000 #define SBEFIFO_TIMEOUT_IN_RSP 1000 /* Other constants */ #define SBEFIFO_MAX_CMD_LEN 4096 struct sbefifo { uint32_t magic; #define SBEFIFO_MAGIC 0x53424546 /* "SBEF" */ struct fsi_device *fsi_dev; struct miscdevice mdev; struct mutex lock; char name[32]; int idx; bool broken; }; struct sbefifo_user { struct sbefifo *sbefifo; unsigned int f_flags; struct mutex file_lock; void *pending_cmd; size_t pending_len; }; static DEFINE_IDA(sbefifo_ida); static int sbefifo_regr(struct sbefifo *sbefifo, int reg, u32 *word) { int rc; __be32 raw_word; rc = fsi_device_read(sbefifo->fsi_dev, reg, &raw_word, sizeof(raw_word)); if (rc) return rc; *word = be32_to_cpu(raw_word); return 0; } static int sbefifo_regw(struct sbefifo *sbefifo, int reg, u32 word) { __be32 raw_word = cpu_to_be32(word); return fsi_device_write(sbefifo->fsi_dev, reg, &raw_word, sizeof(raw_word)); } /* Don't flip endianness of data to/from FIFO, just pass through. */ static int sbefifo_down_read(struct sbefifo *sbefifo, u32 *word) { return fsi_device_read(sbefifo->fsi_dev, SBEFIFO_DOWN, word, sizeof(*word)); } static int sbefifo_up_write(struct sbefifo *sbefifo, u32 word) { return fsi_device_write(sbefifo->fsi_dev, SBEFIFO_UP, &word, sizeof(word)); } static int sbefifo_request_reset(struct sbefifo *sbefifo) { struct device *dev = &sbefifo->fsi_dev->dev; u32 status, timeout; int rc; dev_dbg(dev, "Requesting FIFO reset\n"); /* Mark broken first, will be cleared if reset succeeds */ sbefifo->broken = true; /* Send reset request */ rc = sbefifo_regw(sbefifo, SBEFIFO_UP | SBEFIFO_REQ_RESET, 1); if (rc) { dev_err(dev, "Sending reset request failed, rc=%d\n", rc); return rc; } /* Wait for it to complete */ for (timeout = 0; timeout < SBEFIFO_RESET_TIMEOUT; timeout++) { rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &status); if (rc) { dev_err(dev, "Failed to read UP fifo status during reset" " , rc=%d\n", rc); return rc; } if (!(status & SBEFIFO_STS_RESET_REQ)) { dev_dbg(dev, "FIFO reset done\n"); sbefifo->broken = false; return 0; } msleep(1); } dev_err(dev, "FIFO reset timed out\n"); return -ETIMEDOUT; } static int sbefifo_cleanup_hw(struct sbefifo *sbefifo) { struct device *dev = &sbefifo->fsi_dev->dev; u32 up_status, down_status; bool need_reset = false; int rc; rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &up_status); if (rc) { dev_err(dev, "Cleanup: Reading UP status failed, rc=%d\n", rc); return rc; } rc = sbefifo_regr(sbefifo, SBEFIFO_DOWN | SBEFIFO_STS, &down_status); if (rc) { dev_err(dev, "Cleanup: Reading DOWN status failed, rc=%d\n", rc); return rc; } /* The FIFO already contains a reset request from the SBE ? */ if (down_status & SBEFIFO_STS_RESET_REQ) { dev_info(dev, "Cleanup: FIFO reset request set, resetting\n"); rc = sbefifo_regw(sbefifo, SBEFIFO_UP, SBEFIFO_PERFORM_RESET); if (rc) { sbefifo->broken = true; dev_err(dev, "Cleanup: Reset reg write failed, rc=%d\n", rc); return rc; } sbefifo->broken = false; return 0; } /* Parity error on either FIFO ? */ if ((up_status | down_status) & SBEFIFO_STS_PARITY_ERR) need_reset = true; /* Either FIFO not empty ? */ if (!((up_status & down_status) & SBEFIFO_STS_EMPTY)) need_reset = true; if (!sbefifo->broken && !need_reset) return 0; dev_info(dev, "Cleanup: FIFO not clean (up=0x%08x down=0x%08x broken=%d)\n", up_status, down_status, sbefifo->broken); /* Mark broken, will be cleared if/when reset succeeds */ return sbefifo_request_reset(sbefifo); } static int sbefifo_wait(struct sbefifo *sbefifo, bool up, u32 *status, unsigned long timeout) { struct device *dev = &sbefifo->fsi_dev->dev; unsigned long end_time; bool ready = false; u32 addr, sts = 0; int rc; dev_vdbg(dev, "Wait on %s fifo...\n", up ? "up" : "down"); addr = (up ? SBEFIFO_UP : SBEFIFO_DOWN) | SBEFIFO_STS; end_time = jiffies + timeout; while (!time_after(jiffies, end_time)) { rc = sbefifo_regr(sbefifo, addr, &sts); if (rc < 0) { dev_err(dev, "FSI error %d reading status register\n", rc); return rc; } if (!up && sbefifo_parity_err(sts)) { dev_err(dev, "Parity error in DOWN FIFO\n"); return -ENXIO; } ready = !(up ? sbefifo_full(sts) : sbefifo_empty(sts)); if (ready) break; cond_resched(); } if (!ready) { dev_err(dev, "FIFO Timeout !\n"); return -ETIMEDOUT; } dev_vdbg(dev, "End of wait status: %08x\n", sts); *status = sts; return 0; } static int sbefifo_send_command(struct sbefifo *sbefifo, const __be32 *command, size_t cmd_len) { struct device *dev = &sbefifo->fsi_dev->dev; size_t len, chunk, vacant = 0, remaining = cmd_len; unsigned long timeout; u32 status; int rc; dev_vdbg(dev, "sending command (%d words, cmd=%04x)\n", cmd_len, be32_to_cpu(command[1])); /* As long as there's something to send */ timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_START_CMD); while (remaining) { /* Wait for room in the FIFO */ rc = sbefifo_wait(sbefifo, true, &status, timeout); if (rc < 0) return rc; timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_CMD); vacant = sbefifo_vacant(status); len = chunk = min(vacant, remaining); dev_vdbg(dev, " status=%08x vacant=%d chunk=%d\n", status, vacant, chunk); /* Write as much as we can */ while (len--) { rc = sbefifo_up_write(sbefifo, *(command++)); if (rc) { dev_err(dev, "FSI error %d writing UP FIFO\n", rc); return rc; } } remaining -= chunk; vacant -= chunk; } /* If there's no room left, wait for some to write EOT */ if (!vacant) { rc = sbefifo_wait(sbefifo, true, &status, timeout); if (rc) return rc; } /* Send an EOT */ rc = sbefifo_regw(sbefifo, SBEFIFO_UP | SBEFIFO_EOT_RAISE, 0); if (rc) dev_err(dev, "FSI error %d writing EOT\n", rc); return rc; } static int sbefifo_read_response(struct sbefifo *sbefifo, __be32 __user *response, size_t *resp_len) { struct device *dev = &sbefifo->fsi_dev->dev; u32 data, status, eot_set; size_t len, remaining; unsigned long timeout; bool overflow = false; int rc; remaining = *resp_len; *resp_len = 0; dev_vdbg(dev, "reading response, buflen = %d\n", remaining); timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_START_RSP); for (;;) { /* Grab FIFO status (this will handle parity errors) */ rc = sbefifo_wait(sbefifo, false, &status, timeout); if (rc < 0) return rc; timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_RSP); /* Decode status */ len = sbefifo_populated(status); eot_set = sbefifo_eot_set(status); dev_vdbg(dev, " chunk size %d eot_set=0x%x\n", len, eot_set); /* Go through the chunk */ while(len--) { /* Read the data */ rc = sbefifo_down_read(sbefifo, &data); if (rc < 0) return rc; /* Was it an EOT ? */ if (eot_set & 0x80) { /* * There should be nothing else in the FIFO, * if there is, mark broken, this will force * a reset on next use, but don't fail the * command. */ if (len) { dev_warn(dev, "FIFO read hit" " EOT with still %d data\n", len); sbefifo->broken = true; } /* We are done */ rc = sbefifo_regw(sbefifo, SBEFIFO_DOWN | SBEFIFO_EOT_ACK, 0); /* * If that write fail, still complete the request but mark * the fifo as broken for subsequent reset (not much else * we can do here). */ if (rc) { dev_err(dev, "FSI error %d ack'ing EOT\n", rc); sbefifo->broken = true; } dev_vdbg(dev, "got %d words response%s\n", *resp_len, overflow ? "with overflow !" : ""); /* Tell whether we overflowed */ return overflow ? -EOVERFLOW : 0; } /* Store it if there is room */ if (remaining) { rc = put_user(data, response++); if (rc) return rc; remaining--; (*resp_len)++; } else overflow = true; /* Next EOT bit */ eot_set <<= 1; } } /* Shouldn't happen */ return -EIO; } static int __sbefifo_submit(struct sbefifo *sbefifo, const __be32 *command, size_t cmd_len, __be32 __user *response, size_t *resp_len) { struct device *dev = &sbefifo->fsi_dev->dev; int rc; if (cmd_len < 2 || be32_to_cpu(command[0]) != cmd_len) { dev_vdbg(dev, "Invalid command len %d (header: %d)\n", cmd_len, be32_to_cpu(command[0])); return -EINVAL; } /* First ensure the HW is in a clean state */ rc = sbefifo_cleanup_hw(sbefifo); if (rc) return rc; /* Try sending the command */ rc = sbefifo_send_command(sbefifo, command, cmd_len); if (rc) goto fail; /* Now, get the response */ rc = sbefifo_read_response(sbefifo, response, resp_len); if (rc != 0 && rc != -EOVERFLOW) goto fail; return rc; fail: /* * On failure, attempt a reset. Ignore the result, it will mark * the fifo broken if the reset fails */ sbefifo_request_reset(sbefifo); /* Return original error */ return rc; } /** * sbefifo_submit() - Submit and SBE fifo command and receive response * @dev: The sbefifo device * @command: The raw command data * @cmd_len: The command size (in 32-bit words) * @response: The output response buffer * @resp_len: In: Response buffer size, Out: Response size * * This will perform the entire operation. If the reponse buffer * overflows, returns -EOVERFLOW */ int sbefifo_submit(struct device *dev, const __be32 *command, size_t cmd_len, __be32 *response, size_t *resp_len) { struct sbefifo *sbefifo = dev_get_drvdata(dev); mm_segment_t old_fs; int rc; if (!dev || !sbefifo) return -ENODEV; if (WARN_ON_ONCE(sbefifo->magic != SBEFIFO_MAGIC)) return -ENODEV; if (!resp_len || !command || !response || cmd_len > SBEFIFO_MAX_CMD_LEN) return -EINVAL; mutex_lock(&sbefifo->lock); old_fs = get_fs(); set_fs(KERNEL_DS); rc = __sbefifo_submit(sbefifo, command, cmd_len, response, resp_len); set_fs(old_fs); mutex_unlock(&sbefifo->lock); return rc; } EXPORT_SYMBOL_GPL(sbefifo_submit); int sbefifo_parse_status(u16 cmd, __be32 *response, size_t resp_len, size_t *data_len) { u32 dh, s0, s1; if (resp_len < 3) { pr_debug("sbefifo: cmd %04x, response too small: %d\n", cmd, resp_len); return -ENXIO; } dh = be32_to_cpu(response[resp_len - 1]); if (dh > resp_len || dh < 3) { pr_debug("sbefifo: cmd %04x, status offset out of range: %d/%d\n", cmd, dh, resp_len); return -ENXIO; } s0 = be32_to_cpu(response[resp_len - dh]); s1 = be32_to_cpu(response[resp_len - dh + 1]); if (((s0 >> 16) != 0xC0DE) || ((s0 & 0xffff) != cmd)) { pr_debug("sbefifo: cmd %04x, status signature invalid: 0x%08x 0x%08x\n", cmd, s0, s1); return -ENXIO; } if (s1 != 0) { pr_debug("sbefifo: cmd %04x, error status: prim=%04x sec=%04x\n", cmd, s1 >> 16, s1 & 0xffff); } if (data_len) *data_len = resp_len - dh; /* * Primary status don't have the top bit set, so can't be confused with * Linux negative error codes, so return the status word whole. */ return s1; } EXPORT_SYMBOL_GPL(sbefifo_parse_status); /* * Char device interface */ static int sbefifo_user_open(struct inode *inode, struct file *file) { struct sbefifo *sbefifo = container_of(file->private_data, struct sbefifo, mdev); struct sbefifo_user *user; user = kzalloc(sizeof(struct sbefifo_user), GFP_KERNEL); if (!user) return -ENOMEM; file->private_data = user; user->sbefifo = sbefifo; user->f_flags = file->f_flags; mutex_init(&user->file_lock); return 0; } static ssize_t sbefifo_user_read(struct file *file, char __user *buf, size_t len, loff_t *offset) { struct sbefifo_user *user = file->private_data; struct sbefifo *sbefifo; size_t rlen; int rc; if (len & 3) return -EINVAL; mutex_lock(&user->file_lock); sbefifo = user->sbefifo; /* No command ? Ugh... */ if (!user->pending_cmd) { rc = -ENXIO; goto bail; } mutex_lock(&sbefifo->lock); rlen = len >> 2; rc = __sbefifo_submit(sbefifo, user->pending_cmd, user->pending_len >> 2, (__be32 __user *)buf, &rlen); mutex_unlock(&sbefifo->lock); if (rc < 0) goto bail; rc = rlen << 2; bail: kfree(user->pending_cmd); user->pending_len = 0; mutex_unlock(&user->file_lock); return rc; } static ssize_t sbefifo_user_write(struct file *file, const char __user *buf, size_t len, loff_t *offset) { struct sbefifo_user *user = file->private_data; void *cmd_ptr; size_t cmd_len; int rc = len; if (!user) return -EINVAL; if (len > SBEFIFO_MAX_CMD_LEN) return -EINVAL; if (len & 3) return -EINVAL; /* XXX Add special "reset" command */ /* If we already have a command, append to it, otherwise copy it over */ mutex_lock(&user->file_lock); cmd_ptr = user->pending_cmd; if (cmd_ptr) { cmd_len = user->pending_len + len; if (cmd_len > SBEFIFO_MAX_CMD_LEN) { rc = -EINVAL; goto bail; } user->pending_cmd = krealloc(cmd_ptr, cmd_len, GFP_KERNEL); cmd_ptr = user->pending_cmd + user->pending_len; } else { cmd_len = len; user->pending_cmd = cmd_ptr = kmalloc(len, GFP_KERNEL); } if (!user->pending_cmd) { rc = -ENOMEM; goto bail; } user->pending_len = cmd_len; /* Copy the user data */ if (copy_from_user(cmd_ptr, buf, len)) rc = -EFAULT; else rc = len; bail: mutex_unlock(&user->file_lock); /* And that's it, we'll issue the command on a read */ return rc; } static int sbefifo_user_release(struct inode *inode, struct file *file) { struct sbefifo_user *user = file->private_data; kfree(user); return 0; } static const struct file_operations sbefifo_fops = { .owner = THIS_MODULE, .open = sbefifo_user_open, .read = sbefifo_user_read, .write = sbefifo_user_write, .release = sbefifo_user_release, }; /* * Probe/remove */ static int sbefifo_probe(struct device *dev) { struct fsi_device *fsi_dev = to_fsi_dev(dev); struct sbefifo *sbefifo; struct device_node *np; struct platform_device *child; char child_name[32]; int rc, child_idx = 0; dev_dbg(dev, "Found sbefifo device\n"); sbefifo = devm_kzalloc(dev, sizeof(*sbefifo), GFP_KERNEL); if (!sbefifo) return -ENOMEM; sbefifo->magic = SBEFIFO_MAGIC; sbefifo->fsi_dev = fsi_dev; mutex_init(&sbefifo->lock); rc = sbefifo_cleanup_hw(sbefifo); if (rc) { dev_err(dev, "Initial HW cleanup failed, rc=%d !\n", rc); return rc; } sbefifo->idx = ida_simple_get(&sbefifo_ida, 1, INT_MAX, GFP_KERNEL); snprintf(sbefifo->name, sizeof(sbefifo->name), "sbefifo%d", sbefifo->idx); dev_set_drvdata(dev, sbefifo); /* Create misc chardev for userspace access */ sbefifo->mdev.minor = MISC_DYNAMIC_MINOR; sbefifo->mdev.fops = &sbefifo_fops; sbefifo->mdev.name = sbefifo->name; sbefifo->mdev.parent = dev; rc = misc_register(&sbefifo->mdev); if (rc) { dev_err(dev, "Failed to register miscdevice: %d\n", rc); ida_simple_remove(&sbefifo_ida, sbefifo->idx); return rc; } /* Create platform devs for dts child nodes (occ, etc) */ for_each_available_child_of_node(dev->of_node, np) { snprintf(child_name, sizeof(child_name), "%s-dev%d", sbefifo->name, child_idx++); child = of_platform_device_create(np, child_name, dev); if (!child) dev_warn(dev, "failed to create child %s dev\n", child_name); } return 0; } static int sbefifo_unregister_child(struct device *dev, void *data) { struct platform_device *child = to_platform_device(dev); of_device_unregister(child); if (dev->of_node) of_node_clear_flag(dev->of_node, OF_POPULATED); return 0; } static int sbefifo_remove(struct device *dev) { struct sbefifo *sbefifo = dev_get_drvdata(dev); dev_dbg(dev, "Removing sbefifo device...\n"); misc_deregister(&sbefifo->mdev); device_for_each_child(dev, NULL, sbefifo_unregister_child); ida_simple_remove(&sbefifo_ida, sbefifo->idx); return 0; } static struct fsi_device_id sbefifo_ids[] = { { .engine_type = FSI_ENGID_SBE, .version = FSI_VERSION_ANY, }, { 0 } }; static struct fsi_driver sbefifo_drv = { .id_table = sbefifo_ids, .drv = { .name = DEVICE_NAME, .bus = &fsi_bus_type, .probe = sbefifo_probe, .remove = sbefifo_remove, } }; static int sbefifo_init(void) { return fsi_driver_register(&sbefifo_drv); } static void sbefifo_exit(void) { fsi_driver_unregister(&sbefifo_drv); ida_destroy(&sbefifo_ida); } module_init(sbefifo_init); module_exit(sbefifo_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Brad Bishop "); MODULE_AUTHOR("Eddie James "); MODULE_AUTHOR("Andrew Jeffery "); MODULE_AUTHOR("Benjamin Herrenschmidt "); MODULE_DESCRIPTION("Linux device interface to the POWER Self Boot Engine");