static int bcm_rx_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;
	int do_rx_register;
	int err = 0;

	if ((msg_head->flags & RX_FILTER_ID) || (!(msg_head->nframes))) {
		/* be robust against wrong usage ... */
		msg_head->flags |= RX_FILTER_ID;
		/* ignore trailing garbage */
		msg_head->nframes = 0;
	}

	/* the first element contains the mux-mask => MAX_NFRAMES + 1  */
	if (msg_head->nframes > MAX_NFRAMES + 1)
		return -EINVAL;

	if ((msg_head->flags & RX_RTR_FRAME) &&
	    ((msg_head->nframes != 1) ||
	     (!(msg_head->can_id & CAN_RTR_FLAG))))
		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->rx_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;

		if (msg_head->nframes) {
			/* update CAN frames content */
			err = memcpy_from_msg(op->frames, msg,
					      msg_head->nframes * op->cfsiz);
			if (err < 0)
				return err;

			/* clear last_frames to indicate 'nothing received' */
			memset(op->last_frames, 0, msg_head->nframes * op->cfsiz);
		}

		op->nframes = msg_head->nframes;
		op->flags = msg_head->flags;

		/* Only an update -> do not call can_rx_register() */
		do_rx_register = 0;

	} 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->nframes = msg_head->nframes;
		op->cfsiz = CFSIZ(msg_head->flags);
		op->flags = msg_head->flags;

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

			/* create and init array for received CAN frames */
			op->last_frames = kcalloc(msg_head->nframes,
						  op->cfsiz,
						  GFP_KERNEL);
			if (!op->last_frames) {
				kfree(op->frames);
				kfree(op);
				return -ENOMEM;
			}

		} else {
			op->frames = &op->sframe;
			op->last_frames = &op->last_sframe;
		}

		if (msg_head->nframes) {
			err = memcpy_from_msg(op->frames, msg,
					      msg_head->nframes * op->cfsiz);
			if (err < 0) {
				if (op->frames != &op->sframe)
					kfree(op->frames);
				if (op->last_frames != &op->last_sframe)
					kfree(op->last_frames);
				kfree(op);
				return err;
			}
		}

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

		/* ifindex for timeout events w/o previous frame reception */
		op->rx_ifindex = ifindex;

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

		hrtimer_init(&op->thrtimer, CLOCK_MONOTONIC,
			     HRTIMER_MODE_REL_SOFT);
		op->thrtimer.function = bcm_rx_thr_handler;

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

		/* call can_rx_register() */
		do_rx_register = 1;

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

	/* check flags */

	if (op->flags & RX_RTR_FRAME) {
		struct canfd_frame *frame0 = op->frames;

		/* no timers in RTR-mode */
		hrtimer_cancel(&op->thrtimer);
		hrtimer_cancel(&op->timer);

		/*
		 * funny feature in RX(!)_SETUP only for RTR-mode:
		 * copy can_id into frame BUT without RTR-flag to
		 * prevent a full-load-loopback-test ... ;-]
		 */
		if ((op->flags & TX_CP_CAN_ID) ||
		    (frame0->can_id == op->can_id))
			frame0->can_id = op->can_id & ~CAN_RTR_FLAG;

	} else {
		if (op->flags & SETTIMER) {

			/* set timer value */
			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 value? */
			if (!op->kt_ival1)
				hrtimer_cancel(&op->timer);

			/*
			 * In any case cancel the throttle timer, flush
			 * potentially blocked msgs and reset throttle handling
			 */
			op->kt_lastmsg = 0;
			hrtimer_cancel(&op->thrtimer);
			bcm_rx_thr_flush(op);
		}

		if ((op->flags & STARTTIMER) && op->kt_ival1)
			hrtimer_start(&op->timer, op->kt_ival1,
				      HRTIMER_MODE_REL_SOFT);
	}

	/* now we can register for can_ids, if we added a new bcm_op */
	if (do_rx_register) {
		if (ifindex) {
			struct net_device *dev;

			dev = dev_get_by_index(sock_net(sk), ifindex);
			if (dev) {
				err = can_rx_register(sock_net(sk), dev,
						      op->can_id,
						      REGMASK(op->can_id),
						      bcm_rx_handler, op,
						      "bcm", sk);

				op->rx_reg_dev = dev;
				dev_put(dev);
			}

		} else
			err = can_rx_register(sock_net(sk), NULL, op->can_id,
					      REGMASK(op->can_id),
					      bcm_rx_handler, op, "bcm", sk);
		if (err) {
			/* this bcm rx op is broken -> remove it */
			list_del(&op->list);
			bcm_remove_op(op);
			return err;
		}
	}

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