static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
			int ifindex, struct sock *sk)
{
	struct bcm_sock *bo = bcm_sk(sk);
	struct bcm_op *op;
	struct canfd_frame *cf;
	unsigned int i;
	int err;

	/* we need a real device to send frames */
	if (!ifindex)
		return -ENODEV;

	/* check nframes boundaries - we need at least one CAN frame */
	if (msg_head->nframes < 1 || msg_head->nframes > MAX_NFRAMES)
		return -EINVAL;

	/* check timeval limitations */
	if ((msg_head->flags & SETTIMER) && bcm_is_invalid_tv(msg_head))
		return -EINVAL;

	/* check the given can_id */
	op = bcm_find_op(&bo->tx_ops, msg_head, ifindex);
	if (op) {
		/* update existing BCM operation */

		/*
		 * Do we need more space for the CAN frames than currently
		 * allocated? -> This is a _really_ unusual use-case and
		 * therefore (complexity / locking) it is not supported.
		 */
		if (msg_head->nframes > op->nframes)
			return -E2BIG;

		/* update CAN frames content */
		for (i = 0; i < msg_head->nframes; i++) {

			cf = op->frames + op->cfsiz * i;
			err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz);

			if (op->flags & CAN_FD_FRAME) {
				if (cf->len > 64)
					err = -EINVAL;
			} else {
				if (cf->len > 8)
					err = -EINVAL;
			}

			if (err < 0)
				return err;

			if (msg_head->flags & TX_CP_CAN_ID) {
				/* copy can_id into frame */
				cf->can_id = msg_head->can_id;
			}
		}
		op->flags = msg_head->flags;

	} else {
		/* insert new BCM operation for the given can_id */

		op = kzalloc(OPSIZ, GFP_KERNEL);
		if (!op)
			return -ENOMEM;

		op->can_id = msg_head->can_id;
		op->cfsiz = CFSIZ(msg_head->flags);
		op->flags = msg_head->flags;

		/* create array for CAN frames and copy the data */
		if (msg_head->nframes > 1) {
			op->frames = kmalloc_array(msg_head->nframes,
						   op->cfsiz,
						   GFP_KERNEL);
			if (!op->frames) {
				kfree(op);
				return -ENOMEM;
			}
		} else
			op->frames = &op->sframe;

		for (i = 0; i < msg_head->nframes; i++) {

			cf = op->frames + op->cfsiz * i;
			err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz);

			if (op->flags & CAN_FD_FRAME) {
				if (cf->len > 64)
					err = -EINVAL;
			} else {
				if (cf->len > 8)
					err = -EINVAL;
			}

			if (err < 0) {
				if (op->frames != &op->sframe)
					kfree(op->frames);
				kfree(op);
				return err;
			}

			if (msg_head->flags & TX_CP_CAN_ID) {
				/* copy can_id into frame */
				cf->can_id = msg_head->can_id;
			}
		}

		/* tx_ops never compare with previous received messages */
		op->last_frames = NULL;

		/* bcm_can_tx / bcm_tx_timeout_handler needs this */
		op->sk = sk;
		op->ifindex = ifindex;

		/* initialize uninitialized (kzalloc) structure */
		hrtimer_init(&op->timer, CLOCK_MONOTONIC,
			     HRTIMER_MODE_REL_SOFT);
		op->timer.function = bcm_tx_timeout_handler;

		/* currently unused in tx_ops */
		hrtimer_init(&op->thrtimer, CLOCK_MONOTONIC,
			     HRTIMER_MODE_REL_SOFT);

		/* add this bcm_op to the list of the tx_ops */
		list_add(&op->list, &bo->tx_ops);

	} /* if ((op = bcm_find_op(&bo->tx_ops, msg_head->can_id, ifindex))) */

	if (op->nframes != msg_head->nframes) {
		op->nframes   = msg_head->nframes;
		/* start multiple frame transmission with index 0 */
		op->currframe = 0;
	}

	/* check flags */

	if (op->flags & TX_RESET_MULTI_IDX) {
		/* start multiple frame transmission with index 0 */
		op->currframe = 0;
	}

	if (op->flags & SETTIMER) {
		/* set timer values */
		op->count = msg_head->count;
		op->ival1 = msg_head->ival1;
		op->ival2 = msg_head->ival2;
		op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1);
		op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2);

		/* disable an active timer due to zero values? */
		if (!op->kt_ival1 && !op->kt_ival2)
			hrtimer_cancel(&op->timer);
	}

	if (op->flags & STARTTIMER) {
		hrtimer_cancel(&op->timer);
		/* spec: send CAN frame when starting timer */
		op->flags |= TX_ANNOUNCE;
	}

	if (op->flags & TX_ANNOUNCE) {
		bcm_can_tx(op);
		if (op->count)
			op->count--;
	}

	if (op->flags & STARTTIMER)
		bcm_tx_start_timer(op);

	return msg_head->nframes * op->cfsiz + MHSIZ;
}