static int handle_ipmb_get_msg_cmd(struct ipmi_smi *intf,
				   struct ipmi_smi_msg *msg)
{
	struct cmd_rcvr          *rcvr;
	int                      rv = 0;
	unsigned char            netfn;
	unsigned char            cmd;
	unsigned char            chan;
	struct ipmi_user         *user = NULL;
	struct ipmi_ipmb_addr    *ipmb_addr;
	struct ipmi_recv_msg     *recv_msg;

	if (msg->rsp_size < 10) {
		/* Message not big enough, just ignore it. */
		ipmi_inc_stat(intf, invalid_commands);
		return 0;
	}

	if (msg->rsp[2] != 0) {
		/* An error getting the response, just ignore it. */
		return 0;
	}

	netfn = msg->rsp[4] >> 2;
	cmd = msg->rsp[8];
	chan = msg->rsp[3] & 0xf;

	rcu_read_lock();
	rcvr = find_cmd_rcvr(intf, netfn, cmd, chan);
	if (rcvr) {
		user = rcvr->user;
		kref_get(&user->refcount);
	} else
		user = NULL;
	rcu_read_unlock();

	if (user == NULL) {
		/* We didn't find a user, deliver an error response. */
		ipmi_inc_stat(intf, unhandled_commands);

		msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
		msg->data[1] = IPMI_SEND_MSG_CMD;
		msg->data[2] = msg->rsp[3];
		msg->data[3] = msg->rsp[6];
		msg->data[4] = ((netfn + 1) << 2) | (msg->rsp[7] & 0x3);
		msg->data[5] = ipmb_checksum(&msg->data[3], 2);
		msg->data[6] = intf->addrinfo[msg->rsp[3] & 0xf].address;
		/* rqseq/lun */
		msg->data[7] = (msg->rsp[7] & 0xfc) | (msg->rsp[4] & 0x3);
		msg->data[8] = msg->rsp[8]; /* cmd */
		msg->data[9] = IPMI_INVALID_CMD_COMPLETION_CODE;
		msg->data[10] = ipmb_checksum(&msg->data[6], 4);
		msg->data_size = 11;

		pr_debug("Invalid command: %*ph\n", msg->data_size, msg->data);

		rcu_read_lock();
		if (!intf->in_shutdown) {
			smi_send(intf, intf->handlers, msg, 0);
			/*
			 * We used the message, so return the value
			 * that causes it to not be freed or
			 * queued.
			 */
			rv = -1;
		}
		rcu_read_unlock();
	} else {
		recv_msg = ipmi_alloc_recv_msg();
		if (!recv_msg) {
			/*
			 * We couldn't allocate memory for the
			 * message, so requeue it for handling
			 * later.
			 */
			rv = 1;
			kref_put(&user->refcount, free_user);
		} else {
			/* Extract the source address from the data. */
			ipmb_addr = (struct ipmi_ipmb_addr *) &recv_msg->addr;
			ipmb_addr->addr_type = IPMI_IPMB_ADDR_TYPE;
			ipmb_addr->slave_addr = msg->rsp[6];
			ipmb_addr->lun = msg->rsp[7] & 3;
			ipmb_addr->channel = msg->rsp[3] & 0xf;

			/*
			 * Extract the rest of the message information
			 * from the IPMB header.
			 */
			recv_msg->user = user;
			recv_msg->recv_type = IPMI_CMD_RECV_TYPE;
			recv_msg->msgid = msg->rsp[7] >> 2;
			recv_msg->msg.netfn = msg->rsp[4] >> 2;
			recv_msg->msg.cmd = msg->rsp[8];
			recv_msg->msg.data = recv_msg->msg_data;

			/*
			 * We chop off 10, not 9 bytes because the checksum
			 * at the end also needs to be removed.
			 */
			recv_msg->msg.data_len = msg->rsp_size - 10;
			memcpy(recv_msg->msg_data, &msg->rsp[9],
			       msg->rsp_size - 10);
			if (deliver_response(intf, recv_msg))
				ipmi_inc_stat(intf, unhandled_commands);
			else
				ipmi_inc_stat(intf, handled_commands);
		}
	}

	return rv;
}


static bool sensor_fw_synd_rfr(struct mlx5_core_dev *dev)
{
	struct mlx5_core_health *health = &dev->priv.health;
	struct health_buffer __iomem *h = health->health;
	u32 rfr = ioread32be(&h->rfr) >> MLX5_RFR_OFFSET;
	u8 synd = ioread8(&h->synd);

	if (rfr && synd)
		mlx5_core_dbg(dev, "FW requests reset, synd: %d\n", synd);
	return rfr && synd;
}


void mlx5_drain_health_wq(struct mlx5_core_dev *dev)
{
	struct mlx5_core_health *health = &dev->priv.health;
	unsigned long flags;

	spin_lock_irqsave(&health->wq_lock, flags);
	set_bit(MLX5_DROP_NEW_HEALTH_WORK, &health->flags);
	spin_unlock_irqrestore(&health->wq_lock, flags);
	cancel_work_sync(&health->report_work);
	cancel_work_sync(&health->fatal_report_work);
}


static const char *hsynd_str(u8 synd)
{
	switch (synd) {
	case MLX5_HEALTH_SYNDR_FW_ERR:
		return "firmware internal error";
	case MLX5_HEALTH_SYNDR_IRISC_ERR:
		return "irisc not responding";
	case MLX5_HEALTH_SYNDR_HW_UNRECOVERABLE_ERR:
		return "unrecoverable hardware error";
	case MLX5_HEALTH_SYNDR_CRC_ERR:
		return "firmware CRC error";
	case MLX5_HEALTH_SYNDR_FETCH_PCI_ERR:
		return "ICM fetch PCI error";
	case MLX5_HEALTH_SYNDR_HW_FTL_ERR:
		return "HW fatal error\n";
	case MLX5_HEALTH_SYNDR_ASYNC_EQ_OVERRUN_ERR:
		return "async EQ buffer overrun";
	case MLX5_HEALTH_SYNDR_EQ_ERR:
		return "EQ error";
	case MLX5_HEALTH_SYNDR_EQ_INV:
		return "Invalid EQ referenced";
	case MLX5_HEALTH_SYNDR_FFSER_ERR:
		return "FFSER error";
	case MLX5_HEALTH_SYNDR_HIGH_TEMP:
		return "High temperature";
	default:
		return "unrecognized error";
	}
}


int vbg_hgcm_call(struct vbg_dev *gdev, u32 requestor, u32 client_id,
		  u32 function, u32 timeout_ms,
		  struct vmmdev_hgcm_function_parameter *parms, u32 parm_count,
		  int *vbox_status)
{
	struct vmmdev_hgcm_call *call;
	void **bounce_bufs = NULL;
	bool leak_it;
	size_t size;
	int i, ret;

	size = sizeof(struct vmmdev_hgcm_call) +
		   parm_count * sizeof(struct vmmdev_hgcm_function_parameter);
	/*
	 * Validate and buffer the parameters for the call. This also increases
	 * call_size with the amount of extra space needed for page lists.
	 */
	ret = hgcm_call_preprocess(parms, parm_count, &bounce_bufs, &size);
	if (ret) {
		/* Even on error bounce bufs may still have been allocated */
		goto free_bounce_bufs;
	}

	call = vbg_req_alloc(size, VMMDEVREQ_HGCM_CALL, requestor);
	if (!call) {
		ret = -ENOMEM;
		goto free_bounce_bufs;
	}

	hgcm_call_init_call(call, client_id, function, parms, parm_count,
			    bounce_bufs);

	ret = vbg_hgcm_do_call(gdev, call, timeout_ms, &leak_it);
	if (ret == 0) {
		*vbox_status = call->header.result;
		ret = hgcm_call_copy_back_result(call, parms, parm_count,
						 bounce_bufs);
	}

	if (!leak_it)
		vbg_req_free(call, size);

free_bounce_bufs:
	if (bounce_bufs) {
		for (i = 0; i < parm_count; i++)
			kvfree(bounce_bufs[i]);
		kfree(bounce_bufs);
	}

	return ret;
}


int vbg_hgcm_disconnect(struct vbg_dev *gdev, u32 requestor,
			u32 client_id, int *vbox_status)
{
	struct vmmdev_hgcm_disconnect *hgcm_disconnect = NULL;
	int rc;

	hgcm_disconnect = vbg_req_alloc(sizeof(*hgcm_disconnect),
					VMMDEVREQ_HGCM_DISCONNECT,
					requestor);
	if (!hgcm_disconnect)
		return -ENOMEM;

	hgcm_disconnect->header.flags = 0;
	hgcm_disconnect->client_id = client_id;

	rc = vbg_req_perform(gdev, hgcm_disconnect);

	if (rc == VINF_HGCM_ASYNC_EXECUTE)
		wait_event(gdev->hgcm_wq,
			   hgcm_req_done(gdev, &hgcm_disconnect->header));

	if (rc >= 0)
		rc = hgcm_disconnect->header.result;

	vbg_req_free(hgcm_disconnect, sizeof(*hgcm_disconnect));

	*vbox_status = rc;
	return 0;
}


int vbg_status_code_to_errno(int rc)
{
	if (rc >= 0)
		return 0;

	rc = -rc;
	if (rc >= ARRAY_SIZE(vbg_status_code_to_errno_table) ||
	    vbg_status_code_to_errno_table[rc] == 0) {
		vbg_warn("%s: Unhandled err %d\n", __func__, -rc);
		return -EPROTO;
	}

	return vbg_status_code_to_errno_table[rc];
}


static void __init of_unittest_overlay_i2c_12(void)
{
	/* device should enable */
	if (of_unittest_apply_overlay_check(12, 12, 0, 1, I2C_OVERLAY))
		return;

	unittest(1, "overlay test %d passed\n", 12);
}


static void __init of_unittest_parse_phandle_with_args(void)
{
	struct device_node *np;
	struct of_phandle_args args;
	int i, rc;

	np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a");
	if (!np) {
		pr_err("missing testcase data\n");
		return;
	}

	rc = of_count_phandle_with_args(np, "phandle-list", "#phandle-cells");
	unittest(rc == 7, "of_count_phandle_with_args() returned %i, expected 7\n", rc);

	for (i = 0; i < 8; i++) {
		bool passed = true;

		memset(&args, 0, sizeof(args));
		rc = of_parse_phandle_with_args(np, "phandle-list",
						"#phandle-cells", i, &args);

		/* Test the values from tests-phandle.dtsi */
		switch (i) {
		case 0:
			passed &= !rc;
			passed &= (args.args_count == 1);
			passed &= (args.args[0] == (i + 1));
			break;
		case 1:
			passed &= !rc;
			passed &= (args.args_count == 2);
			passed &= (args.args[0] == (i + 1));
			passed &= (args.args[1] == 0);
			break;
		case 2:
			passed &= (rc == -ENOENT);
			break;
		case 3:
			passed &= !rc;
			passed &= (args.args_count == 3);
			passed &= (args.args[0] == (i + 1));
			passed &= (args.args[1] == 4);
			passed &= (args.args[2] == 3);
			break;
		case 4:
			passed &= !rc;
			passed &= (args.args_count == 2);
			passed &= (args.args[0] == (i + 1));
			passed &= (args.args[1] == 100);
			break;
		case 5:
			passed &= !rc;
			passed &= (args.args_count == 0);
			break;
		case 6:
			passed &= !rc;
			passed &= (args.args_count == 1);
			passed &= (args.args[0] == (i + 1));
			break;
		case 7:
			passed &= (rc == -ENOENT);
			break;
		default:
			passed = false;
		}

		unittest(passed, "index %i - data error on node %pOF rc=%i\n",
			 i, args.np, rc);
	}

	/* Check for missing list property */
	memset(&args, 0, sizeof(args));
	rc = of_parse_phandle_with_args(np, "phandle-list-missing",
					"#phandle-cells", 0, &args);
	unittest(rc == -ENOENT, "expected:%i got:%i\n", -ENOENT, rc);
	rc = of_count_phandle_with_args(np, "phandle-list-missing",
					"#phandle-cells");
	unittest(rc == -ENOENT, "expected:%i got:%i\n", -ENOENT, rc);

	/* Check for missing cells property */
	memset(&args, 0, sizeof(args));
	rc = of_parse_phandle_with_args(np, "phandle-list",
					"#phandle-cells-missing", 0, &args);
	unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
	rc = of_count_phandle_with_args(np, "phandle-list",
					"#phandle-cells-missing");
	unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);

	/* Check for bad phandle in list */
	memset(&args, 0, sizeof(args));
	rc = of_parse_phandle_with_args(np, "phandle-list-bad-phandle",
					"#phandle-cells", 0, &args);
	unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
	rc = of_count_phandle_with_args(np, "phandle-list-bad-phandle",
					"#phandle-cells");
	unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);

	/* Check for incorrectly formed argument list */
	memset(&args, 0, sizeof(args));
	rc = of_parse_phandle_with_args(np, "phandle-list-bad-args",
					"#phandle-cells", 1, &args);
	unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
	rc = of_count_phandle_with_args(np, "phandle-list-bad-args",
					"#phandle-cells");
	unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
}


static void __init of_unittest_printf(void)
{
	struct device_node *np;
	const char *full_name = "/testcase-data/platform-tests/test-device@1/dev@100";
	char phandle_str[16] = "";

	np = of_find_node_by_path(full_name);
	if (!np) {
		unittest(np, "testcase data missing\n");
		return;
	}

	num_to_str(phandle_str, sizeof(phandle_str), np->phandle, 0);

	of_unittest_printf_one(np, "%pOF",  full_name);
	of_unittest_printf_one(np, "%pOFf", full_name);
	of_unittest_printf_one(np, "%pOFn", "dev");
	of_unittest_printf_one(np, "%2pOFn", "dev");
	of_unittest_printf_one(np, "%5pOFn", "  dev");
	of_unittest_printf_one(np, "%pOFnc", "dev:test-sub-device");
	of_unittest_printf_one(np, "%pOFp", phandle_str);
	of_unittest_printf_one(np, "%pOFP", "dev@100");
	of_unittest_printf_one(np, "ABC %pOFP ABC", "ABC dev@100 ABC");
	of_unittest_printf_one(np, "%10pOFP", "   dev@100");
	of_unittest_printf_one(np, "%-10pOFP", "dev@100   ");
	of_unittest_printf_one(of_root, "%pOFP", "/");
	of_unittest_printf_one(np, "%pOFF", "----");
	of_unittest_printf_one(np, "%pOFPF", "dev@100:----");
	of_unittest_printf_one(np, "%pOFPFPc", "dev@100:----:dev@100:test-sub-device");
	of_unittest_printf_one(np, "%pOFc", "test-sub-device");
	of_unittest_printf_one(np, "%pOFC",
			"\"test-sub-device\",\"test-compat2\",\"test-compat3\"");
}


