/* * Analogy for Linux, command related features * * Copyright (C) 1997-2000 David A. Schleef * Copyright (C) 2008 Alexis Berlemont * * Xenomai is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Xenomai 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. * * You should have received a copy of the GNU General Public License * along with Xenomai; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include /* --- Command descriptor management functions --- */ int a4l_fill_cmddesc(struct a4l_device_context *cxt, struct a4l_cmd_desc *desc, unsigned int **chan_descs, void *arg) { unsigned int *tmpchans = NULL; int ret = 0; ret = rtdm_safe_copy_from_user(rtdm_private_to_fd(cxt), desc, arg, sizeof(struct a4l_cmd_desc)); if (ret != 0) goto out_cmddesc; if (desc->nb_chan == 0) { ret = -EINVAL; goto out_cmddesc; } tmpchans = rtdm_malloc(desc->nb_chan * sizeof(unsigned int)); if (tmpchans == NULL) { ret = -ENOMEM; goto out_cmddesc; } ret = rtdm_safe_copy_from_user(rtdm_private_to_fd(cxt), tmpchans, desc->chan_descs, desc->nb_chan * sizeof(unsigned int)); if (ret != 0) { __a4l_err("%s invalid arguments \n", __FUNCTION__); goto out_cmddesc; } *chan_descs = desc->chan_descs; desc->chan_descs = tmpchans; __a4l_dbg(1, core_dbg, "desc dump: \n"); __a4l_dbg(1, core_dbg, "\t->idx_subd=%u\n", desc->idx_subd); __a4l_dbg(1, core_dbg, "\t->flags=%lu\n", desc->flags); __a4l_dbg(1, core_dbg, "\t->nb_chan=%u\n", desc->nb_chan); __a4l_dbg(1, core_dbg, "\t->chan_descs=0x%x\n", *desc->chan_descs); __a4l_dbg(1, core_dbg, "\t->data_len=%u\n", desc->data_len); __a4l_dbg(1, core_dbg, "\t->pdata=0x%p\n", desc->data); out_cmddesc: if (ret != 0) { __a4l_err("a4l_fill_cmddesc: %d \n", ret); if (tmpchans != NULL) rtdm_free(tmpchans); desc->chan_descs = NULL; } return ret; } void a4l_free_cmddesc(struct a4l_cmd_desc * desc) { if (desc->chan_descs != NULL) rtdm_free(desc->chan_descs); } int a4l_check_cmddesc(struct a4l_device_context * cxt, struct a4l_cmd_desc * desc) { struct a4l_device *dev = a4l_get_dev(cxt); struct a4l_subdevice *subd; if (desc->idx_subd >= dev->transfer.nb_subd) { __a4l_err("a4l_check_cmddesc: " "subdevice index out of range (idx=%u)\n", desc->idx_subd); return -EINVAL; } subd = dev->transfer.subds[desc->idx_subd]; if ((subd->flags & A4L_SUBD_TYPES) == A4L_SUBD_UNUSED) { __a4l_err("a4l_check_cmddesc: " "subdevice type incoherent\n"); return -EIO; } if (!(subd->flags & A4L_SUBD_CMD)) { __a4l_err("a4l_check_cmddesc: operation not supported, " "synchronous only subdevice\n"); return -EIO; } if (test_bit(A4L_SUBD_BUSY, &subd->status)) { __a4l_err("a4l_check_cmddesc: subdevice busy\n"); return -EBUSY; } return a4l_check_chanlist(dev->transfer.subds[desc->idx_subd], desc->nb_chan, desc->chan_descs); } /* --- Command checking functions --- */ int a4l_check_generic_cmdcnt(struct a4l_cmd_desc * desc) { unsigned int tmp1, tmp2; /* Makes sure trigger sources are trivially valid */ tmp1 = desc->start_src & ~(TRIG_NOW | TRIG_INT | TRIG_EXT | TRIG_FOLLOW); tmp2 = desc->start_src & (TRIG_NOW | TRIG_INT | TRIG_EXT | TRIG_FOLLOW); if (tmp1 != 0 || tmp2 == 0) { __a4l_err("a4l_check_cmddesc: start_src, weird trigger\n"); return -EINVAL; } tmp1 = desc->scan_begin_src & ~(TRIG_TIMER | TRIG_EXT | TRIG_FOLLOW); tmp2 = desc->scan_begin_src & (TRIG_TIMER | TRIG_EXT | TRIG_FOLLOW); if (tmp1 != 0 || tmp2 == 0) { __a4l_err("a4l_check_cmddesc: scan_begin_src, , weird trigger\n"); return -EINVAL; } tmp1 = desc->convert_src & ~(TRIG_TIMER | TRIG_EXT | TRIG_NOW); tmp2 = desc->convert_src & (TRIG_TIMER | TRIG_EXT | TRIG_NOW); if (tmp1 != 0 || tmp2 == 0) { __a4l_err("a4l_check_cmddesc: convert_src, weird trigger\n"); return -EINVAL; } tmp1 = desc->scan_end_src & ~(TRIG_COUNT); if (tmp1 != 0) { __a4l_err("a4l_check_cmddesc: scan_end_src, weird trigger\n"); return -EINVAL; } tmp1 = desc->stop_src & ~(TRIG_COUNT | TRIG_NONE); tmp2 = desc->stop_src & (TRIG_COUNT | TRIG_NONE); if (tmp1 != 0 || tmp2 == 0) { __a4l_err("a4l_check_cmddesc: stop_src, weird trigger\n"); return -EINVAL; } /* Makes sure trigger sources are unique */ if (desc->start_src != TRIG_NOW && desc->start_src != TRIG_INT && desc->start_src != TRIG_EXT && desc->start_src != TRIG_FOLLOW) { __a4l_err("a4l_check_cmddesc: start_src, " "only one trigger should be set\n"); return -EINVAL; } if (desc->scan_begin_src != TRIG_TIMER && desc->scan_begin_src != TRIG_EXT && desc->scan_begin_src != TRIG_FOLLOW) { __a4l_err("a4l_check_cmddesc: scan_begin_src, " "only one trigger should be set\n"); return -EINVAL; } if (desc->convert_src != TRIG_TIMER && desc->convert_src != TRIG_EXT && desc->convert_src != TRIG_NOW) { __a4l_err("a4l_check_cmddesc: convert_src, " "only one trigger should be set\n"); return -EINVAL; } if (desc->stop_src != TRIG_COUNT && desc->stop_src != TRIG_NONE) { __a4l_err("a4l_check_cmddesc: stop_src, " "only one trigger should be set\n"); return -EINVAL; } /* Makes sure arguments are trivially compatible */ tmp1 = desc->start_src & (TRIG_NOW | TRIG_FOLLOW | TRIG_INT); tmp2 = desc->start_arg; if (tmp1 != 0 && tmp2 != 0) { __a4l_err("a4l_check_cmddesc: no start_arg expected\n"); return -EINVAL; } tmp1 = desc->scan_begin_src & TRIG_FOLLOW; tmp2 = desc->scan_begin_arg; if (tmp1 != 0 && tmp2 != 0) { __a4l_err("a4l_check_cmddesc: no scan_begin_arg expected\n"); return -EINVAL; } tmp1 = desc->convert_src & TRIG_NOW; tmp2 = desc->convert_arg; if (tmp1 != 0 && tmp2 != 0) { __a4l_err("a4l_check_cmddesc: no convert_arg expected\n"); return -EINVAL; } tmp1 = desc->stop_src & TRIG_NONE; tmp2 = desc->stop_arg; if (tmp1 != 0 && tmp2 != 0) { __a4l_err("a4l_check_cmddesc: no stop_arg expected\n"); return -EINVAL; } return 0; } int a4l_check_specific_cmdcnt(struct a4l_device_context * cxt, struct a4l_cmd_desc * desc) { unsigned int tmp1, tmp2; struct a4l_device *dev = a4l_get_dev(cxt); struct a4l_cmd_desc *cmd_mask = dev->transfer.subds[desc->idx_subd]->cmd_mask; if (cmd_mask == NULL) return 0; if (cmd_mask->start_src != 0) { tmp1 = desc->start_src & ~(cmd_mask->start_src); tmp2 = desc->start_src & (cmd_mask->start_src); if (tmp1 != 0 || tmp2 == 0) { __a4l_err("a4l_check_cmddesc: start_src, " "trigger unsupported\n"); return -EINVAL; } } if (cmd_mask->scan_begin_src != 0) { tmp1 = desc->scan_begin_src & ~(cmd_mask->scan_begin_src); tmp2 = desc->scan_begin_src & (cmd_mask->scan_begin_src); if (tmp1 != 0 || tmp2 == 0) { __a4l_err("a4l_check_cmddesc: scan_begin_src, " "trigger unsupported\n"); return -EINVAL; } } if (cmd_mask->convert_src != 0) { tmp1 = desc->convert_src & ~(cmd_mask->convert_src); tmp2 = desc->convert_src & (cmd_mask->convert_src); if (tmp1 != 0 || tmp2 == 0) { __a4l_err("a4l_check_cmddesc: convert_src, " "trigger unsupported\n"); return -EINVAL; } } if (cmd_mask->scan_end_src != 0) { tmp1 = desc->scan_end_src & ~(cmd_mask->scan_end_src); if (tmp1 != 0) { __a4l_err("a4l_check_cmddesc: scan_end_src, " "trigger unsupported\n"); return -EINVAL; } } if (cmd_mask->stop_src != 0) { tmp1 = desc->stop_src & ~(cmd_mask->stop_src); tmp2 = desc->stop_src & (cmd_mask->stop_src); if (tmp1 != 0 || tmp2 == 0) { __a4l_err("a4l_check_cmddesc: stop_src, " "trigger unsupported\n"); return -EINVAL; } } return 0; } /* --- IOCTL / FOPS function --- */ int a4l_ioctl_cmd(struct a4l_device_context * ctx, void *arg) { int ret = 0, simul_flag = 0; struct a4l_cmd_desc *cmd_desc = NULL; struct a4l_device *dev = a4l_get_dev(ctx); unsigned int *chan_descs, *tmp; struct a4l_subdevice *subd; /* The command launching cannot be done in real-time because of some possible buffer allocations in the drivers */ if (rtdm_in_rt_context()) return -ENOSYS; /* Basically check the device */ if (!test_bit(A4L_DEV_ATTACHED_NR, &dev->flags)) { __a4l_err("a4l_ioctl_cmd: cannot command " "an unattached device\n"); return -EINVAL; } /* Allocates the command */ cmd_desc = (struct a4l_cmd_desc *) rtdm_malloc(sizeof(struct a4l_cmd_desc)); if (cmd_desc == NULL) return -ENOMEM; memset(cmd_desc, 0, sizeof(struct a4l_cmd_desc)); /* Gets the command */ ret = a4l_fill_cmddesc(ctx, cmd_desc, &chan_descs, arg); if (ret != 0) goto out_ioctl_cmd; /* Checks the command */ ret = a4l_check_cmddesc(ctx, cmd_desc); if (ret != 0) goto out_ioctl_cmd; ret = a4l_check_generic_cmdcnt(cmd_desc); if (ret != 0) goto out_ioctl_cmd; ret = a4l_check_specific_cmdcnt(ctx, cmd_desc); if (ret != 0) goto out_ioctl_cmd; __a4l_dbg(1, core_dbg,"1st cmd checks passed\n"); subd = dev->transfer.subds[cmd_desc->idx_subd]; /* Tests the command with the cmdtest function */ if (cmd_desc->flags & A4L_CMD_SIMUL) { simul_flag = 1; if (!subd->do_cmdtest) { __a4l_err("a4l_ioctl_cmd: driver's cmd_test NULL\n"); ret = -EINVAL; goto out_ioctl_cmd; } ret = subd->do_cmdtest(subd, cmd_desc); if (ret != 0) { __a4l_err("a4l_ioctl_cmd: driver's cmd_test failed\n"); goto out_ioctl_cmd; } __a4l_dbg(1, core_dbg, "driver's cmd checks passed\n"); goto out_ioctl_cmd; } /* Gets the transfer system ready */ ret = a4l_setup_buffer(ctx, cmd_desc); if (ret < 0) goto out_ioctl_cmd; /* Eventually launches the command */ ret = subd->do_cmd(subd, cmd_desc); if (ret != 0) { a4l_cancel_buffer(ctx); goto out_ioctl_cmd; } out_ioctl_cmd: if (simul_flag) { /* copy the kernel based descriptor */ tmp = cmd_desc->chan_descs; /* return the user based descriptor */ cmd_desc->chan_descs = chan_descs; rtdm_safe_copy_to_user(rtdm_private_to_fd(ctx), arg, cmd_desc, sizeof(struct a4l_cmd_desc)); /* make sure we release the memory associated to the kernel */ cmd_desc->chan_descs = tmp; } if (ret != 0 || simul_flag == 1) { a4l_free_cmddesc(cmd_desc); rtdm_free(cmd_desc); } return ret; }