int ldc_copy()

in kernel/ldc.c [2263:2350]


int ldc_copy(struct ldc_channel *lp, int copy_dir,
	     void *buf, unsigned int len, unsigned long offset,
	     struct ldc_trans_cookie *cookies, int ncookies)
{
	unsigned int orig_len;
	unsigned long ra;
	int i;

	if (copy_dir != LDC_COPY_IN && copy_dir != LDC_COPY_OUT) {
		printk(KERN_ERR PFX "ldc_copy: ID[%lu] Bad copy_dir[%d]\n",
		       lp->id, copy_dir);
		return -EINVAL;
	}

	ra = __pa(buf);
	if ((ra | len | offset) & (8UL - 1)) {
		printk(KERN_ERR PFX "ldc_copy: ID[%lu] Unaligned buffer "
		       "ra[%lx] len[%x] offset[%lx]\n",
		       lp->id, ra, len, offset);
		return -EFAULT;
	}

	if (lp->hs_state != LDC_HS_COMPLETE ||
	    (lp->flags & LDC_FLAG_RESET)) {
		printk(KERN_ERR PFX "ldc_copy: ID[%lu] Link down hs_state[%x] "
		       "flags[%x]\n", lp->id, lp->hs_state, lp->flags);
		return -ECONNRESET;
	}

	orig_len = len;
	for (i = 0; i < ncookies; i++) {
		unsigned long cookie_raddr = cookies[i].cookie_addr;
		unsigned long this_len = cookies[i].cookie_size;
		unsigned long actual_len;

		if (unlikely(offset)) {
			unsigned long this_off = offset;

			if (this_off > this_len)
				this_off = this_len;

			offset -= this_off;
			this_len -= this_off;
			if (!this_len)
				continue;
			cookie_raddr += this_off;
		}

		if (this_len > len)
			this_len = len;

		while (1) {
			unsigned long hv_err;

			hv_err = sun4v_ldc_copy(lp->id, copy_dir,
						cookie_raddr, ra,
						this_len, &actual_len);
			if (unlikely(hv_err)) {
				printk(KERN_ERR PFX "ldc_copy: ID[%lu] "
				       "HV error %lu\n",
				       lp->id, hv_err);
				if (lp->hs_state != LDC_HS_COMPLETE ||
				    (lp->flags & LDC_FLAG_RESET))
					return -ECONNRESET;
				else
					return -EFAULT;
			}

			cookie_raddr += actual_len;
			ra += actual_len;
			len -= actual_len;
			if (actual_len == this_len)
				break;

			this_len -= actual_len;
		}

		if (!len)
			break;
	}

	/* It is caller policy what to do about short copies.
	 * For example, a networking driver can declare the
	 * packet a runt and drop it.
	 */

	return orig_len - len;
}