static void __init of_unittest_overlay_8(void)
{
	int i, ov_id[2], ovcs_id;
	int overlay_nr = 8, unittest_nr = 8;
	const char *overlay_name;

	/* we don't care about device state in this test */

	/* apply the overlays */
	for (i = 0; i < 2; i++) {

		overlay_name = overlay_name_from_nr(overlay_nr + i);

		if (!overlay_data_apply(overlay_name, &ovcs_id)) {
			unittest(0, "could not apply overlay \"%s\"\n",
					overlay_name);
			return;
		}
		ov_id[i] = ovcs_id;
		of_unittest_track_overlay(ov_id[i]);
	}

	/* now try to remove first overlay (it should fail) */
	ovcs_id = ov_id[0];
	if (!of_overlay_remove(&ovcs_id)) {
		unittest(0, "%s was destroyed @\"%s\"\n",
				overlay_name_from_nr(overlay_nr + 0),
				unittest_path(unittest_nr,
					PDEV_OVERLAY));
		return;
	}

	/* removing them in order should work */
	for (i = 1; i >= 0; i--) {
		ovcs_id = ov_id[i];
		if (of_overlay_remove(&ovcs_id)) {
			unittest(0, "%s not destroyed @\"%s\"\n",
					overlay_name_from_nr(overlay_nr + i),
					unittest_path(unittest_nr,
						PDEV_OVERLAY));
			return;
		}
		of_unittest_untrack_overlay(ov_id[i]);
	}

	unittest(1, "overlay test %d passed\n", 8);
}


static int __init of_unittest_apply_revert_overlay_check(int overlay_nr,
		int unittest_nr, int before, int after,
		enum overlay_type ovtype)
{
	int ret, ovcs_id;

	/* unittest device must be in before state */
	if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
		unittest(0, "%s with device @\"%s\" %s\n",
				overlay_name_from_nr(overlay_nr),
				unittest_path(unittest_nr, ovtype),
				!before ? "enabled" : "disabled");
		return -EINVAL;
	}

	/* apply the overlay */
	ovcs_id = 0;
	ret = of_unittest_apply_overlay(overlay_nr, &ovcs_id);
	if (ret != 0) {
		/* of_unittest_apply_overlay already called unittest() */
		return ret;
	}

	/* unittest device must be in after state */
	if (of_unittest_device_exists(unittest_nr, ovtype) != after) {
		unittest(0, "%s failed to create @\"%s\" %s\n",
				overlay_name_from_nr(overlay_nr),
				unittest_path(unittest_nr, ovtype),
				!after ? "enabled" : "disabled");
		return -EINVAL;
	}

	ret = of_overlay_remove(&ovcs_id);
	if (ret != 0) {
		unittest(0, "%s failed to be destroyed @\"%s\"\n",
				overlay_name_from_nr(overlay_nr),
				unittest_path(unittest_nr, ovtype));
		return ret;
	}

	/* unittest device must be again in before state */
	if (of_unittest_device_exists(unittest_nr, PDEV_OVERLAY) != before) {
		unittest(0, "%s with device @\"%s\" %s\n",
				overlay_name_from_nr(overlay_nr),
				unittest_path(unittest_nr, ovtype),
				!before ? "enabled" : "disabled");
		return -EINVAL;
	}

	return 0;
}


static int unittest_i2c_master_xfer(struct i2c_adapter *adap,
		struct i2c_msg *msgs, int num)
{
	struct unittest_i2c_bus_data *std = i2c_get_adapdata(adap);

	(void)std;

	return num;
}


static int unittest_i2c_mux_remove(struct i2c_client *client)
{
	struct device *dev = &client->dev;
	struct device_node *np = client->dev.of_node;
	struct i2c_mux_core *muxc = i2c_get_clientdata(client);

	dev_dbg(dev, "%s for node @%pOF\n", __func__, np);
	i2c_mux_del_adapters(muxc);
	return 0;
}


static void __init of_unittest_overlay_5(void)
{
	/* device should disable */
	if (of_unittest_apply_revert_overlay_check(5, 5, 0, 1, PDEV_OVERLAY))
		return;

	unittest(1, "overlay test %d passed\n", 5);
}


static int crypto_report_aead(struct sk_buff *skb, struct crypto_alg *alg)
{
	struct crypto_stat_aead raead;

	memset(&raead, 0, sizeof(raead));

	strscpy(raead.type, "aead", sizeof(raead.type));

	raead.stat_encrypt_cnt = atomic64_read(&alg->stats.aead.encrypt_cnt);
	raead.stat_encrypt_tlen = atomic64_read(&alg->stats.aead.encrypt_tlen);
	raead.stat_decrypt_cnt = atomic64_read(&alg->stats.aead.decrypt_cnt);
	raead.stat_decrypt_tlen = atomic64_read(&alg->stats.aead.decrypt_tlen);
	raead.stat_err_cnt = atomic64_read(&alg->stats.aead.err_cnt);

	return nla_put(skb, CRYPTOCFGA_STAT_AEAD, sizeof(raead), &raead);
}


void sdma_all_running(struct hfi1_devdata *dd)
{
	struct sdma_engine *sde;
	unsigned int i;

	/* move all engines to running */
	for (i = 0; i < dd->num_sdma; ++i) {
		sde = &dd->per_sdma[i];
		sdma_process_event(sde, sdma_event_e30_go_running);
	}
}


static inline u16 submit_tx(struct sdma_engine *sde, struct sdma_txreq *tx)
{
	int i;
	u16 tail;
	struct sdma_desc *descp = tx->descp;
	u8 skip = 0, mode = ahg_mode(tx);

	tail = sde->descq_tail & sde->sdma_mask;
	sde->descq[tail].qw[0] = cpu_to_le64(descp->qw[0]);
	sde->descq[tail].qw[1] = cpu_to_le64(add_gen(sde, descp->qw[1]));
	trace_hfi1_sdma_descriptor(sde, descp->qw[0], descp->qw[1],
				   tail, &sde->descq[tail]);
	tail = ++sde->descq_tail & sde->sdma_mask;
	descp++;
	if (mode > SDMA_AHG_APPLY_UPDATE1)
		skip = mode >> 1;
	for (i = 1; i < tx->num_desc; i++, descp++) {
		u64 qw1;

		sde->descq[tail].qw[0] = cpu_to_le64(descp->qw[0]);
		if (skip) {
			/* edits don't have generation */
			qw1 = descp->qw[1];
			skip--;
		} else {
			/* replace generation with real one for non-edits */
			qw1 = add_gen(sde, descp->qw[1]);
		}
		sde->descq[tail].qw[1] = cpu_to_le64(qw1);
		trace_hfi1_sdma_descriptor(sde, descp->qw[0], qw1,
					   tail, &sde->descq[tail]);
		tail = ++sde->descq_tail & sde->sdma_mask;
	}
	tx->next_descq_idx = tail;
#ifdef CONFIG_HFI1_DEBUG_SDMA_ORDER
	tx->sn = sde->tail_sn++;
	trace_hfi1_sdma_in_sn(sde, tx->sn);
	WARN_ON_ONCE(sde->tx_ring[sde->tx_tail & sde->sdma_mask]);
#endif
	sde->tx_ring[sde->tx_tail++ & sde->sdma_mask] = tx;
	sde->desc_avail -= tx->num_desc;
	return tail;
}


static void set_sdma_integrity(struct sdma_engine *sde)
{
	struct hfi1_devdata *dd = sde->dd;

	write_sde_csr(sde, SD(CHECK_ENABLE),
		      hfi1_pkt_base_sdma_integrity(dd));
}


void sdma_clean(struct hfi1_devdata *dd, size_t num_engines)
{
	size_t i;
	struct sdma_engine *sde;

	if (dd->sdma_pad_dma) {
		dma_free_coherent(&dd->pcidev->dev, 4,
				  (void *)dd->sdma_pad_dma,
				  dd->sdma_pad_phys);
		dd->sdma_pad_dma = NULL;
		dd->sdma_pad_phys = 0;
	}
	if (dd->sdma_heads_dma) {
		dma_free_coherent(&dd->pcidev->dev, dd->sdma_heads_size,
				  (void *)dd->sdma_heads_dma,
				  dd->sdma_heads_phys);
		dd->sdma_heads_dma = NULL;
		dd->sdma_heads_phys = 0;
	}
	for (i = 0; dd->per_sdma && i < num_engines; ++i) {
		sde = &dd->per_sdma[i];

		sde->head_dma = NULL;
		sde->head_phys = 0;

		if (sde->descq) {
			dma_free_coherent(
				&dd->pcidev->dev,
				sde->descq_cnt * sizeof(u64[2]),
				sde->descq,
				sde->descq_phys
			);
			sde->descq = NULL;
			sde->descq_phys = 0;
		}
		kvfree(sde->tx_ring);
		sde->tx_ring = NULL;
	}
	spin_lock_irq(&dd->sde_map_lock);
	sdma_map_free(rcu_access_pointer(dd->sdma_map));
	RCU_INIT_POINTER(dd->sdma_map, NULL);
	spin_unlock_irq(&dd->sde_map_lock);
	synchronize_rcu();
	kfree(dd->per_sdma);
	dd->per_sdma = NULL;

	if (dd->sdma_rht) {
		rhashtable_free_and_destroy(dd->sdma_rht, sdma_rht_free, NULL);
		kfree(dd->sdma_rht);
		dd->sdma_rht = NULL;
	}
}


static void sdma_put(struct sdma_state *ss)
{
	kref_put(&ss->kref, sdma_complete);
}


static void sdma_map_rcu_callback(struct rcu_head *list)
{
	struct sdma_vl_map *m = container_of(list, struct sdma_vl_map, list);

	sdma_map_free(m);
}


struct sdma_engine *sdma_select_engine_sc(
	struct hfi1_devdata *dd,
	u32 selector,
	u8 sc5)
{
	u8 vl = sc_to_vlt(dd, sc5);

	return sdma_select_engine_vl(dd, selector, vl);
}


void sdma_ahg_free(struct sdma_engine *sde, int ahg_index)
{
	if (!sde)
		return;
	trace_hfi1_ahg_deallocate(sde, ahg_index);
	if (ahg_index < 0 || ahg_index > 31)
		return;
	clear_bit(ahg_index, &sde->ahg_bits);
}


void _sdma_txreq_ahgadd(
	struct sdma_txreq *tx,
	u8 num_ahg,
	u8 ahg_entry,
	u32 *ahg,
	u8 ahg_hlen)
{
	u32 i, shift = 0, desc = 0;
	u8 mode;

	WARN_ON_ONCE(num_ahg > 9 || (ahg_hlen & 3) || ahg_hlen == 4);
	/* compute mode */
	if (num_ahg == 1)
		mode = SDMA_AHG_APPLY_UPDATE1;
	else if (num_ahg <= 5)
		mode = SDMA_AHG_APPLY_UPDATE2;
	else
		mode = SDMA_AHG_APPLY_UPDATE3;
	tx->num_desc++;
	/* initialize to consumed descriptors to zero */
	switch (mode) {
	case SDMA_AHG_APPLY_UPDATE3:
		tx->num_desc++;
		tx->descs[2].qw[0] = 0;
		tx->descs[2].qw[1] = 0;
		/* FALLTHROUGH */
	case SDMA_AHG_APPLY_UPDATE2:
		tx->num_desc++;
		tx->descs[1].qw[0] = 0;
		tx->descs[1].qw[1] = 0;
		break;
	}
	ahg_hlen >>= 2;
	tx->descs[0].qw[1] |=
		(((u64)ahg_entry & SDMA_DESC1_HEADER_INDEX_MASK)
			<< SDMA_DESC1_HEADER_INDEX_SHIFT) |
		(((u64)ahg_hlen & SDMA_DESC1_HEADER_DWS_MASK)
			<< SDMA_DESC1_HEADER_DWS_SHIFT) |
		(((u64)mode & SDMA_DESC1_HEADER_MODE_MASK)
			<< SDMA_DESC1_HEADER_MODE_SHIFT) |
		(((u64)ahg[0] & SDMA_DESC1_HEADER_UPDATE1_MASK)
			<< SDMA_DESC1_HEADER_UPDATE1_SHIFT);
	for (i = 0; i < (num_ahg - 1); i++) {
		if (!shift && !(i & 2))
			desc++;
		tx->descs[desc].qw[!!(i & 2)] |=
			(((u64)ahg[i + 1])
				<< shift);
		shift = (shift + 32) & 63;
	}
}


void sdma_seqfile_dump_sde(struct seq_file *s, struct sdma_engine *sde)
{
	u16 head, tail;
	struct hw_sdma_desc *descqp;
	u64 desc[2];
	u64 addr;
	u8 gen;
	u16 len;

	head = sde->descq_head & sde->sdma_mask;
	tail = READ_ONCE(sde->descq_tail) & sde->sdma_mask;
	seq_printf(s, SDE_FMT, sde->this_idx,
		   sde->cpu,
		   sdma_state_name(sde->state.current_state),
		   (unsigned long long)read_sde_csr(sde, SD(CTRL)),
		   (unsigned long long)read_sde_csr(sde, SD(STATUS)),
		   (unsigned long long)read_sde_csr(sde, SD(ENG_ERR_STATUS)),
		   (unsigned long long)read_sde_csr(sde, SD(TAIL)), tail,
		   (unsigned long long)read_sde_csr(sde, SD(HEAD)), head,
		   (unsigned long long)le64_to_cpu(*sde->head_dma),
		   (unsigned long long)read_sde_csr(sde, SD(MEMORY)),
		   (unsigned long long)read_sde_csr(sde, SD(LEN_GEN)),
		   (unsigned long long)read_sde_csr(sde, SD(RELOAD_CNT)),
		   (unsigned long long)sde->last_status,
		   (unsigned long long)sde->ahg_bits,
		   sde->tx_tail,
		   sde->tx_head,
		   sde->descq_tail,
		   sde->descq_head,
		   !list_empty(&sde->flushlist),
		   sde->descq_full_count,
		   (unsigned long long)read_sde_csr(sde, SEND_DMA_CHECK_SLID));

	/* print info for each entry in the descriptor queue */
	while (head != tail) {
		char flags[6] = { 'x', 'x', 'x', 'x', 0 };

		descqp = &sde->descq[head];
		desc[0] = le64_to_cpu(descqp->qw[0]);
		desc[1] = le64_to_cpu(descqp->qw[1]);
		flags[0] = (desc[1] & SDMA_DESC1_INT_REQ_FLAG) ? 'I' : '-';
		flags[1] = (desc[1] & SDMA_DESC1_HEAD_TO_HOST_FLAG) ?
				'H' : '-';
		flags[2] = (desc[0] & SDMA_DESC0_FIRST_DESC_FLAG) ? 'F' : '-';
		flags[3] = (desc[0] & SDMA_DESC0_LAST_DESC_FLAG) ? 'L' : '-';
		addr = (desc[0] >> SDMA_DESC0_PHY_ADDR_SHIFT)
			& SDMA_DESC0_PHY_ADDR_MASK;
		gen = (desc[1] >> SDMA_DESC1_GENERATION_SHIFT)
			& SDMA_DESC1_GENERATION_MASK;
		len = (desc[0] >> SDMA_DESC0_BYTE_COUNT_SHIFT)
			& SDMA_DESC0_BYTE_COUNT_MASK;
		seq_printf(s,
			   "\tdesc[%u]: flags:%s addr:0x%016llx gen:%u len:%u bytes\n",
			   head, flags, addr, gen, len);
		if (desc[0] & SDMA_DESC0_FIRST_DESC_FLAG)
			seq_printf(s, "\t\tahgidx: %u ahgmode: %u\n",
				   (u8)((desc[1] &
					 SDMA_DESC1_HEADER_INDEX_SMASK) >>
					SDMA_DESC1_HEADER_INDEX_SHIFT),
				   (u8)((desc[1] &
					 SDMA_DESC1_HEADER_MODE_SMASK) >>
					SDMA_DESC1_HEADER_MODE_SHIFT));
		head = (head + 1) & sde->sdma_mask;
	}
}


