/*- * Copyright (c) 2017 Mellanox Technologies. All rights reserved. * * This software is available to you under a choice of one of two * licenses. You may choose to be licensed under the terms of the GNU * General Public License (GPL) Version 2, available from the file * COPYING in the main directory of this source tree, or the * OpenIB.org BSD license below: * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #define CHUNK_SIZE (128 * 1024) struct tools_context { struct mlx5_fpga_tools_dev *tdev; enum mlx5_fpga_access_type access_type; }; static void tools_char_ctx_dtor(void *data) { free(data, M_DEVBUF); } static int tools_char_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct tools_context *context; context = malloc(sizeof(*context), M_DEVBUF, M_WAITOK); context->tdev = dev->si_drv1; context->access_type = MLX5_FPGA_ACCESS_TYPE_DONTCARE; devfs_set_cdevpriv(context, tools_char_ctx_dtor); return (0); } static int mem_read(struct mlx5_fpga_tools_dev *tdev, void *buf, size_t count, u64 address, enum mlx5_fpga_access_type access_type, size_t *retcnt) { int ret; ret = sx_xlock_sig(&tdev->lock); if (ret != 0) return (ret); ret = mlx5_fpga_mem_read(tdev->fdev, count, address, buf, access_type); sx_xunlock(&tdev->lock); if (ret < 0) { dev_dbg(mlx5_fpga_dev(tdev->fdev), "Failed to read %zu bytes at address 0x%jx: %d\n", count, (uintmax_t)address, ret); return (-ret); } *retcnt = ret; return (0); } static int mem_write(struct mlx5_fpga_tools_dev *tdev, void *buf, size_t count, u64 address, enum mlx5_fpga_access_type access_type, size_t *retcnt) { int ret; ret = sx_xlock_sig(&tdev->lock); if (ret != 0) return (ret); ret = mlx5_fpga_mem_write(tdev->fdev, count, address, buf, access_type); sx_xunlock(&tdev->lock); if (ret < 0) { dev_dbg(mlx5_fpga_dev(tdev->fdev), "Failed to write %zu bytes at address 0x%jx: %d\n", count, (uintmax_t)address, ret); return (-ret); } *retcnt = ret; return (0); } static void tools_char_llseek(struct tools_context *context, struct uio *uio, ssize_t *len) { uint64_t fbase, fsize; size_t llen; llen = uio->uio_resid; if (llen < 1) { *len = 0; return; } if (llen > CHUNK_SIZE) llen = CHUNK_SIZE; fbase = mlx5_fpga_ddr_base_get(context->tdev->fdev); fsize = mlx5_fpga_ddr_size_get(context->tdev->fdev); if (uio->uio_offset > fbase) *len = 0; else if (uio->uio_offset + *len > fbase + fsize) *len = fbase + fsize - uio->uio_offset; else *len = llen; } static int tools_char_read(struct cdev *dev, struct uio *uio, int ioflag) { struct tools_context *context; void *kbuf; size_t len, len1; int ret; ret = devfs_get_cdevpriv((void **)&context); if (ret != 0) return (ret); dev_dbg(mlx5_fpga_dev(context->tdev->fdev), "tools char device reading %zu bytes at 0x%jx\n", uio->uio_resid, (uintmax_t)uio->uio_offset); tools_char_llseek(context, uio, &len); if (len == 0) return (0); kbuf = malloc(len, M_DEVBUF, M_WAITOK); ret = mem_read(context->tdev, kbuf, len, uio->uio_offset, context->access_type, &len1); if (ret == 0) ret = uiomove(kbuf, len1, uio); free(kbuf, M_DEVBUF); return (ret); } static int tools_char_write(struct cdev *dev, struct uio *uio, int ioflag) { struct tools_context *context; void *kbuf; off_t of; size_t len, len1; int ret; ret = devfs_get_cdevpriv((void **)&context); if (ret != 0) return (ret); dev_dbg(mlx5_fpga_dev(context->tdev->fdev), "tools char device reading %zu bytes at 0x%jx\n", uio->uio_resid, (uintmax_t)uio->uio_offset); tools_char_llseek(context, uio, &len); if (len == 0) return (0); kbuf = malloc(len, M_DEVBUF, M_WAITOK); len1 = uio->uio_resid; of = uio->uio_offset; ret = uiomove(kbuf, len, uio); if (ret == 0) { len1 -= uio->uio_resid; ret = mem_write(context->tdev, kbuf, len, of, context->access_type, &len1); } free(kbuf, M_DEVBUF); return (ret); } CTASSERT(MLX5_FPGA_CAP_ARR_SZ == MLX5_ST_SZ_DW(fpga_cap)); static int tools_char_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct tools_context *context; struct mlx5_fpga_device *fdev; struct mlx5_fpga_query query; struct mlx5_fpga_temperature *temperature; enum mlx5_fpga_connect *connect; u32 fpga_cap[MLX5_ST_SZ_DW(fpga_cap)] = {0}; int arg, err; err = devfs_get_cdevpriv((void **)&context); if (err != 0) return (err); fdev = context->tdev->fdev; if (fdev == NULL) return (ENXIO); switch (cmd) { case MLX5_FPGA_ACCESS_TYPE: arg = *(int *)data; if (arg > MLX5_FPGA_ACCESS_TYPE_MAX) { dev_err(mlx5_fpga_dev(fdev), "unknown access type %u\n", arg); err = EINVAL; break; } context->access_type = arg; break; case MLX5_FPGA_LOAD: arg = *(int *)data; if (arg > MLX5_FPGA_IMAGE_FACTORY) { dev_err(mlx5_fpga_dev(fdev), "unknown image type %u\n", arg); err = EINVAL; break; } err = mlx5_fpga_device_reload(fdev, arg); break; case MLX5_FPGA_RESET: err = mlx5_fpga_device_reload(fdev, MLX5_FPGA_IMAGE_RESET); break; case MLX5_FPGA_RELOAD: err = mlx5_fpga_device_reload(fdev, MLX5_FPGA_IMAGE_RELOAD); break; case MLX5_FPGA_IMAGE_SEL: arg = *(int *)data; if (arg > MLX5_FPGA_IMAGE_FACTORY) { dev_err(mlx5_fpga_dev(fdev), "unknown image type %u\n", arg); err = EINVAL; break; } err = mlx5_fpga_flash_select(fdev, arg); break; case MLX5_FPGA_QUERY: mlx5_fpga_device_query(fdev, &query); bcopy(&query, data, sizeof(query)); err = 0; break; case MLX5_FPGA_CAP: mlx5_fpga_get_cap(fdev, fpga_cap); bcopy(&fpga_cap, data, sizeof(fpga_cap)); err = 0; break; case MLX5_FPGA_TEMPERATURE: temperature = (struct mlx5_fpga_temperature *)data; mlx5_fpga_temperature(fdev, temperature); err = 0; /* XXXKIB */ break; case MLX5_FPGA_CONNECT: connect = (enum mlx5_fpga_connect *)data; mlx5_fpga_connectdisconnect(fdev, connect); err = 0; /* XXXKIB */ break; default: dev_err(mlx5_fpga_dev(fdev), "unknown ioctl command %#08lx\n", cmd); err = ENOTTY; } return (err); } static struct cdevsw mlx5_tools_char_cdevsw = { .d_version = D_VERSION, .d_name = "mlx5_tools_char", .d_open = tools_char_open, .d_read = tools_char_read, .d_write = tools_char_write, .d_ioctl = tools_char_ioctl, }; int mlx5_fpga_tools_char_add_one(struct mlx5_fpga_tools_dev *tdev) { struct make_dev_args mda; struct cdev *cd; device_t bdev; int ret; make_dev_args_init(&mda); mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME; mda.mda_devsw = &mlx5_tools_char_cdevsw; mda.mda_uid = UID_ROOT; mda.mda_gid = GID_OPERATOR; mda.mda_mode = 0660; mda.mda_si_drv1 = tdev; bdev = mlx5_fpga_dev(tdev->fdev)->bsddev; ret = make_dev_s(&mda, &cd, "%04x:%02x:%02x.%x" MLX5_FPGA_TOOLS_NAME_SUFFIX, pci_get_domain(bdev), pci_get_bus(bdev), pci_get_slot(bdev), pci_get_function(bdev)); if (ret != 0) { tdev->char_device = NULL; dev_err(mlx5_fpga_dev(tdev->fdev), "Failed to create a char device: %d\n", ret); return (-ret); } tdev->char_device = cd; dev_dbg(mlx5_fpga_dev(tdev->fdev), "tools char device %s created\n", cd->si_name); return (0); } void mlx5_fpga_tools_char_remove_one(struct mlx5_fpga_tools_dev *tdev) { dev_err(mlx5_fpga_dev(tdev->fdev), "tools char device %s destroyed\n", ((struct cdev *)(tdev->char_device))->si_name); destroy_dev((struct cdev *)(tdev->char_device)); } int mlx5_fpga_tools_char_init(void) { return (0); } void mlx5_fpga_tools_char_deinit(void) { }