static struct gs_can *gs_make_candev(unsigned int channel,
				     struct usb_interface *intf,
				     struct gs_device_config *dconf)
{
	struct gs_can *dev;
	struct net_device *netdev;
	int rc;
	struct gs_device_bt_const *bt_const;

	bt_const = kmalloc(sizeof(*bt_const), GFP_KERNEL);
	if (!bt_const)
		return ERR_PTR(-ENOMEM);

	/* fetch bit timing constants */
	rc = usb_control_msg(interface_to_usbdev(intf),
			     usb_rcvctrlpipe(interface_to_usbdev(intf), 0),
			     GS_USB_BREQ_BT_CONST,
			     USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
			     channel,
			     0,
			     bt_const,
			     sizeof(*bt_const),
			     1000);

	if (rc < 0) {
		dev_err(&intf->dev,
			"Couldn't get bit timing const for channel (err=%d)\n",
			rc);
		kfree(bt_const);
		return ERR_PTR(rc);
	}

	/* create netdev */
	netdev = alloc_candev(sizeof(struct gs_can), GS_MAX_TX_URBS);
	if (!netdev) {
		dev_err(&intf->dev, "Couldn't allocate candev\n");
		kfree(bt_const);
		return ERR_PTR(-ENOMEM);
	}

	dev = netdev_priv(netdev);

	netdev->netdev_ops = &gs_usb_netdev_ops;

	netdev->flags |= IFF_ECHO; /* we support full roundtrip echo */

	/* dev settup */
	strcpy(dev->bt_const.name, "gs_usb");
	dev->bt_const.tseg1_min = bt_const->tseg1_min;
	dev->bt_const.tseg1_max = bt_const->tseg1_max;
	dev->bt_const.tseg2_min = bt_const->tseg2_min;
	dev->bt_const.tseg2_max = bt_const->tseg2_max;
	dev->bt_const.sjw_max = bt_const->sjw_max;
	dev->bt_const.brp_min = bt_const->brp_min;
	dev->bt_const.brp_max = bt_const->brp_max;
	dev->bt_const.brp_inc = bt_const->brp_inc;

	dev->udev = interface_to_usbdev(intf);
	dev->iface = intf;
	dev->netdev = netdev;
	dev->channel = channel;

	init_usb_anchor(&dev->tx_submitted);
	atomic_set(&dev->active_tx_urbs, 0);
	spin_lock_init(&dev->tx_ctx_lock);
	for (rc = 0; rc < GS_MAX_TX_URBS; rc++) {
		dev->tx_context[rc].dev = dev;
		dev->tx_context[rc].echo_id = GS_MAX_TX_URBS;
	}

	/* can settup */
	dev->can.state = CAN_STATE_STOPPED;
	dev->can.clock.freq = bt_const->fclk_can;
	dev->can.bittiming_const = &dev->bt_const;
	dev->can.do_set_bittiming = gs_usb_set_bittiming;

	dev->can.ctrlmode_supported = 0;

	if (bt_const->feature & GS_CAN_FEATURE_LISTEN_ONLY)
		dev->can.ctrlmode_supported |= CAN_CTRLMODE_LISTENONLY;

	if (bt_const->feature & GS_CAN_FEATURE_LOOP_BACK)
		dev->can.ctrlmode_supported |= CAN_CTRLMODE_LOOPBACK;

	if (bt_const->feature & GS_CAN_FEATURE_TRIPLE_SAMPLE)
		dev->can.ctrlmode_supported |= CAN_CTRLMODE_3_SAMPLES;

	if (bt_const->feature & GS_CAN_FEATURE_ONE_SHOT)
		dev->can.ctrlmode_supported |= CAN_CTRLMODE_ONE_SHOT;

	SET_NETDEV_DEV(netdev, &intf->dev);

	if (dconf->sw_version > 1)
		if (bt_const->feature & GS_CAN_FEATURE_IDENTIFY)
			netdev->ethtool_ops = &gs_usb_ethtool_ops;

	kfree(bt_const);

	rc = register_candev(dev->netdev);
	if (rc) {
		free_candev(dev->netdev);
		dev_err(&intf->dev, "Couldn't register candev (err=%d)\n", rc);
		return ERR_PTR(rc);
	}

	return dev;
}


static int fsl_lpspi_transfer_one(struct spi_controller *controller,
				  struct spi_device *spi,
				  struct spi_transfer *t)
{
	struct fsl_lpspi_data *fsl_lpspi =
					spi_controller_get_devdata(controller);
	int ret;

	fsl_lpspi->is_first_byte = true;
	ret = fsl_lpspi_setup_transfer(controller, spi, t);
	if (ret < 0)
		return ret;

	fsl_lpspi_set_cmd(fsl_lpspi);
	fsl_lpspi->is_first_byte = false;

	if (fsl_lpspi->usedma)
		ret = fsl_lpspi_dma_transfer(controller, fsl_lpspi, t);
	else
		ret = fsl_lpspi_pio_transfer(controller, t);
	if (ret < 0)
		return ret;

	return 0;
}


static int fsl_lpspi_calculate_timeout(struct fsl_lpspi_data *fsl_lpspi,
				       int size)
{
	unsigned long timeout = 0;

	/* Time with actual data transfer and CS change delay related to HW */
	timeout = (8 + 4) * size / fsl_lpspi->config.speed_hz;

	/* Add extra second for scheduler related activities */
	timeout += 1;

	/* Double calculated timeout */
	return msecs_to_jiffies(2 * timeout * MSEC_PER_SEC);
}


static int fsl_lpspi_prepare_message(struct spi_controller *controller,
				     struct spi_message *msg)
{
	struct fsl_lpspi_data *fsl_lpspi =
					spi_controller_get_devdata(controller);
	struct spi_device *spi = msg->spi;
	int gpio = fsl_lpspi->chipselect[spi->chip_select];

	if (gpio_is_valid(gpio))
		gpio_direction_output(gpio, spi->mode & SPI_CS_HIGH ? 0 : 1);

	return 0;
}


static int fsl_lpspi_bytes_per_word(const int bpw)
{
	return DIV_ROUND_UP(bpw, BITS_PER_BYTE);
}


static int fsl_lpspi_slave_abort(struct spi_controller *controller)
{
	struct fsl_lpspi_data *fsl_lpspi =
				spi_controller_get_devdata(controller);

	fsl_lpspi->slave_aborted = true;
	if (!fsl_lpspi->usedma)
		complete(&fsl_lpspi->xfer_done);
	else {
		complete(&fsl_lpspi->dma_tx_completion);
		complete(&fsl_lpspi->dma_rx_completion);
	}

	return 0;
}


static void _rtl_usb_transmit(struct ieee80211_hw *hw, struct sk_buff *skb,
		       enum rtl_txq qnum)
{
	struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw));
	u32 ep_num;
	struct urb *_urb = NULL;
	struct sk_buff *_skb = NULL;

	WARN_ON(NULL == rtlusb->usb_tx_aggregate_hdl);
	if (unlikely(IS_USB_STOP(rtlusb))) {
		pr_err("USB device is stopping...\n");
		kfree_skb(skb);
		return;
	}
	ep_num = rtlusb->ep_map.ep_mapping[qnum];
	_skb = skb;
	_urb = _rtl_usb_tx_urb_setup(hw, _skb, ep_num);
	if (unlikely(!_urb)) {
		pr_err("Can't allocate urb. Drop skb!\n");
		kfree_skb(skb);
		return;
	}
	_rtl_submit_tx_urb(hw, _urb);
}


static struct sk_buff *_none_usb_tx_aggregate_hdl(struct ieee80211_hw *hw,
						  struct sk_buff_head *list)
{
	return skb_dequeue(list);
}


int rtl_usb_resume(struct usb_interface *pusb_intf)
{
	return 0;
}


static void _usb_write8_async(struct rtl_priv *rtlpriv, u32 addr, u8 val)
{
	struct device *dev = rtlpriv->io.dev;

	_usb_write_async(to_usb_device(dev), addr, val, 1);
}


static int rtl_usb_tx(struct ieee80211_hw *hw,
		      struct ieee80211_sta *sta,
		      struct sk_buff *skb,
		      struct rtl_tcb_desc *dummy)
{
	struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw));
	struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw));
	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data);
	__le16 fc = hdr->frame_control;
	u16 hw_queue;

	if (unlikely(is_hal_stop(rtlhal)))
		goto err_free;
	hw_queue = rtlusb->usb_mq_to_hwq(fc, skb_get_queue_mapping(skb));
	_rtl_usb_tx_preprocess(hw, sta, skb, hw_queue);
	_rtl_usb_transmit(hw, skb, hw_queue);
	return NETDEV_TX_OK;

err_free:
	dev_kfree_skb_any(skb);
	return NETDEV_TX_OK;
}


static void _rtl_usb_io_handler_init(struct device *dev,
				     struct ieee80211_hw *hw)
{
	struct rtl_priv *rtlpriv = rtl_priv(hw);

	rtlpriv->io.dev = dev;
	mutex_init(&rtlpriv->io.bb_mutex);
	rtlpriv->io.write8_async	= _usb_write8_async;
	rtlpriv->io.write16_async	= _usb_write16_async;
	rtlpriv->io.write32_async	= _usb_write32_async;
	rtlpriv->io.read8_sync		= _usb_read8_sync;
	rtlpriv->io.read16_sync		= _usb_read16_sync;
	rtlpriv->io.read32_sync		= _usb_read32_sync;
	rtlpriv->io.writen_sync		= _usb_writen_sync;
}


static void _rtl_usb_cleanup_rx(struct ieee80211_hw *hw)
{
	struct rtl_priv *rtlpriv = rtl_priv(hw);
	struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw));
	struct urb *urb;

	usb_kill_anchored_urbs(&rtlusb->rx_submitted);

	tasklet_kill(&rtlusb->rx_work_tasklet);
	cancel_work_sync(&rtlpriv->works.lps_change_work);

	flush_workqueue(rtlpriv->works.rtl_wq);
	destroy_workqueue(rtlpriv->works.rtl_wq);

	skb_queue_purge(&rtlusb->rx_queue);

	while ((urb = usb_get_from_anchor(&rtlusb->rx_cleanup_urbs))) {
		usb_free_coherent(urb->dev, urb->transfer_buffer_length,
				urb->transfer_buffer, urb->transfer_dma);
		usb_free_urb(urb);
	}
}


static int __init crypto_user_init(void)
{
	return register_pernet_subsys(&crypto_netlink_net_ops);
}


static void *iwl_dump_ini_mem_fill_header(struct iwl_fw_runtime *fwrt,
					  struct iwl_fw_ini_region_cfg *reg,
					  void *data)
{
	struct iwl_fw_ini_error_dump *dump = data;

	dump->header.version = cpu_to_le32(IWL_INI_DUMP_VER);

	return dump->ranges;
}


static void iwl_read_radio_regs(struct iwl_fw_runtime *fwrt,
				struct iwl_fw_error_dump_data **dump_data)
{
	u8 *pos = (void *)(*dump_data)->data;
	unsigned long flags;
	int i;

	IWL_DEBUG_INFO(fwrt, "WRT radio registers dump\n");

	if (!iwl_trans_grab_nic_access(fwrt->trans, &flags))
		return;

	(*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RADIO_REG);
	(*dump_data)->len = cpu_to_le32(RADIO_REG_MAX_READ);

	for (i = 0; i < RADIO_REG_MAX_READ; i++) {
		u32 rd_cmd = RADIO_RSP_RD_CMD;

		rd_cmd |= i << RADIO_RSP_ADDR_POS;
		iwl_write_prph_no_grab(fwrt->trans, RSP_RADIO_CMD, rd_cmd);
		*pos = (u8)iwl_read_prph_no_grab(fwrt->trans, RSP_RADIO_RDDAT);

		pos++;
	}

	*dump_data = iwl_fw_error_next_data(*dump_data);

	iwl_trans_release_nic_access(fwrt->trans, &flags);
}


int _iwl_fw_dbg_ini_collect(struct iwl_fw_runtime *fwrt,
			    enum iwl_fw_ini_trigger_id id)
{
	struct iwl_fw_ini_active_triggers *active;
	u32 occur, delay;
	unsigned long idx;

	if (WARN_ON(!iwl_fw_ini_trigger_on(fwrt, id)))
		return -EINVAL;

	if (!iwl_fw_ini_trigger_on(fwrt, id)) {
		IWL_WARN(fwrt, "WRT: Trigger %d is not active, aborting dump\n",
			 id);
		return -EINVAL;
	}

	active = &fwrt->dump.active_trigs[id];
	delay = le32_to_cpu(active->trig->dump_delay);
	occur = le32_to_cpu(active->trig->occurrences);
	if (!occur)
		return 0;

	active->trig->occurrences = cpu_to_le32(--occur);

	if (le32_to_cpu(active->trig->force_restart)) {
		IWL_WARN(fwrt, "WRT: Force restart: trigger %d fired.\n", id);
		iwl_force_nmi(fwrt->trans);
		return 0;
	}

	/* Check there is an available worker.
	 * ffz return value is undefined if no zero exists,
	 * so check against ~0UL first.
	 */
	if (fwrt->dump.active_wks == ~0UL)
		return -EBUSY;

	idx = ffz(fwrt->dump.active_wks);

	if (idx >= IWL_FW_RUNTIME_DUMP_WK_NUM ||
	    test_and_set_bit(fwrt->dump.wks[idx].idx, &fwrt->dump.active_wks))
		return -EBUSY;

	fwrt->dump.wks[idx].ini_trig_id = id;

	IWL_WARN(fwrt, "WRT: Collecting data: ini trigger %d fired.\n", id);

	schedule_delayed_work(&fwrt->dump.wks[idx].wk, usecs_to_jiffies(delay));

	return 0;
}


static int iwl_fw_dbg_suspend_resume_hcmd(struct iwl_trans *trans, bool suspend)
{
	struct iwl_dbg_suspend_resume_cmd cmd = {
		.operation = suspend ?
			cpu_to_le32(DBGC_SUSPEND_CMD) :
			cpu_to_le32(DBGC_RESUME_CMD),
	};
	struct iwl_host_cmd hcmd = {
		.id = WIDE_ID(DEBUG_GROUP, DBGC_SUSPEND_RESUME),
		.data[0] = &cmd,
		.len[0] = sizeof(cmd),
	};

	return iwl_trans_send_cmd(trans, &hcmd);
}


void iwl_fw_dbg_read_d3_debug_data(struct iwl_fw_runtime *fwrt)
{
	const struct iwl_cfg *cfg = fwrt->trans->cfg;

	if (!iwl_fw_dbg_is_d3_debug_enabled(fwrt))
		return;

	if (!fwrt->dump.d3_debug_data) {
		fwrt->dump.d3_debug_data = kmalloc(cfg->d3_debug_data_length,
						   GFP_KERNEL);
		if (!fwrt->dump.d3_debug_data) {
			IWL_ERR(fwrt,
				"failed to allocate memory for D3 debug data\n");
			return;
		}
	}

	/* if the buffer holds previous debug data it is overwritten */
	iwl_trans_read_mem_bytes(fwrt->trans, cfg->d3_debug_data_base_addr,
				 fwrt->dump.d3_debug_data,
				 cfg->d3_debug_data_length);
}


static u32 iwl_dump_ini_paging_get_size(struct iwl_fw_runtime *fwrt,
					struct iwl_fw_ini_region_cfg *reg)
{
	int i;
	u32 range_header_len = sizeof(struct iwl_fw_ini_error_dump_range);
	u32 size = sizeof(struct iwl_fw_ini_error_dump);

	if (fwrt->trans->trans_cfg->gen2) {
		for (i = 0; i < iwl_dump_ini_paging_ranges(fwrt, reg); i++)
			size += range_header_len +
				fwrt->trans->init_dram.paging[i].size;
	} else {
		for (i = 1; i <= iwl_dump_ini_paging_ranges(fwrt, reg); i++)
			size += range_header_len +
				fwrt->fw_paging_db[i].fw_paging_size;
	}

	return size;
}


static int mwifiex_pcie_disable_host_int(struct mwifiex_adapter *adapter)
{
	if (mwifiex_pcie_ok_to_access_hw(adapter)) {
		if (mwifiex_write_reg(adapter, PCIE_HOST_INT_MASK,
				      0x00000000)) {
			mwifiex_dbg(adapter, ERROR,
				    "Disable host interrupt failed\n");
			return -1;
		}
	}

	atomic_set(&adapter->tx_hw_pending, 0);
	return 0;
}


static int mwifiex_pcie_process_cmd_complete(struct mwifiex_adapter *adapter)
{
	struct pcie_service_card *card = adapter->card;
	const struct mwifiex_pcie_card_reg *reg = card->pcie.reg;
	struct sk_buff *skb = card->cmdrsp_buf;
	int count = 0;
	u16 rx_len;

	mwifiex_dbg(adapter, CMD,
		    "info: Rx CMD Response\n");

	if (adapter->curr_cmd)
		mwifiex_unmap_pci_memory(adapter, skb, PCI_DMA_FROMDEVICE);
	else
		pci_dma_sync_single_for_cpu(card->dev,
					    MWIFIEX_SKB_DMA_ADDR(skb),
					    MWIFIEX_UPLD_SIZE,
					    PCI_DMA_FROMDEVICE);

	/* Unmap the command as a response has been received. */
	if (card->cmd_buf) {
		mwifiex_unmap_pci_memory(adapter, card->cmd_buf,
					 PCI_DMA_TODEVICE);
		dev_kfree_skb_any(card->cmd_buf);
		card->cmd_buf = NULL;
	}

	rx_len = get_unaligned_le16(skb->data);
	skb_put(skb, MWIFIEX_UPLD_SIZE - skb->len);
	skb_trim(skb, rx_len);

	if (!adapter->curr_cmd) {
		if (adapter->ps_state == PS_STATE_SLEEP_CFM) {
			pci_dma_sync_single_for_device(card->dev,
						MWIFIEX_SKB_DMA_ADDR(skb),
						MWIFIEX_SLEEP_COOKIE_SIZE,
						PCI_DMA_FROMDEVICE);
			if (mwifiex_write_reg(adapter,
					      PCIE_CPU_INT_EVENT,
					      CPU_INTR_SLEEP_CFM_DONE)) {
				mwifiex_dbg(adapter, ERROR,
					    "Write register failed\n");
				return -1;
			}
			mwifiex_delay_for_sleep_cookie(adapter,
						       MWIFIEX_MAX_DELAY_COUNT);
			mwifiex_unmap_pci_memory(adapter, skb,
						 PCI_DMA_FROMDEVICE);
			skb_pull(skb, adapter->intf_hdr_len);
			while (reg->sleep_cookie && (count++ < 10) &&
			       mwifiex_pcie_ok_to_access_hw(adapter))
				usleep_range(50, 60);
			mwifiex_pcie_enable_host_int(adapter);
			mwifiex_process_sleep_confirm_resp(adapter, skb->data,
							   skb->len);
		} else {
			mwifiex_dbg(adapter, ERROR,
				    "There is no command but got cmdrsp\n");
		}
		memcpy(adapter->upld_buf, skb->data,
		       min_t(u32, MWIFIEX_SIZE_OF_CMD_BUFFER, skb->len));
		skb_push(skb, adapter->intf_hdr_len);
		if (mwifiex_map_pci_memory(adapter, skb, MWIFIEX_UPLD_SIZE,
					   PCI_DMA_FROMDEVICE))
			return -1;
	} else if (mwifiex_pcie_ok_to_access_hw(adapter)) {
		skb_pull(skb, adapter->intf_hdr_len);
		adapter->curr_cmd->resp_skb = skb;
		adapter->cmd_resp_received = true;
		/* Take the pointer and set it to CMD node and will
		   return in the response complete callback */
		card->cmdrsp_buf = NULL;

		/* Clear the cmd-rsp buffer address in scratch registers. This
		   will prevent firmware from writing to the same response
		   buffer again. */
		if (mwifiex_write_reg(adapter, reg->cmdrsp_addr_lo, 0)) {
			mwifiex_dbg(adapter, ERROR,
				    "cmd_done: failed to clear cmd_rsp_addr_lo\n");
			return -1;
		}
		/* Write the upper 32bits of the cmdrsp buffer physical
		   address */
		if (mwifiex_write_reg(adapter, reg->cmdrsp_addr_hi, 0)) {
			mwifiex_dbg(adapter, ERROR,
				    "cmd_done: failed to clear cmd_rsp_addr_hi\n");
			return -1;
		}
	}

	return 0;
}


static int mwifiex_pcie_create_txbd_ring(struct mwifiex_adapter *adapter)
{
	struct pcie_service_card *card = adapter->card;
	const struct mwifiex_pcie_card_reg *reg = card->pcie.reg;

	/*
	 * driver maintaines the write pointer and firmware maintaines the read
	 * pointer. The write pointer starts at 0 (zero) while the read pointer
	 * starts at zero with rollover bit set
	 */
	card->txbd_wrptr = 0;

	if (reg->pfu_enabled)
		card->txbd_rdptr = 0;
	else
		card->txbd_rdptr |= reg->tx_rollover_ind;

	/* allocate shared memory for the BD ring and divide the same in to
	   several descriptors */
	if (reg->pfu_enabled)
		card->txbd_ring_size = sizeof(struct mwifiex_pfu_buf_desc) *
				       MWIFIEX_MAX_TXRX_BD;
	else
		card->txbd_ring_size = sizeof(struct mwifiex_pcie_buf_desc) *
				       MWIFIEX_MAX_TXRX_BD;

	mwifiex_dbg(adapter, INFO,
		    "info: txbd_ring: Allocating %d bytes\n",
		    card->txbd_ring_size);
	card->txbd_ring_vbase = pci_alloc_consistent(card->dev,
						     card->txbd_ring_size,
						     &card->txbd_ring_pbase);
	if (!card->txbd_ring_vbase) {
		mwifiex_dbg(adapter, ERROR,
			    "allocate consistent memory (%d bytes) failed!\n",
			    card->txbd_ring_size);
		return -ENOMEM;
	}
	mwifiex_dbg(adapter, DATA,
		    "info: txbd_ring - base: %p, pbase: %#x:%x, len: %x\n",
		    card->txbd_ring_vbase, (unsigned int)card->txbd_ring_pbase,
		    (u32)((u64)card->txbd_ring_pbase >> 32),
		    card->txbd_ring_size);

	return mwifiex_init_txq_ring(adapter);
}


static irqreturn_t mwifiex_pcie_interrupt(int irq, void *context)
{
	struct mwifiex_msix_context *ctx = context;
	struct pci_dev *pdev = ctx->dev;
	struct pcie_service_card *card;
	struct mwifiex_adapter *adapter;

	card = pci_get_drvdata(pdev);

	if (!card->adapter) {
		pr_err("info: %s: card=%p adapter=%p\n", __func__, card,
		       card ? card->adapter : NULL);
		goto exit;
	}
	adapter = card->adapter;

	if (test_bit(MWIFIEX_SURPRISE_REMOVED, &adapter->work_flags))
		goto exit;

	if (card->msix_enable)
		mwifiex_interrupt_status(adapter, ctx->msg_id);
	else
		mwifiex_interrupt_status(adapter, -1);

	mwifiex_queue_main_work(adapter);

exit:
	return IRQ_HANDLED;
}


static int mwifiex_pcie_resume(struct device *dev)
{
	struct mwifiex_adapter *adapter;
	struct pcie_service_card *card = dev_get_drvdata(dev);

	if (!card->adapter) {
		dev_err(dev, "adapter structure is not valid\n");
		return 0;
	}

	adapter = card->adapter;

	if (!test_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags)) {
		mwifiex_dbg(adapter, WARN,
			    "Device already resumed\n");
		return 0;
	}

	clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags);

	mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA),
			  MWIFIEX_ASYNC_CMD);
	mwifiex_disable_wake(adapter);

	return 0;
}


static void mwifiex_unregister_dev(struct mwifiex_adapter *adapter)
{
	struct pcie_service_card *card = adapter->card;
	struct pci_dev *pdev = card->dev;
	int i;

	if (card->msix_enable) {
		for (i = 0; i < MWIFIEX_NUM_MSIX_VECTORS; i++)
			synchronize_irq(card->msix_entries[i].vector);

		for (i = 0; i < MWIFIEX_NUM_MSIX_VECTORS; i++)
			free_irq(card->msix_entries[i].vector,
				 &card->msix_ctx[i]);

		card->msix_enable = 0;
		pci_disable_msix(pdev);
	} else {
		mwifiex_dbg(adapter, INFO,
			    "%s(): calling free_irq()\n", __func__);
	       free_irq(card->dev->irq, &card->share_irq_ctx);

		if (card->msi_enable)
			pci_disable_msi(pdev);
	}
	card->adapter = NULL;
}


static int rpmsg_eptdev_destroy(struct device *dev, void *data)
{
	struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);

	mutex_lock(&eptdev->ept_lock);
	if (eptdev->ept) {
		rpmsg_destroy_ept(eptdev->ept);
		eptdev->ept = NULL;
	}
	mutex_unlock(&eptdev->ept_lock);

	/* wake up any blocked readers */
	wake_up_interruptible(&eptdev->readq);

	device_del(&eptdev->dev);
	put_device(&eptdev->dev);

	return 0;
}


static int cx23888_ir_log_status(struct v4l2_subdev *sd)
{
	struct cx23888_ir_state *state = to_state(sd);
	struct cx23885_dev *dev = state->dev;
	char *s;
	int i, j;

	u32 cntrl = cx23888_ir_read4(dev, CX23888_IR_CNTRL_REG);
	u32 txclk = cx23888_ir_read4(dev, CX23888_IR_TXCLK_REG) & TXCLK_TCD;
	u32 rxclk = cx23888_ir_read4(dev, CX23888_IR_RXCLK_REG) & RXCLK_RCD;
	u32 cduty = cx23888_ir_read4(dev, CX23888_IR_CDUTY_REG) & CDUTY_CDC;
	u32 stats = cx23888_ir_read4(dev, CX23888_IR_STATS_REG);
	u32 irqen = cx23888_ir_read4(dev, CX23888_IR_IRQEN_REG);
	u32 filtr = cx23888_ir_read4(dev, CX23888_IR_FILTR_REG) & FILTR_LPF;

	v4l2_info(sd, "IR Receiver:\n");
	v4l2_info(sd, "\tEnabled:                           %s\n",
		  cntrl & CNTRL_RXE ? "yes" : "no");
	v4l2_info(sd, "\tDemodulation from a carrier:       %s\n",
		  cntrl & CNTRL_DMD ? "enabled" : "disabled");
	v4l2_info(sd, "\tFIFO:                              %s\n",
		  cntrl & CNTRL_RFE ? "enabled" : "disabled");
	switch (cntrl & CNTRL_EDG) {
	case CNTRL_EDG_NONE:
		s = "disabled";
		break;
	case CNTRL_EDG_FALL:
		s = "falling edge";
		break;
	case CNTRL_EDG_RISE:
		s = "rising edge";
		break;
	case CNTRL_EDG_BOTH:
		s = "rising & falling edges";
		break;
	default:
		s = "??? edge";
		break;
	}
	v4l2_info(sd, "\tPulse timers' start/stop trigger:  %s\n", s);
	v4l2_info(sd, "\tFIFO data on pulse timer overflow: %s\n",
		  cntrl & CNTRL_R ? "not loaded" : "overflow marker");
	v4l2_info(sd, "\tFIFO interrupt watermark:          %s\n",
		  cntrl & CNTRL_RIC ? "not empty" : "half full or greater");
	v4l2_info(sd, "\tLoopback mode:                     %s\n",
		  cntrl & CNTRL_LBM ? "loopback active" : "normal receive");
	if (cntrl & CNTRL_DMD) {
		v4l2_info(sd, "\tExpected carrier (16 clocks):      %u Hz\n",
			  clock_divider_to_carrier_freq(rxclk));
		switch (cntrl & CNTRL_WIN) {
		case CNTRL_WIN_3_3:
			i = 3;
			j = 3;
			break;
		case CNTRL_WIN_4_3:
			i = 4;
			j = 3;
			break;
		case CNTRL_WIN_3_4:
			i = 3;
			j = 4;
			break;
		case CNTRL_WIN_4_4:
			i = 4;
			j = 4;
			break;
		default:
			i = 0;
			j = 0;
			break;
		}
		v4l2_info(sd, "\tNext carrier edge window:	    16 clocks -%1d/+%1d, %u to %u Hz\n",
			  i, j,
			  clock_divider_to_freq(rxclk, 16 + j),
			  clock_divider_to_freq(rxclk, 16 - i));
	}
	v4l2_info(sd, "\tMax measurable pulse width:        %u us, %llu ns\n",
		  pulse_width_count_to_us(FIFO_RXTX, rxclk),
		  pulse_width_count_to_ns(FIFO_RXTX, rxclk));
	v4l2_info(sd, "\tLow pass filter:                   %s\n",
		  filtr ? "enabled" : "disabled");
	if (filtr)
		v4l2_info(sd, "\tMin acceptable pulse width (LPF):  %u us, %u ns\n",
			  lpf_count_to_us(filtr),
			  lpf_count_to_ns(filtr));
	v4l2_info(sd, "\tPulse width timer timed-out:       %s\n",
		  stats & STATS_RTO ? "yes" : "no");
	v4l2_info(sd, "\tPulse width timer time-out intr:   %s\n",
		  irqen & IRQEN_RTE ? "enabled" : "disabled");
	v4l2_info(sd, "\tFIFO overrun:                      %s\n",
		  stats & STATS_ROR ? "yes" : "no");
	v4l2_info(sd, "\tFIFO overrun interrupt:            %s\n",
		  irqen & IRQEN_ROE ? "enabled" : "disabled");
	v4l2_info(sd, "\tBusy:                              %s\n",
		  stats & STATS_RBY ? "yes" : "no");
	v4l2_info(sd, "\tFIFO service requested:            %s\n",
		  stats & STATS_RSR ? "yes" : "no");
	v4l2_info(sd, "\tFIFO service request interrupt:    %s\n",
		  irqen & IRQEN_RSE ? "enabled" : "disabled");

	v4l2_info(sd, "IR Transmitter:\n");
	v4l2_info(sd, "\tEnabled:                           %s\n",
		  cntrl & CNTRL_TXE ? "yes" : "no");
	v4l2_info(sd, "\tModulation onto a carrier:         %s\n",
		  cntrl & CNTRL_MOD ? "enabled" : "disabled");
	v4l2_info(sd, "\tFIFO:                              %s\n",
		  cntrl & CNTRL_TFE ? "enabled" : "disabled");
	v4l2_info(sd, "\tFIFO interrupt watermark:          %s\n",
		  cntrl & CNTRL_TIC ? "not empty" : "half full or less");
	v4l2_info(sd, "\tOutput pin level inversion         %s\n",
		  cntrl & CNTRL_IVO ? "yes" : "no");
	v4l2_info(sd, "\tCarrier polarity:                  %s\n",
		  cntrl & CNTRL_CPL ? "space:burst mark:noburst"
				    : "space:noburst mark:burst");
	if (cntrl & CNTRL_MOD) {
		v4l2_info(sd, "\tCarrier (16 clocks):               %u Hz\n",
			  clock_divider_to_carrier_freq(txclk));
		v4l2_info(sd, "\tCarrier duty cycle:                %2u/16\n",
			  cduty + 1);
	}
	v4l2_info(sd, "\tMax pulse width:                   %u us, %llu ns\n",
		  pulse_width_count_to_us(FIFO_RXTX, txclk),
		  pulse_width_count_to_ns(FIFO_RXTX, txclk));
	v4l2_info(sd, "\tBusy:                              %s\n",
		  stats & STATS_TBY ? "yes" : "no");
	v4l2_info(sd, "\tFIFO service requested:            %s\n",
		  stats & STATS_TSR ? "yes" : "no");
	v4l2_info(sd, "\tFIFO service request interrupt:    %s\n",
		  irqen & IRQEN_TSE ? "enabled" : "disabled");

	return 0;
}


inline int cx23888_ir_write4(struct cx23885_dev *dev, u32 addr, u32 value)
{
	cx_write(addr, value);
	return 0;
}


static inline unsigned int clock_divider_to_carrier_freq(unsigned int divider)
{
	return DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ, (divider + 1) * 16);
}


static inline void control_tx_enable(struct cx23885_dev *dev, bool enable)
{
	cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~(CNTRL_TXE | CNTRL_TFE),
			   enable ? (CNTRL_TXE | CNTRL_TFE) : 0);
}


int service_main(int argc __attribute__((unused)),
                 char **argv __attribute__((unused)),
                 char **envp __attribute__((unused)))
{
    sasl_security_properties_t *secprops=NULL;
    const char *mechlist, *mech;
    int mechcount = 0;
    size_t mechlen;
    struct auth_scheme_t *scheme;
    struct http_connection http_conn;

    prometheus_decrement(CYRUS_HTTP_READY_LISTENERS);

    session_new_id();

    signals_poll();

    httpd_in = prot_new(0, 0);
    httpd_out = prot_new(1, 1);
    protgroup_insert(protin, httpd_in);

    /* Setup HTTP connection */
    memset(&http_conn, 0, sizeof(struct http_connection));
    http_conn.pin = httpd_in;
    http_conn.pout = httpd_out;
    http_conn.logfd = -1;

    /* Create XML parser context */
    if (!(http_conn.xml = xmlNewParserCtxt())) {
        fatal("Unable to create XML parser", EX_TEMPFAIL);
    }

    /* Find out name of client host */
    http_conn.clienthost = get_clienthost(0, &httpd_localip, &httpd_remoteip);

    if (httpd_localip && httpd_remoteip) {
        buf_setcstr(&saslprops.ipremoteport, httpd_remoteip);
        buf_setcstr(&saslprops.iplocalport, httpd_localip);
    }

    /* other params should be filled in */
    if (sasl_server_new("HTTP", config_servername, NULL,
                        buf_cstringnull_ifempty(&saslprops.iplocalport),
                        buf_cstringnull_ifempty(&saslprops.ipremoteport),
                        NULL, SASL_USAGE_FLAGS, &httpd_saslconn) != SASL_OK)
        fatal("SASL failed initializing: sasl_server_new()",EX_TEMPFAIL);

    /* will always return something valid */
    secprops = mysasl_secprops(0);

    /* no HTTP clients seem to use "auth-int" */
    secprops->max_ssf = 0;                              /* "auth" only */
    secprops->maxbufsize = 0;                           /* don't need maxbuf */
    if (sasl_setprop(httpd_saslconn, SASL_SEC_PROPS, secprops) != SASL_OK)
        fatal("Failed to set SASL property", EX_TEMPFAIL);
    if (sasl_setprop(httpd_saslconn, SASL_SSF_EXTERNAL, &extprops_ssf) != SASL_OK)
        fatal("Failed to set SASL property", EX_TEMPFAIL);

    if (httpd_remoteip) {
        char hbuf[NI_MAXHOST], *p;

        /* Create pre-authentication telemetry log based on client IP */
        strlcpy(hbuf, httpd_remoteip, NI_MAXHOST);
        if ((p = strchr(hbuf, ';'))) *p = '\0';
        http_conn.logfd = telemetry_log(hbuf, httpd_in, httpd_out, 0);
    }

    /* See which auth schemes are available to us */
    avail_auth_schemes = 0; /* Reset auth schemes for each connection */
    if ((extprops_ssf >= 2) || config_getswitch(IMAPOPT_ALLOWPLAINTEXT)) {
        avail_auth_schemes |=  AUTH_BASIC;
    }
    sasl_listmech(httpd_saslconn, NULL, NULL, " ", NULL,
                  &mechlist, NULL, &mechcount);
    for (mech = mechlist; mechcount--; mech += ++mechlen) {
        mechlen = strcspn(mech, " \0");
        for (scheme = auth_schemes; scheme->name; scheme++) {
            if (scheme->saslmech && !strncmp(mech, scheme->saslmech, mechlen)) {
                avail_auth_schemes |= scheme->id;
                break;
            }
        }
    }
    httpd_tls_required =
        config_getswitch(IMAPOPT_TLS_REQUIRED) || !avail_auth_schemes;

    proc_register(config_ident, http_conn.clienthost, NULL, NULL, NULL);

    /* Set inactivity timer */
    httpd_timeout = config_getduration(IMAPOPT_HTTPTIMEOUT, 'm');
    if (httpd_timeout < 0) httpd_timeout = 0;
    prot_settimeout(httpd_in, httpd_timeout);
    prot_setflushonread(httpd_in, httpd_out);

    /* we were connected on https port so we should do
       TLS negotiation immediately */
    if (https == 1) {
        if (starttls(NULL, &http_conn) != 0) shut_down(0);
    }
    else if (http2_preface(&http_conn)) {
        /* HTTP/2 client connection preface */
        if (http2_start_session(NULL, &http_conn) != 0)
            fatal("Failed initializing HTTP/2 session", EX_TEMPFAIL);
    }

    /* Setup the signal handler for keepalive heartbeat */
    httpd_keepalive = config_getduration(IMAPOPT_HTTPKEEPALIVE, 's');
    if (httpd_keepalive < 0) httpd_keepalive = 0;
    if (httpd_keepalive) {
        struct sigaction action;

        sigemptyset(&action.sa_mask);
        action.sa_flags = 0;
#ifdef SA_RESTART
        action.sa_flags |= SA_RESTART;
#endif
        action.sa_handler = sigalrm_handler;
        if (sigaction(SIGALRM, &action, NULL) < 0) {
            syslog(LOG_ERR, "unable to install signal handler for %d: %m", SIGALRM);
            httpd_keepalive = 0;
        }
    }

    index_text_extractor_init(httpd_in);

    prometheus_increment(CYRUS_HTTP_CONNECTIONS_TOTAL);
    prometheus_increment(CYRUS_HTTP_ACTIVE_CONNECTIONS);

    cmdloop(&http_conn);

    prometheus_decrement(CYRUS_HTTP_ACTIVE_CONNECTIONS);

    /* Closing connection */

    /* cleanup */
    signal(SIGALRM, SIG_IGN);
    httpd_reset(&http_conn);

    prometheus_increment(CYRUS_HTTP_READY_LISTENERS);

    return 0;
}


static int auth_success(struct transaction_t *txn, const char *userid)
{
    struct auth_scheme_t *scheme = txn->auth_chal.scheme;
    int logfd = txn->conn->logfd;
    int i;

    httpd_userid = xstrdup(userid);
    httpd_userisanonymous = is_userid_anonymous(httpd_userid);

    syslog(LOG_NOTICE, "login: %s %s %s%s %s SESSIONID=<%s>",
           txn->conn->clienthost, httpd_userid, scheme->name,
           txn->conn->tls_ctx ? "+TLS" : "", "User logged in",
           session_id());

    /* Recreate telemetry log entry for request (w/ credentials redacted) */
    assert(!buf_len(&txn->buf));
    buf_printf(&txn->buf, "<%ld<", time(NULL));         /* timestamp */
    buf_printf(&txn->buf, "%s %s %s\r\n",               /* request-line*/
               txn->req_line.meth, txn->req_line.uri, txn->req_line.ver);
    spool_enum_hdrcache(txn->req_hdrs,                  /* header fields */
                        &log_cachehdr, &txn->buf);
    buf_appendcstr(&txn->buf, "\r\n");                  /* CRLF */
    buf_append(&txn->buf, &txn->req_body.payload);      /* message body */
    buf_appendmap(&txn->buf,                            /* buffered input */
                  (const char *) httpd_in->ptr, httpd_in->cnt);

    if (logfd != -1) {
        /* Rewind log to current request and truncate it */
        off_t end = lseek(logfd, 0, SEEK_END);

        if (ftruncate(logfd, end - buf_len(&txn->buf)))
            syslog(LOG_ERR, "IOERROR: failed to truncate http log");

        /* Close existing telemetry log */
        close(logfd);
    }

    prot_setlog(httpd_in, PROT_NO_FD);
    prot_setlog(httpd_out, PROT_NO_FD);

    /* Create telemetry log based on new userid */
    if (txn->conn->sess_ctx)
        txn->conn->logfd = logfd = telemetry_log(userid, NULL, NULL, 0);
    else
        txn->conn->logfd = logfd = telemetry_log(userid, httpd_in, httpd_out, 0);

    if (logfd != -1) {
        /* Log credential-redacted request */
        if (write(logfd, buf_cstring(&txn->buf), buf_len(&txn->buf)) < 0)
            syslog(LOG_ERR, "IOERROR: failed to write to http log");
    }

    buf_reset(&txn->buf);

    /* Do any namespace specific post-auth processing */
    for (i = 0; http_namespaces[i]; i++) {
        if (http_namespaces[i]->enabled && http_namespaces[i]->auth) {
            int ret = http_namespaces[i]->auth(httpd_userid);
            if (ret) return ret;
        }
    }

    return 0;
}


static int parse_request_line(struct transaction_t *txn)
{
    struct request_line_t *req_line = &txn->req_line;
    char *p;
    tok_t tok;
    int ret = 0;

    /* Trim CRLF from request-line */
    p = req_line->buf + strlen(req_line->buf);
    if (p[-1] == '\n') *--p = '\0';
    if (p[-1] == '\r') *--p = '\0';

    /* Parse request-line = method SP request-target SP HTTP-version CRLF */
    tok_initm(&tok, req_line->buf, " ", 0);
    if (!(req_line->meth = tok_next(&tok))) {
        ret = HTTP_BAD_REQUEST;
        txn->error.desc = "Missing method in request-line";
    }
    else if (!(req_line->uri = tok_next(&tok))) {
        ret = HTTP_BAD_REQUEST;
        txn->error.desc = "Missing request-target in request-line";
    }
    else if ((size_t) (p - req_line->buf) > MAX_REQ_LINE - 2) {
        /* request-line overran the size of our buffer */
        ret = HTTP_URI_TOO_LONG;
        buf_printf(&txn->buf,
                   "Length of request-line MUST be less than %u octets",
                   MAX_REQ_LINE);
        txn->error.desc = buf_cstring(&txn->buf);
    }
    else if (!(req_line->ver = tok_next(&tok))) {
        ret = HTTP_BAD_REQUEST;
        txn->error.desc = "Missing HTTP-version in request-line";
    }
    else if (tok_next(&tok)) {
        ret = HTTP_BAD_REQUEST;
        txn->error.desc = "Unexpected extra argument(s) in request-line";
    }

    /* Check HTTP-Version - MUST be HTTP/1.x */
    else if (strlen(req_line->ver) != HTTP_VERSION_LEN
             || strncmp(req_line->ver, HTTP_VERSION, HTTP_VERSION_LEN-1)
             || !isdigit(req_line->ver[HTTP_VERSION_LEN-1])) {
        ret = HTTP_BAD_VERSION;
        buf_printf(&txn->buf,
                   "This server only speaks %.*sx",
                   HTTP_VERSION_LEN-1, HTTP_VERSION);
        txn->error.desc = buf_cstring(&txn->buf);
    }
    else if (req_line->ver[HTTP_VERSION_LEN-1] == '0') {
        /* HTTP/1.0 connection */
        txn->flags.ver = VER_1_0;
    }
    tok_fini(&tok);

    return ret;
}


EXPORTED int end_resp_headers(struct transaction_t *txn, long code)
{
    int r = 0;

    if (txn->flags.ver == VER_2) {
        r = http2_end_headers(txn, code);
    }
    else {
        /* CRLF terminating the header block */
        prot_puts(txn->conn->pout, "\r\n");
    }

    return r;
}


static int starttls(struct transaction_t *txn, struct http_connection *conn)
{
    int https = (txn == NULL);
    int result;
    SSL_CTX *ctx = NULL;

    if (!conn) conn = txn->conn;

    result=tls_init_serverengine("http",
                                 5,        /* depth to verify */
                                 !https,   /* can client auth? */
                                 &ctx);

    if (result == -1) {
        syslog(LOG_ERR, "error initializing TLS");

        if (txn) txn->error.desc = "Error initializing TLS";
        return HTTP_SERVER_ERROR;
    }

    if (http2_enabled()) {
#ifdef HAVE_TLS_ALPN
        /* enable TLS ALPN extension */
        SSL_CTX_set_alpn_select_cb(ctx, alpn_select_cb, conn);
#endif
    }

    if (!https) {
        /* tell client to start TLS upgrade (RFC 2817) */
        response_header(HTTP_SWITCH_PROT, txn);
    }

    result=tls_start_servertls(0, /* read */
                               1, /* write */
                               https ? 180 : httpd_timeout,
                               &saslprops,
                               (SSL **) &conn->tls_ctx);

    /* if error */
    if (result == -1) {
        syslog(LOG_NOTICE, "starttls failed: %s", conn->clienthost);

        if (txn) txn->error.desc = "Error negotiating TLS";
        return HTTP_BAD_REQUEST;
    }

    /* tell SASL about the negotiated layer */
    result = saslprops_set_tls(&saslprops, httpd_saslconn);
    if (result != SASL_OK) {
        syslog(LOG_NOTICE, "saslprops_set_tls() failed: cmd_starttls()");
        if (https == 0) {
            fatal("saslprops_set_tls() failed: cmd_starttls()", EX_TEMPFAIL);
        } else {
            shut_down(0);
        }
    }

    /* tell the prot layer about our new layers */
    prot_settls(httpd_in, conn->tls_ctx);
    prot_settls(httpd_out, conn->tls_ctx);

    httpd_tls_required = 0;

    avail_auth_schemes |= AUTH_BASIC;

    return 0;
}


EXPORTED int examine_request(struct transaction_t *txn, const char *uri)
{
    int ret = 0, sasl_result = 0;
    const char *query;
    const struct namespace_t *namespace;
    struct request_line_t *req_line = &txn->req_line;

    if (!uri) uri = req_line->uri;

    /* Check method */
    if ((ret = check_method(txn))) return ret;

    /* Parse request-target URI */
    if (!(txn->req_uri = parse_uri(txn->meth, uri, 1, &txn->error.desc))) {
        return HTTP_BAD_REQUEST;
    }

    /* Perform pre-authentication check of headers */
    if ((ret = preauth_check_hdrs(txn))) return ret;

    /* Find the namespace of the requested resource */
    if ((ret = check_namespace(txn))) return ret;

    /* Perform check of authentication headers */
    ret = auth_check_hdrs(txn, &sasl_result);

    if (ret && ret != HTTP_UNAUTHORIZED) return ret;

    /* Register service/module and method */
    namespace = txn->req_tgt.namespace;
    buf_printf(&txn->buf, "%s%s", config_ident,
               namespace->well_known ? strrchr(namespace->well_known, '/') :
               namespace->prefix);
    proc_register(buf_cstring(&txn->buf), txn->conn->clienthost, httpd_userid,
                  txn->req_tgt.path, txn->req_line.meth);
    buf_reset(&txn->buf);

    /* Request authentication, if necessary */
    if (!httpd_userid && namespace->need_auth(txn)) {
        ret = HTTP_UNAUTHORIZED;
    }

    if (ret) return client_need_auth(txn, sasl_result);

    /* Parse any query parameters */
    construct_hash_table(&txn->req_qparams, 10, 1);
    query = URI_QUERY(txn->req_uri);
    if (query) parse_query_params(txn, query);

    /* Perform post-authentication check of headers */
    postauth_check_hdrs(txn);

    return 0;
}


static int brotli_compress(struct transaction_t *txn __attribute__((unused)),
                           unsigned flags __attribute__((unused)),
                           const char *buf __attribute__((unused)),
                           unsigned len __attribute__((unused)))
{
    fatal("Brotli Compression requested, but not available", EX_SOFTWARE);
}


HIDDEN void *zstd_init() { return NULL; }


HIDDEN void *brotli_init() { return NULL; }


EXPORTED xmlURIPtr parse_uri(unsigned meth, const char *uri, unsigned path_reqd,
                    const char **errstr)
{
    xmlURIPtr p_uri;  /* parsed URI */

    /* Parse entire URI */
    if ((p_uri = xmlParseURI(uri)) == NULL) {
        *errstr = "Illegal request target URI";
        goto bad_request;
    }

    if (p_uri->scheme) {
        /* Check sanity of scheme */

        if (strcasecmp(p_uri->scheme, "http") &&
            strcasecmp(p_uri->scheme, "https")) {
            *errstr = "Unsupported URI scheme";
            goto bad_request;
        }
    }

    /* Check sanity of path */
    if (path_reqd && (!p_uri->path || !*p_uri->path)) {
        *errstr = "Empty path in target URI";
        goto bad_request;
    }
    else if (p_uri->path) {
        size_t pathlen = strlen(p_uri->path);
        if ((p_uri->path[0] != '/') &&
            (strcmp(p_uri->path, "*") || (meth != METH_OPTIONS))) {
            /* No special URLs except for "OPTIONS * HTTP/1.1" */
            *errstr = "Illegal request target URI";
            goto bad_request;
        }
        else if (strstr(p_uri->path, "/../")) {
            /* Don't allow access up directory tree */
            *errstr = "Illegal request target URI";
            goto bad_request;
        }
        else if (pathlen >= 3 && !strcmp("/..", p_uri->path + pathlen - 3)) {
            /* Don't allow access up directory tree */
            *errstr = "Illegal request target URI";
            goto bad_request;
        }
        else if (pathlen > MAX_MAILBOX_PATH) {
            *errstr = "Request target URI too long";
            goto bad_request;
        }
    }

    return p_uri;

  bad_request:
    if (p_uri) xmlFreeURI(p_uri);
    return NULL;
}


EXPORTED int etagcmp(const char *hdr, const char *etag)
{
    size_t len;

    if (!etag) return -1;               /* no representation       */
    if (!strcmp(hdr, "*")) return 0;    /* any representation      */

    len = strlen(etag);
    if (!strncmp(hdr, "W/", 2)) hdr+=2; /* skip weak prefix        */
    if (*hdr++ != '\"') return 1;       /* match/skip open DQUOTE  */
    if (strlen(hdr) != len+1) return 1; /* make sure lengths match */
    if (hdr[len] != '\"') return 1;     /* match close DQUOTE      */

    return strncmp(hdr, etag, len);
}


static int zstd_compress(struct transaction_t *txn,
                         unsigned flags, const char *buf, unsigned len)
{
    /* Only flush for static content or on last (zero-length) chunk */
    ZSTD_EndDirective mode = (flags & COMPRESS_END) ? ZSTD_e_end : ZSTD_e_flush;
    ZSTD_inBuffer input = { buf, len, 0 };
    ZSTD_CCtx *cctx = txn->zstd;
    size_t remaining;

    if (flags & COMPRESS_START) ZSTD_CCtx_reset(cctx, ZSTD_reset_session_only);

    buf_ensure(&txn->zbuf, ZSTD_compressBound(len));
    buf_reset(&txn->zbuf);

    ZSTD_outBuffer output = { txn->zbuf.s, txn->zbuf.alloc, 0 };
    do {
        remaining = ZSTD_compressStream2(cctx, &output, &input, mode);

        if (ZSTD_isError(remaining)) {
            syslog(LOG_ERR, "Zstandard: %s",
                   ZSTD_getErrorString(ZSTD_getErrorCode(remaining)));
            return -1;
        }
    } while (remaining || (input.pos != input.size));

    buf_truncate(&txn->zbuf, output.pos);

    return 0;
}


HIDDEN void *zlib_init()
{
    z_stream *zstrm = xzmalloc(sizeof(z_stream));

    /* Always use gzip format because IE incorrectly uses raw deflate */
    if (deflateInit2(zstrm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
                     16+MAX_WBITS /* gzip */,
                     MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
        free(zstrm);
        return NULL;
    }
    else {
        accept_encodings |= CE_DEFLATE | CE_GZIP;
        return zstrm;
    }
}


EXPORTED unsigned get_preferences(struct transaction_t *txn)
{
    unsigned mask = 0, prefs = 0;
    const char **hdr;

    /* Create a mask for preferences honored by method */
    switch (txn->meth) {
    case METH_COPY:
    case METH_MOVE:
    case METH_PATCH:
    case METH_POST:
    case METH_PUT:
        mask = PREFER_REP;
        break;

    case METH_GET:
    case METH_MKCALENDAR:
    case METH_MKCOL:
    case METH_PROPPATCH:
        mask = PREFER_MIN;
        break;

    case METH_PROPFIND:
    case METH_REPORT:
        mask = (PREFER_MIN | PREFER_NOROOT);
        break;
    }

    if (!mask) return 0;
    else {
        txn->flags.vary |= VARY_PREFER;
        if (mask & PREFER_MIN) txn->flags.vary |= VARY_BRIEF;
    }

    /* Check for Prefer header(s) */
    if ((hdr = spool_getheader(txn->req_hdrs, "Prefer"))) {
        int i;
        for (i = 0; hdr[i]; i++) {
            tok_t tok;
            char *token;

            tok_init(&tok, hdr[i], ",\r\n", TOK_TRIMLEFT|TOK_TRIMRIGHT);
            while ((token = tok_next(&tok))) {
                if ((mask & PREFER_MIN) &&
                    !strcmp(token, "return=minimal"))
                    prefs |= PREFER_MIN;
                else if ((mask & PREFER_REP) &&
                         !strcmp(token, "return=representation"))
                    prefs |= PREFER_REP;
                else if ((mask & PREFER_NOROOT) &&
                         !strcmp(token, "depth-noroot"))
                    prefs |= PREFER_NOROOT;
            }
            tok_fini(&tok);
        }

        txn->resp_body.prefs = prefs;
    }

    /* Check for Brief header */
    if ((mask & PREFER_MIN) &&
        (hdr = spool_getheader(txn->req_hdrs, "Brief")) &&
        !strcasecmp(hdr[0], "t")) {
        prefs |= PREFER_MIN;
    }

    /* Check for X-MobileMe-DAV-Options header */
    if ((mask & PREFER_REP) &&
        (hdr = spool_getheader(txn->req_hdrs, "X-MobileMe-DAV-Options")) &&
        !strcasecmp(hdr[0], "return-changed-data")) {
        prefs |= PREFER_REP;
    }

    return prefs;
}


int report_sync_col(struct transaction_t *txn, struct meth_params *rparams,
                    xmlNodePtr inroot, struct propfind_ctx *fctx)
{
    int ret = 0, r;
    struct mailbox *mailbox = NULL;
    uint32_t uidvalidity = 0;
    modseq_t syncmodseq = 0;
    modseq_t basemodseq = 0;
    modseq_t highestmodseq = 0;
    modseq_t respmodseq = 0;
    uint32_t limit = UINT32_MAX - 1;
    uint32_t nresp = 0;
    xmlNodePtr node;
    char tokenuri[MAX_MAILBOX_PATH+1];

    /* XXX  Handle Depth (cal-home-set at toplevel) */

    /* Open mailbox for reading */
    r = mailbox_open_irl(txn->req_tgt.mbentry->name, &mailbox);
    if (r) {
        syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
               txn->req_tgt.mbentry->name, error_message(r));
        txn->error.desc = error_message(r);
        ret = HTTP_SERVER_ERROR;
        goto done;
    }

    fctx->mbentry = txn->req_tgt.mbentry;
    fctx->mailbox = mailbox;

    highestmodseq = mailbox->i.highestmodseq;

    /* Parse children element of report */
    for (node = inroot->children; node; node = node->next) {
        xmlNodePtr node2;
        xmlChar *str = NULL;
        if (node->type == XML_ELEMENT_NODE) {
            if (!xmlStrcmp(node->name, BAD_CAST "sync-token") &&
                (str = xmlNodeListGetString(inroot->doc, node->children, 1))) {
                /* Add sync-token to our header cache */
                spool_cache_header(xstrdup(":token"),
                                   xstrdup((const char *) str), txn->req_hdrs);

                /* Parse sync-token */
                r = sscanf((char *) str, SYNC_TOKEN_URL_SCHEME
                           "%u-" MODSEQ_FMT "-" MODSEQ_FMT "%1s",
                           &uidvalidity, &syncmodseq, &basemodseq,
                           tokenuri /* test for trailing junk */);

                syslog(LOG_DEBUG, "scanned token %s to %d %u %llu %llu",
                       str, r, uidvalidity, syncmodseq, basemodseq);
                /* Sanity check the token components */
                if (r < 2 || r > 3 ||
                    (uidvalidity != mailbox->i.uidvalidity) ||
                    (syncmodseq > highestmodseq)) {
                    fctx->txn->error.desc = "Invalid sync-token";
                }
                else if (r == 3) {
                    /* Previous partial read token */
                    if (basemodseq > highestmodseq) {
                        fctx->txn->error.desc = "Invalid sync-token";
                    }
                    else if (basemodseq < mailbox->i.deletedmodseq) {
                        fctx->txn->error.desc = "Stale sync-token";
                    }
                }
                else {
                    /* Regular token */
                    if (syncmodseq < mailbox->i.deletedmodseq) {
                        fctx->txn->error.desc = "Stale sync-token";
                    }
                }

                if (fctx->txn->error.desc) {
                    /* DAV:valid-sync-token */
                    txn->error.precond = DAV_SYNC_TOKEN;
                    ret = HTTP_FORBIDDEN;
                }
            }
            else if (!xmlStrcmp(node->name, BAD_CAST "sync-level") &&
                (str = xmlNodeListGetString(inroot->doc, node->children, 1))) {
                if (!strcmp((char *) str, "infinity")) {
                    fctx->txn->error.desc =
                        "This server DOES NOT support infinite depth requests";
                    ret = HTTP_SERVER_ERROR;
                }
                else if ((sscanf((char *) str, "%u", &fctx->depth) != 1) ||
                         (fctx->depth != 1)) {
                    fctx->txn->error.desc = "Illegal sync-level";
                    ret = HTTP_BAD_REQUEST;
                }
            }
            else if (!xmlStrcmp(node->name, BAD_CAST "limit")) {
                errno = 0;

                for (node2 = node->children; node2; node2 = node2->next) {
                    if ((node2->type == XML_ELEMENT_NODE) &&
                        !xmlStrcmp(node2->name, BAD_CAST "nresults") &&
                        (!(str = xmlNodeListGetString(inroot->doc,
                                                      node2->children, 1)) ||
                         (sscanf((char *) str, "%u", &limit) != 1) ||
                         (errno != 0) || (limit >= UINT32_MAX))) {
                        txn->error.precond = DAV_OVER_LIMIT;
                        ret = HTTP_FORBIDDEN;
                    }
                }
            }

            if (str) xmlFree(str);
            if (ret) goto done;
        }
    }

    /* Check Depth */
    if (!fctx->depth) {
        fctx->txn->error.desc = "Illegal sync-level";
        ret = HTTP_BAD_REQUEST;
        goto done;
    }

    if (!syncmodseq) {
        /* Initial sync - set basemodseq in case client limits results */
        basemodseq = highestmodseq;
    }

    /* Open the DAV DB corresponding to the mailbox */
    fctx->davdb = rparams->davdb.open_db(fctx->mailbox);

    /* Setup for chunked response */
    txn->flags.te |= TE_CHUNKED;

    /* Begin XML response */
    xml_response(HTTP_MULTI_STATUS, txn, fctx->root->doc);

    /* Report the resources within the client requested limit (if any) */
    struct updates_rock rock = { fctx, rparams->get_modseq, limit,
                                 syncmodseq, basemodseq, &respmodseq, &nresp };

    r = rparams->davdb.foreach_update(fctx->davdb, syncmodseq, mailbox->name,
                                      -1 /* ALL kinds of resources */,
                                      (syncmodseq && basemodseq) ? 0 : limit + 1,
                                      &updates_cb, &rock);
    if (r) {
        /* Tell client we truncated the responses */
        if (fctx->req_tgt->resource) *(fctx->req_tgt->resource) = '\0';
        xml_add_response(fctx, HTTP_NO_STORAGE, DAV_OVER_LIMIT, NULL, NULL);
    }
    else {
        /* Full response - respmodseq will be highestmodseq of mailbox */
        respmodseq = highestmodseq;
    }

    if (fctx->davdb) rparams->davdb.close_db(fctx->davdb);

    /* Add sync-token element to root */
    if (respmodseq < basemodseq) {
        /* Client limited results of initial sync - include basemodseq */
        snprintf(tokenuri, MAX_MAILBOX_PATH,
                 SYNC_TOKEN_URL_SCHEME "%u-" MODSEQ_FMT "-" MODSEQ_FMT,
                 mailbox->i.uidvalidity, respmodseq, basemodseq);
    }
    else {
        snprintf(tokenuri, MAX_MAILBOX_PATH,
                 SYNC_TOKEN_URL_SCHEME "%u-" MODSEQ_FMT,
                 mailbox->i.uidvalidity, respmodseq);
    }
    node =
        xmlNewChild(fctx->root, NULL, BAD_CAST "sync-token", BAD_CAST tokenuri);

    /* Add sync-token element to output buffer */
    xml_partial_response(NULL /* !output */,
                         fctx->root->doc, node, 1, &fctx->xmlbuf);

    /* End XML response */
    xml_partial_response(txn, fctx->root->doc, NULL /* end */, 0, &fctx->xmlbuf);
    xmlBufferFree(fctx->xmlbuf);

    /* End of output */
    write_body(0, txn, NULL, 0);

  done:
    mailbox_close(&mailbox);

    return ret;
}


static int allprop_cb(const char *mailbox __attribute__((unused)),
                      uint32_t uid __attribute__((unused)),
                      const char *entry,
                      const char *userid, const struct buf *attrib,
                      const struct annotate_metadata *mdata __attribute__((unused)),
                      void *rock)
{
    struct allprop_rock *arock = (struct allprop_rock *) rock;
    const struct prop_entry *pentry;
    char *href, *name;
    xmlNsPtr ns;
    xmlNodePtr node;

    /* Make sure its a shared entry or the user's private one */
    if (userid && *userid && strcmp(userid, arock->fctx->userid)) return 0;

    /* Split entry into namespace href and name ( <href>name ) */
    buf_setcstr(&arock->fctx->buf, entry + strlen(DAV_ANNOT_NS) + 1);
    href = (char *) buf_cstring(&arock->fctx->buf);
    if ((name = strchr(href, '>'))) *name++ = '\0';
    else if ((name = strchr(href, ':'))) *name++ = '\0';

    /* Look for a match against live properties */
    for (pentry = arock->fctx->lprops;
         pentry->name &&
             (strcmp(name, pentry->name) ||
              strcmp(href, known_namespaces[pentry->ns].href));
         pentry++);

    if (pentry->name &&
        (arock->fctx->mode == PROPFIND_ALL    /* Skip all live properties */
         || (pentry->flags & PROP_ALLPROP)))  /* Skip those already included */
        return 0;

    /* Look for an instance of this namespace in our response */
    ns = hash_lookup(href, arock->fctx->ns_table);

    /* XXX - can return the same property multiple times with annotate masks! */

    /* Add the dead property to the response */
    node = xml_add_prop(HTTP_OK, arock->fctx->ns[NS_DAV],
                        &arock->propstat[PROPSTAT_OK],
                        BAD_CAST name, ns, NULL, 0);
    if (!ns) {
        /* Add the namespace directly to the property -
           its too late to add it to the root when chunking the responses */
        char prefix[9];
        snprintf(prefix, sizeof(prefix), "X%X", strhash(href));
        xmlSetNs(node, xmlNewNs(node, BAD_CAST href, BAD_CAST prefix));
    }

    if (arock->fctx->mode == PROPFIND_ALL) {
        xmlAddChild(node, xmlNewCDataBlock(arock->fctx->root->doc,
                                           BAD_CAST attrib->s, attrib->len));
    }

    return 0;
}


int propfind_owner(const xmlChar *name, xmlNsPtr ns,
                   struct propfind_ctx *fctx,
                   xmlNodePtr prop,
                   xmlNodePtr resp __attribute__((unused)),
                   struct propstat propstat[],
                   void *rock __attribute__((unused)))
{
    mbname_t *mbname;
    const char *owner;
    int r;

    if (!fctx->mbentry) return HTTP_NOT_FOUND;

    mbname = mbname_from_intname(fctx->mbentry->name);
    owner = mbname_userid(mbname);
    if (!owner) {
        static strarray_t *admins = NULL;

        if (!admins) admins = strarray_split(config_getstring(IMAPOPT_ADMINS),
                                             NULL, STRARRAY_TRIM);

        owner = strarray_nth(admins, 0);
    }

    r = propfind_principalurl(name, ns, fctx,
                              prop, resp, propstat, (void *) owner);

    mbname_free(&mbname);

    return r;
}


int propfind_reportset(const xmlChar *name, xmlNsPtr ns,
                       struct propfind_ctx *fctx,
                       xmlNodePtr prop __attribute__((unused)),
                       xmlNodePtr resp __attribute__((unused)),
                       struct propstat propstat[],
                       void *rock)
{
    xmlNodePtr top, node;
    const struct report_type_t *report;

    if (!propstat) {
        /* Prescreen "property" request */
        for (report = (const struct report_type_t *) rock;
             report && report->name; report++) {
            /* Add namespaces for possible reports */
            ensure_ns(fctx->ns, report->ns, fctx->root,
                      known_namespaces[report->ns].href,
                      known_namespaces[report->ns].prefix);
        }

        return 0;
    }

    top = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
                       name, ns, NULL, 0);

    for (report = (const struct report_type_t *) rock;
         report && report->name; report++) {
        node = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL);
        node = xmlNewChild(node, NULL, BAD_CAST "report", NULL);

        ensure_ns(fctx->ns, report->ns, resp->parent,
                  known_namespaces[report->ns].href,
                  known_namespaces[report->ns].prefix);
        xmlNewChild(node, fctx->ns[report->ns], BAD_CAST report->name, NULL);
    }

    return 0;
}


void dav_free_propfilter(struct prop_filter *prop)
{
    struct param_filter *param, *next;

    xmlFree(prop->name);
    if (prop->match) {
        xmlFree(prop->match->text);
        free(prop->match);
    }
    for (param = prop->param; param; param = next) {
        next = param->next;

        xmlFree(param->name);
        if (param->match) {
            xmlFree(param->match->text);
            free(param->match);
        }
        free(param);
    }
    free(prop);
}


static void add_privs(int rights, unsigned flags,
                      xmlNodePtr parent, xmlNodePtr root, xmlNsPtr *ns)
{
    xmlNodePtr priv;
    int do_contained;

    /* DAV:all */
    if ((rights & DACL_ALL) == DACL_ALL &&
        /* DAV:all on CALDAV:schedule-in/outbox MUST include CALDAV:schedule */
        (!(flags & (PRIV_INBOX|PRIV_OUTBOX)) ||
         (rights & DACL_SCHED) == DACL_SCHED)) {
        priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
        xmlNewChild(priv, NULL, BAD_CAST "all", NULL);

        if (!(flags & PRIV_CONTAINED)) return;
    }

    /* DAV:read */
    if ((rights & DACL_READ) == DACL_READ) {
        priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
        xmlNewChild(priv, NULL, BAD_CAST "read", NULL);
        if (flags & PRIV_IMPLICIT) rights |= DACL_READFB;

        do_contained = (flags & PRIV_CONTAINED);
    }
    else do_contained = 1;

    if (do_contained) {
        if ((rights & DACL_READFB) &&
            /* CALDAV:read-free-busy doesn't apply to CALDAV:sched-in/outbox */
            !(flags & (PRIV_INBOX|PRIV_OUTBOX))) {
            priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
            ensure_ns(ns, NS_CALDAV, root, XML_NS_CALDAV, "C");
            xmlNewChild(priv, ns[NS_CALDAV], BAD_CAST  "read-free-busy", NULL);
        }
    }

    /* DAV:write */
    if ((rights & DACL_WRITE) == DACL_WRITE) {
        priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
        xmlNewChild(priv, NULL, BAD_CAST "write", NULL);

        do_contained = (flags & PRIV_CONTAINED);
    }
    else do_contained = 1;

    if (do_contained) {
        if (rights & DACL_WRITECONT) {
            priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
            xmlNewChild(priv, NULL, BAD_CAST "write-content", NULL);
        }

        if (rights & (DACL_WRITEPROPS|DACL_BIND|DACL_UNBIND)) {
            ensure_ns(ns, NS_CYRUS, root, XML_NS_CYRUS, "CY");

            /* DAV:write-properties */
            if ((rights & DACL_WRITEPROPS) == DACL_WRITEPROPS) {
                priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
                xmlNewChild(priv, NULL, BAD_CAST "write-properties", NULL);

                do_contained = (flags & PRIV_CONTAINED);
            }
            else do_contained = 1;

            if (do_contained) {
                if (rights & DACL_PROPCOL) {
                    priv = xmlNewChild(parent, NULL,
                                       BAD_CAST "privilege", NULL);
                    xmlNewChild(priv, ns[NS_CYRUS],
                                BAD_CAST "write-properties-collection", NULL);
                }
                if (rights & DACL_PROPRSRC) {
                    priv = xmlNewChild(parent, NULL,
                                       BAD_CAST "privilege", NULL);
                    xmlNewChild(priv, ns[NS_CYRUS],
                                BAD_CAST "write-properties-resource", NULL);
                }
            }

            /* DAV:bind */
            if ((rights & DACL_BIND) == DACL_BIND) {
                priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
                xmlNewChild(priv, NULL, BAD_CAST "bind", NULL);

                do_contained = (flags & PRIV_CONTAINED);
            }
            else do_contained = 1;

            if (do_contained) {
                if (rights & DACL_MKCOL) {
                    priv = xmlNewChild(parent, NULL,
                                       BAD_CAST "privilege", NULL);
                    xmlNewChild(priv, ns[NS_CYRUS],
                                BAD_CAST "make-collection", NULL);
                }
                if (rights & DACL_ADDRSRC) {
                    priv = xmlNewChild(parent, NULL,
                                       BAD_CAST "privilege", NULL);
                    xmlNewChild(priv, ns[NS_CYRUS],
                                BAD_CAST "add-resource", NULL);
                }
            }

            /* DAV:unbind */
            if ((rights & DACL_UNBIND) == DACL_UNBIND) {
                priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
                xmlNewChild(priv, NULL, BAD_CAST "unbind", NULL);

                do_contained = (flags & PRIV_CONTAINED);
            }
            else do_contained = 1;

            if (do_contained) {
                if (rights & DACL_RMCOL) {
                    priv = xmlNewChild(parent, NULL,
                                       BAD_CAST "privilege", NULL);
                    xmlNewChild(priv, ns[NS_CYRUS],
                                BAD_CAST "remove-collection", NULL);
                }
                if ((rights & DACL_RMRSRC) == DACL_RMRSRC) {
                    priv = xmlNewChild(parent, NULL,
                                       BAD_CAST "privilege", NULL);
                    xmlNewChild(priv, ns[NS_CYRUS],
                                BAD_CAST "remove-resource", NULL);
                }
            }
        }
    }

    /* CYRUS:admin */
    if (rights & DACL_ADMIN) {
        ensure_ns(ns, NS_CYRUS, root, XML_NS_CYRUS, "CY");
        priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
        xmlNewChild(priv, ns[NS_CYRUS], BAD_CAST  "admin", NULL);
    }

    if ((rights & DACL_SCHED) && (flags & (PRIV_INBOX|PRIV_OUTBOX))) {
        struct buf buf = BUF_INITIALIZER;
        size_t len;

        if (flags & PRIV_INBOX) buf_setcstr(&buf, "schedule-deliver");
        else buf_setcstr(&buf, "schedule-send");
        len = buf_len(&buf);

        ensure_ns(ns, NS_CALDAV, root, XML_NS_CALDAV, "C");

        /* CALDAV:schedule */
        if ((rights & DACL_SCHED) == DACL_SCHED) {
            priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
            xmlNewChild(priv, ns[NS_CALDAV], BAD_CAST buf_cstring(&buf), NULL);

            do_contained = (flags & PRIV_CONTAINED);
        }
        else do_contained = 1;

        if (do_contained) {
            if (rights & DACL_INVITE) {
                buf_truncate(&buf, len);
                buf_appendcstr(&buf, "-invite");
                priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
                xmlNewChild(priv, ns[NS_CALDAV],
                            BAD_CAST buf_cstring(&buf), NULL);
            }
            if (rights & DACL_REPLY) {
                buf_truncate(&buf, len);
                buf_appendcstr(&buf, "-reply");
                priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
                xmlNewChild(priv, ns[NS_CALDAV],
                            BAD_CAST buf_cstring(&buf), NULL);
            }
            if (rights & DACL_SCHEDFB) {
                if (flags & PRIV_INBOX) buf_setcstr(&buf, "schedule-query");
                else buf_truncate(&buf, len);
                buf_appendcstr(&buf, "-freebusy");
                priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL);
                xmlNewChild(priv, ns[NS_CALDAV],
                            BAD_CAST buf_cstring(&buf), NULL);
            }
        }
        buf_free(&buf);
    }
}


static int principal_search(const char *userid, void *rock)
{
    struct propfind_ctx *fctx = (struct propfind_ctx *) rock;
    struct search_crit *search_crit;
    size_t len;
    char *p;

    /* XXX - this function needs extradomain and virtdomains support */

    /* Check ACL for current user */
    if (principal_acl_check(userid, httpd_authstate)) return 0;

    /* Check against search criteria */
    for (search_crit = (struct search_crit *) fctx->filter_crit;
         search_crit; search_crit = search_crit->next) {
        struct strlist *prop;

        for (prop = search_crit->props; prop; prop = prop->next) {
            if (!strcmp(prop->s, "displayname")) {
                if (!xmlStrcasestr(BAD_CAST userid,
                                   search_crit->match)) return 0;
            }
            else if (!strcmp(prop->s, "calendar-user-address-set")) {
                char email[MAX_MAILBOX_NAME+1];

                snprintf(email, MAX_MAILBOX_NAME, "%s@%s",
                         userid, config_servername);
                if (!xmlStrcasestr(BAD_CAST email,
                                   search_crit->match)) return 0;
            }
            else if (!strcmp(prop->s, "calendar-user-type")) {
                if (!xmlStrcasestr(BAD_CAST "INDIVIDUAL",
                                   search_crit->match)) return 0;
            }
        }
    }

    /* Append principal name to URL path */
    len = strlen(namespace_principal.prefix);
    p = fctx->req_tgt->path + len;
    snprintf(p, MAX_MAILBOX_PATH - len, "/%s/%s/",
             USER_COLLECTION_PREFIX, userid);

    free(fctx->req_tgt->userid);
    fctx->req_tgt->userid = xstrdup(userid);

    return xml_add_response(fctx, 0, 0, NULL, NULL);
}


static int xml_add_ns(xmlNodePtr req, xmlNsPtr *respNs, xmlNodePtr root)
{
    for (; req; req = req->next) {
        if (req->type == XML_ELEMENT_NODE) {
            xmlNsPtr nsDef;

            for (nsDef = req->nsDef; nsDef; nsDef = nsDef->next) {
                if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_DAV))
                    ensure_ns(respNs, NS_DAV, root,
                              (const char *) nsDef->href,
                              (const char *) nsDef->prefix);
                else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_CALDAV))
                    ensure_ns(respNs, NS_CALDAV, root,
                              (const char *) nsDef->href,
                              (const char *) nsDef->prefix);
                else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_CARDDAV))
                    ensure_ns(respNs, NS_CARDDAV, root,
                              (const char *) nsDef->href,
                              (const char *) nsDef->prefix);
                else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_CS))
                    ensure_ns(respNs, NS_CS, root,
                              (const char *) nsDef->href,
                              (const char *) nsDef->prefix);
                else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_MECOM))
                    ensure_ns(respNs, NS_MECOM, root,
                              (const char *) nsDef->href,
                              (const char *) nsDef->prefix);
                else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_MOBME))
                    ensure_ns(respNs, NS_MOBME, root,
                              (const char *) nsDef->href,
                              (const char *) nsDef->prefix);
                else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_CYRUS))
                    ensure_ns(respNs, NS_CYRUS, root,
                              (const char *) nsDef->href,
                              (const char *) nsDef->prefix);
                else
                    xmlNewNs(root, nsDef->href, nsDef->prefix);
            }
        }

        xml_add_ns(req->children, respNs, root);
    }

    /* XXX  check for errors */
    return 0;
}


int propfind_getdata(const xmlChar *name, xmlNsPtr ns,
                     struct propfind_ctx *fctx,
                     xmlNodePtr prop, struct propstat propstat[],
                     struct mime_type_t *mime_types,
                     struct mime_type_t **out_type,
                     const char *data, unsigned long datalen)
{
    int ret = 0;

    if (!propstat) {
        /* Prescreen "property" request */
        xmlChar *type, *ver = NULL;
        struct mime_type_t *mime;

        type = xmlGetProp(prop, BAD_CAST "content-type");
        if (type) ver = xmlGetProp(prop, BAD_CAST "version");

        /* Check/find requested MIME type */
        for (mime = mime_types; type && mime->content_type; mime++) {
            if (is_mediatype((const char *) type, mime->content_type)) {
                if (ver &&
                    (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) {
                    continue;
                }
                break;
            }
        }

        if (type) xmlFree(type);
        if (ver) xmlFree(ver);

        *out_type = mime;
    }
    else {
        /* Add "property" */
        struct mime_type_t *mime = *out_type;
        char *freeme = NULL;

        prop = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
                            &propstat[PROPSTAT_OK], name, ns, NULL, 0);

        if (mime != mime_types) {
            /* Not the storage format - convert into requested MIME type */
            struct buf inbuf, *outbuf;

            if (!fctx->obj) {
                buf_init_ro(&inbuf, data, datalen);
                fctx->obj = mime_types->to_object(&inbuf);
                buf_free(&inbuf);
            }

            outbuf = mime->from_object(fctx->obj);
            datalen = buf_len(outbuf);
            data = freeme = buf_release(outbuf);
            buf_destroy(outbuf);

            xmlSetProp(prop,
                       BAD_CAST "content-type", BAD_CAST mime->content_type);
            if (mime->version)
                xmlSetProp(prop, BAD_CAST "version", BAD_CAST mime->version);
        }

        xmlAddChild(prop,
                    xmlNewCDataBlock(fctx->root->doc, BAD_CAST data, datalen));

        fctx->flags.fetcheddata = 1;

        if (freeme) free(freeme);
    }

    return ret;
}


int meth_get_head(struct transaction_t *txn, void *params)
{
    struct meth_params *gparams = (struct meth_params *) params;
    const char **hdr;
    struct mime_type_t *mime = NULL;
    int ret = 0, r = 0, precond, rights;
    const char *data = NULL;
    unsigned long datalen = 0, offset = 0;
    struct buf msg_buf = BUF_INITIALIZER;
    struct resp_body_t *resp_body = &txn->resp_body;
    struct mailbox *mailbox = NULL;
    struct dav_data *ddata;
    struct index_record record;
    const char *etag = NULL;
    time_t lastmod = 0;
    void *davdb = NULL, *obj = NULL;
    char *freeme = NULL;

    /* Parse the path */
    r = dav_parse_req_target(txn, gparams);
    if (r) return r;

    if (txn->req_tgt.namespace->id == URL_NS_PRINCIPAL) {
        /* Special "principal" */
        if (txn->req_tgt.flags == TGT_SERVER_INFO) return get_server_info(txn);

        /* No content for principals (yet) */
        return HTTP_NO_CONTENT;
    }

    if (!txn->req_tgt.resource) {
        /* Do any collection processing */
        if (gparams->get) return gparams->get(txn, NULL, NULL, NULL, NULL);

        /* We don't handle GET on a collection */
        return HTTP_NO_CONTENT;
    }

    /* Check ACL for current user */
    rights = httpd_myrights(httpd_authstate, txn->req_tgt.mbentry);
    if ((rights & DACL_READ) != DACL_READ) {
        /* DAV:need-privileges */
        txn->error.precond = DAV_NEED_PRIVS;
        txn->error.resource = txn->req_tgt.path;
        txn->error.rights = DACL_READ;
        return HTTP_NO_PRIVS;
    }

    if (gparams->mime_types) {
        /* Check requested MIME type:
           1st entry in gparams->mime_types array MUST be default MIME type */
        if ((hdr = spool_getheader(txn->req_hdrs, "Accept")))
            mime = get_accept_type(hdr, gparams->mime_types);
        else mime = gparams->mime_types;
        if (!mime) return HTTP_NOT_ACCEPTABLE;
    }

    if (txn->req_tgt.mbentry->server) {
        /* Remote mailbox */
        struct backend *be;

        be = proxy_findserver(txn->req_tgt.mbentry->server,
                              &http_protocol, httpd_userid,
                              &backend_cached, NULL, NULL, httpd_in);
        if (!be) return HTTP_UNAVAILABLE;

        return http_pipe_req_resp(be, txn);
    }

    /* Local Mailbox */

    /* Open mailbox for reading */
    r = mailbox_open_irl(txn->req_tgt.mbentry->name, &mailbox);
    if (r) {
        syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
               txn->req_tgt.mbentry->name, error_message(r));
        goto done;
    }

    /* Open the DAV DB corresponding to the mailbox */
    davdb = gparams->davdb.open_db(mailbox);

    /* Find message UID for the resource */
    gparams->davdb.lookup_resource(davdb, txn->req_tgt.mbentry->name,
                                   txn->req_tgt.resource, (void **) &ddata, 0);
    if (!ddata->rowid) {
        ret = HTTP_NOT_FOUND;
        goto done;
    }

    /* Fetch resource validators */
    r = gparams->get_validators(mailbox, (void *) ddata, httpd_userid,
                                &record, &etag, &lastmod);
    if (r) {
        txn->error.desc = error_message(r);
        ret = HTTP_SERVER_ERROR;
        goto done;
    }

    txn->flags.ranges = (ddata->imap_uid != 0);

    /* Check any preconditions, including range request */
    precond = gparams->check_precond(txn, params, mailbox,
                                     (void *) ddata, etag, lastmod);

    switch (precond) {
    case HTTP_OK:
    case HTTP_PARTIAL:
    case HTTP_NOT_MODIFIED:
        /* Fill in ETag, Last-Modified, Expires, and Cache-Control */
        resp_body->etag = etag;
        resp_body->lastmod = lastmod;
        resp_body->maxage = 3600;       /* 1 hr */
        txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE;  /* don't use stale data */
        if (httpd_userid) txn->flags.cc |= CC_PRIVATE;

        if (precond != HTTP_NOT_MODIFIED && record.uid) break;

        GCC_FALLTHROUGH

    default:
        /* We failed a precondition - don't perform the request */
        ret = precond;
        goto done;
    }

    /* Do any special processing */
    if (gparams->get) {
        ret = gparams->get(txn, mailbox, &record, ddata, &obj);
        if (ret != HTTP_CONTINUE) goto done;

        ret = 0;
    }

    if (mime && !resp_body->type) {
        txn->flags.vary |= VARY_ACCEPT;
        resp_body->type = mime->content_type;
    }

    if (!obj) {
        /* Raw resource - length doesn't include RFC 5322 header */
        offset = record.header_size;
        datalen = record.size - offset;

        if (txn->meth == METH_GET) {
            /* Load message containing the resource */
            r = mailbox_map_record(mailbox, &record, &msg_buf);
            if (r) goto done;

            data = buf_base(&msg_buf) + offset;

            if (mime != gparams->mime_types) {
                /* Not the storage format - create resource object */
                struct buf inbuf;

                buf_init_ro(&inbuf, data, datalen);
                obj = gparams->mime_types[0].to_object(&inbuf);
                buf_free(&inbuf);
            }
        }
    }

    if (obj) {
        /* Convert object into requested MIME type */
        struct buf *outbuf = mime->from_object(obj);

        datalen = buf_len(outbuf);
        if (txn->meth == METH_GET) data = freeme = buf_release(outbuf);
        buf_destroy(outbuf);

        if (gparams->mime_types[0].free) gparams->mime_types[0].free(obj);
    }

    write_body(precond, txn, data, datalen);

    buf_free(&msg_buf);
    free(freeme);

  done:
    if (davdb) gparams->davdb.close_db(davdb);
    if (r) {
        txn->error.desc = error_message(r);
        ret = HTTP_SERVER_ERROR;
    }
    mailbox_close(&mailbox);

    return ret;
}


static int principal_acl_check(const char *userid, struct auth_state *authstate)
{
    int r = 0;

    if (!httpd_userisadmin) {
        char *mboxname = caldav_mboxname(userid, NULL);
        mbentry_t *mbentry = NULL;

        r = http_mlookup(mboxname, &mbentry, NULL);
        if (r) {
            syslog(LOG_ERR, "mlookup(%s) failed: %s",
                   mboxname, error_message(r));
            r = HTTP_NOT_FOUND;
        }
        else if (!(httpd_myrights(authstate, mbentry) & ACL_LOOKUP)) {
            // allow READ (for owner) or USER6 (to grant access generally without anything else)
            r = HTTP_NOT_FOUND;
        }

        mboxlist_entry_free(&mbentry);
        free(mboxname);
    }

    return r;
}