pci_clp.c 14.4 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0
J
Jan Glauber 已提交
2 3 4 5 6 7 8
/*
 * Copyright IBM Corp. 2012
 *
 * Author(s):
 *   Jan Glauber <jang@linux.vnet.ibm.com>
 */

G
Gerald Schaefer 已提交
9 10
#define KMSG_COMPONENT "zpci"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
J
Jan Glauber 已提交
11

12
#include <linux/compat.h>
J
Jan Glauber 已提交
13
#include <linux/kernel.h>
14
#include <linux/miscdevice.h>
J
Jan Glauber 已提交
15 16 17 18
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/pci.h>
19
#include <linux/uaccess.h>
S
Sebastian Ott 已提交
20
#include <asm/pci_debug.h>
J
Jan Glauber 已提交
21
#include <asm/pci_clp.h>
22 23
#include <asm/clp.h>
#include <uapi/asm/clp.h>
J
Jan Glauber 已提交
24

25 26
bool zpci_unique_uid;

27 28 29 30 31 32 33 34
static void update_uid_checking(bool new)
{
	if (zpci_unique_uid != new)
		zpci_dbg(1, "uid checking:%d\n", new);

	zpci_unique_uid = new;
}

S
Sebastian Ott 已提交
35 36 37 38 39 40 41 42 43 44
static inline void zpci_err_clp(unsigned int rsp, int rc)
{
	struct {
		unsigned int rsp;
		int rc;
	} __packed data = {rsp, rc};

	zpci_err_hex(&data, sizeof(data));
}

J
Jan Glauber 已提交
45
/*
46 47
 * Call Logical Processor with c=1, lps=0 and command 1
 * to get the bit mask of installed logical processors
J
Jan Glauber 已提交
48
 */
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
static inline int clp_get_ilp(unsigned long *ilp)
{
	unsigned long mask;
	int cc = 3;

	asm volatile (
		"	.insn	rrf,0xb9a00000,%[mask],%[cmd],8,0\n"
		"0:	ipm	%[cc]\n"
		"	srl	%[cc],28\n"
		"1:\n"
		EX_TABLE(0b, 1b)
		: [cc] "+d" (cc), [mask] "=d" (mask) : [cmd] "a" (1)
		: "cc");
	*ilp = mask;
	return cc;
}

/*
 * Call Logical Processor with c=0, the give constant lps and an lpcb request.
 */
static inline int clp_req(void *data, unsigned int lps)
J
Jan Glauber 已提交
70
{
71 72
	struct { u8 _[CLP_BLK_SIZE]; } *req = data;
	u64 ignored;
73
	int cc = 3;
J
Jan Glauber 已提交
74 75

	asm volatile (
76 77
		"	.insn	rrf,0xb9a00000,%[ign],%[req],0,%[lps]\n"
		"0:	ipm	%[cc]\n"
J
Jan Glauber 已提交
78
		"	srl	%[cc],28\n"
79 80 81 82
		"1:\n"
		EX_TABLE(0b, 1b)
		: [cc] "+d" (cc), [ign] "=d" (ignored), "+m" (*req)
		: [req] "a" (req), [lps] "i" (lps)
83
		: "cc");
J
Jan Glauber 已提交
84 85 86
	return cc;
}

S
Sebastian Ott 已提交
87
static void *clp_alloc_block(gfp_t gfp_mask)
J
Jan Glauber 已提交
88
{
S
Sebastian Ott 已提交
89
	return (void *) __get_free_pages(gfp_mask, get_order(CLP_BLK_SIZE));
J
Jan Glauber 已提交
90 91 92 93 94 95 96 97 98 99
}

static void clp_free_block(void *ptr)
{
	free_pages((unsigned long) ptr, get_order(CLP_BLK_SIZE));
}

static void clp_store_query_pci_fngrp(struct zpci_dev *zdev,
				      struct clp_rsp_query_pci_grp *response)
{
J
Jan Glauber 已提交
100 101
	zdev->tlb_refresh = response->refresh;
	zdev->dma_mask = response->dasm;
102
	zdev->msi_addr = response->msia;
103
	zdev->max_msi = response->noi;
104
	zdev->fmb_update = response->mui;
105

J
Jan Glauber 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
	switch (response->version) {
	case 1:
		zdev->max_bus_speed = PCIE_SPEED_5_0GT;
		break;
	default:
		zdev->max_bus_speed = PCI_SPEED_UNKNOWN;
		break;
	}
}

static int clp_query_pci_fngrp(struct zpci_dev *zdev, u8 pfgid)
{
	struct clp_req_rsp_query_pci_grp *rrb;
	int rc;

S
Sebastian Ott 已提交
121
	rrb = clp_alloc_block(GFP_KERNEL);
J
Jan Glauber 已提交
122 123 124 125 126 127 128 129 130
	if (!rrb)
		return -ENOMEM;

	memset(rrb, 0, sizeof(*rrb));
	rrb->request.hdr.len = sizeof(rrb->request);
	rrb->request.hdr.cmd = CLP_QUERY_PCI_FNGRP;
	rrb->response.hdr.len = sizeof(rrb->response);
	rrb->request.pfgid = pfgid;

131
	rc = clp_req(rrb, CLP_LPS_PCI);
J
Jan Glauber 已提交
132 133 134
	if (!rc && rrb->response.hdr.rsp == CLP_RC_OK)
		clp_store_query_pci_fngrp(zdev, &rrb->response);
	else {
S
Sebastian Ott 已提交
135 136
		zpci_err("Q PCI FGRP:\n");
		zpci_err_clp(rrb->response.hdr.rsp, rc);
J
Jan Glauber 已提交
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
		rc = -EIO;
	}
	clp_free_block(rrb);
	return rc;
}

static int clp_store_query_pci_fn(struct zpci_dev *zdev,
				  struct clp_rsp_query_pci *response)
{
	int i;

	for (i = 0; i < PCI_BAR_COUNT; i++) {
		zdev->bars[i].val = le32_to_cpu(response->bar[i]);
		zdev->bars[i].size = response->bar_size[i];
	}
J
Jan Glauber 已提交
152 153
	zdev->start_dma = response->sdma;
	zdev->end_dma = response->edma;
J
Jan Glauber 已提交
154 155
	zdev->pchid = response->pchid;
	zdev->pfgid = response->pfgid;
156 157 158
	zdev->pft = response->pft;
	zdev->vfn = response->vfn;
	zdev->uid = response->uid;
S
Sebastian Ott 已提交
159
	zdev->fmb_length = sizeof(u32) * response->fmb_len;
160 161 162 163 164 165

	memcpy(zdev->pfip, response->pfip, sizeof(zdev->pfip));
	if (response->util_str_avail) {
		memcpy(zdev->util_str, response->util_str,
		       sizeof(zdev->util_str));
	}
166 167 168 169
	zdev->mio_capable = response->mio_addr_avail;
	for (i = 0; i < PCI_BAR_COUNT; i++) {
		if (!(response->mio_valid & (1 << (PCI_BAR_COUNT - i - 1))))
			continue;
170

171 172 173
		zdev->bars[i].mio_wb = (void __iomem *) response->addr[i].wb;
		zdev->bars[i].mio_wt = (void __iomem *) response->addr[i].wt;
	}
J
Jan Glauber 已提交
174 175 176 177 178 179 180 181
	return 0;
}

static int clp_query_pci_fn(struct zpci_dev *zdev, u32 fh)
{
	struct clp_req_rsp_query_pci *rrb;
	int rc;

S
Sebastian Ott 已提交
182
	rrb = clp_alloc_block(GFP_KERNEL);
J
Jan Glauber 已提交
183 184 185 186 187 188 189 190 191
	if (!rrb)
		return -ENOMEM;

	memset(rrb, 0, sizeof(*rrb));
	rrb->request.hdr.len = sizeof(rrb->request);
	rrb->request.hdr.cmd = CLP_QUERY_PCI_FN;
	rrb->response.hdr.len = sizeof(rrb->response);
	rrb->request.fh = fh;

192
	rc = clp_req(rrb, CLP_LPS_PCI);
J
Jan Glauber 已提交
193 194 195 196
	if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) {
		rc = clp_store_query_pci_fn(zdev, &rrb->response);
		if (rc)
			goto out;
197
		rc = clp_query_pci_fngrp(zdev, rrb->response.pfgid);
J
Jan Glauber 已提交
198
	} else {
S
Sebastian Ott 已提交
199 200
		zpci_err("Q PCI FN:\n");
		zpci_err_clp(rrb->response.hdr.rsp, rc);
J
Jan Glauber 已提交
201 202 203 204 205 206 207 208 209 210
		rc = -EIO;
	}
out:
	clp_free_block(rrb);
	return rc;
}

int clp_add_pci_device(u32 fid, u32 fh, int configured)
{
	struct zpci_dev *zdev;
211
	int rc = -ENOMEM;
J
Jan Glauber 已提交
212

S
Sebastian Ott 已提交
213
	zpci_dbg(3, "add fid:%x, fh:%x, c:%d\n", fid, fh, configured);
214 215
	zdev = kzalloc(sizeof(*zdev), GFP_KERNEL);
	if (!zdev)
216
		goto error;
J
Jan Glauber 已提交
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236

	zdev->fh = fh;
	zdev->fid = fid;

	/* Query function properties and update zdev */
	rc = clp_query_pci_fn(zdev, fh);
	if (rc)
		goto error;

	if (configured)
		zdev->state = ZPCI_FN_STATE_CONFIGURED;
	else
		zdev->state = ZPCI_FN_STATE_STANDBY;

	rc = zpci_create_device(zdev);
	if (rc)
		goto error;
	return 0;

error:
237
	zpci_dbg(0, "add fid:%x, rc:%d\n", fid, rc);
238
	kfree(zdev);
J
Jan Glauber 已提交
239 240 241 242 243 244 245 246 247
	return rc;
}

/*
 * Enable/Disable a given PCI function defined by its function handle.
 */
static int clp_set_pci_fn(u32 *fh, u8 nr_dma_as, u8 command)
{
	struct clp_req_rsp_set_pci *rrb;
248
	int rc, retries = 100;
J
Jan Glauber 已提交
249

S
Sebastian Ott 已提交
250
	rrb = clp_alloc_block(GFP_KERNEL);
J
Jan Glauber 已提交
251 252 253 254 255 256 257 258 259 260 261 262
	if (!rrb)
		return -ENOMEM;

	do {
		memset(rrb, 0, sizeof(*rrb));
		rrb->request.hdr.len = sizeof(rrb->request);
		rrb->request.hdr.cmd = CLP_SET_PCI_FN;
		rrb->response.hdr.len = sizeof(rrb->response);
		rrb->request.fh = *fh;
		rrb->request.oc = command;
		rrb->request.ndas = nr_dma_as;

263
		rc = clp_req(rrb, CLP_LPS_PCI);
J
Jan Glauber 已提交
264 265 266 267
		if (rrb->response.hdr.rsp == CLP_RC_SETPCIFN_BUSY) {
			retries--;
			if (retries < 0)
				break;
268
			msleep(20);
J
Jan Glauber 已提交
269 270 271 272 273 274
		}
	} while (rrb->response.hdr.rsp == CLP_RC_SETPCIFN_BUSY);

	if (!rc && rrb->response.hdr.rsp == CLP_RC_OK)
		*fh = rrb->response.fh;
	else {
S
Sebastian Ott 已提交
275 276
		zpci_err("Set PCI FN:\n");
		zpci_err_clp(rrb->response.hdr.rsp, rc);
J
Jan Glauber 已提交
277 278 279 280 281 282 283 284 285 286 287 288
		rc = -EIO;
	}
	clp_free_block(rrb);
	return rc;
}

int clp_enable_fh(struct zpci_dev *zdev, u8 nr_dma_as)
{
	u32 fh = zdev->fh;
	int rc;

	rc = clp_set_pci_fn(&fh, nr_dma_as, CLP_SET_ENABLE_PCI_FN);
289 290 291
	zpci_dbg(3, "ena fid:%x, fh:%x, rc:%d\n", zdev->fid, fh, rc);
	if (rc)
		goto out;
S
Sebastian Ott 已提交
292

293 294 295 296 297 298 299 300
	zdev->fh = fh;
	if (zdev->mio_capable) {
		rc = clp_set_pci_fn(&fh, nr_dma_as, CLP_SET_ENABLE_MIO);
		zpci_dbg(3, "ena mio fid:%x, fh:%x, rc:%d\n", zdev->fid, fh, rc);
		if (rc)
			clp_disable_fh(zdev);
	}
out:
J
Jan Glauber 已提交
301 302 303 304 305 306 307 308 309 310 311 312
	return rc;
}

int clp_disable_fh(struct zpci_dev *zdev)
{
	u32 fh = zdev->fh;
	int rc;

	if (!zdev_enabled(zdev))
		return 0;

	rc = clp_set_pci_fn(&fh, 0, CLP_SET_DISABLE_PCI_FN);
313
	zpci_dbg(3, "dis fid:%x, fh:%x, rc:%d\n", zdev->fid, fh, rc);
J
Jan Glauber 已提交
314 315
	if (!rc)
		zdev->fh = fh;
S
Sebastian Ott 已提交
316

J
Jan Glauber 已提交
317 318 319
	return rc;
}

320 321
static int clp_list_pci(struct clp_req_rsp_list_pci *rrb, void *data,
			void (*cb)(struct clp_fh_list_entry *, void *))
J
Jan Glauber 已提交
322 323 324 325 326 327 328 329 330 331 332 333 334
{
	u64 resume_token = 0;
	int entries, i, rc;

	do {
		memset(rrb, 0, sizeof(*rrb));
		rrb->request.hdr.len = sizeof(rrb->request);
		rrb->request.hdr.cmd = CLP_LIST_PCI;
		/* store as many entries as possible */
		rrb->response.hdr.len = CLP_BLK_SIZE - LIST_PCI_HDR_LEN;
		rrb->request.resume_token = resume_token;

		/* Get PCI function handle list */
335
		rc = clp_req(rrb, CLP_LPS_PCI);
J
Jan Glauber 已提交
336
		if (rc || rrb->response.hdr.rsp != CLP_RC_OK) {
S
Sebastian Ott 已提交
337 338
			zpci_err("List PCI FN:\n");
			zpci_err_clp(rrb->response.hdr.rsp, rc);
J
Jan Glauber 已提交
339 340 341 342
			rc = -EIO;
			goto out;
		}

343
		update_uid_checking(rrb->response.uid_checking);
J
Jan Glauber 已提交
344 345 346 347 348 349 350 351
		WARN_ON_ONCE(rrb->response.entry_size !=
			sizeof(struct clp_fh_list_entry));

		entries = (rrb->response.hdr.len - LIST_PCI_HDR_LEN) /
			rrb->response.entry_size;

		resume_token = rrb->response.resume_token;
		for (i = 0; i < entries; i++)
352
			cb(&rrb->response.fh_list[i], data);
J
Jan Glauber 已提交
353 354
	} while (resume_token);
out:
S
Sebastian Ott 已提交
355 356 357
	return rc;
}

358
static void __clp_add(struct clp_fh_list_entry *entry, void *data)
S
Sebastian Ott 已提交
359 360 361 362 363 364 365
{
	struct zpci_dev *zdev;

	if (!entry->vendor_id)
		return;

	zdev = get_zdev_by_fid(entry->fid);
366
	if (!zdev)
S
Sebastian Ott 已提交
367 368 369
		clp_add_pci_device(entry->fid, entry->fh, entry->config_state);
}

370
static void __clp_update(struct clp_fh_list_entry *entry, void *data)
371 372 373 374 375 376 377 378 379 380 381 382 383
{
	struct zpci_dev *zdev;

	if (!entry->vendor_id)
		return;

	zdev = get_zdev_by_fid(entry->fid);
	if (!zdev)
		return;

	zdev->fh = entry->fh;
}

S
Sebastian Ott 已提交
384 385 386 387 388 389 390 391 392
int clp_scan_pci_devices(void)
{
	struct clp_req_rsp_list_pci *rrb;
	int rc;

	rrb = clp_alloc_block(GFP_KERNEL);
	if (!rrb)
		return -ENOMEM;

393
	rc = clp_list_pci(rrb, NULL, __clp_add);
S
Sebastian Ott 已提交
394 395 396 397 398 399 400 401 402 403

	clp_free_block(rrb);
	return rc;
}

int clp_rescan_pci_devices(void)
{
	struct clp_req_rsp_list_pci *rrb;
	int rc;

404 405
	zpci_remove_reserved_devices();

S
Sebastian Ott 已提交
406 407 408 409
	rrb = clp_alloc_block(GFP_KERNEL);
	if (!rrb)
		return -ENOMEM;

410
	rc = clp_list_pci(rrb, NULL, __clp_add);
S
Sebastian Ott 已提交
411

J
Jan Glauber 已提交
412 413 414
	clp_free_block(rrb);
	return rc;
}
415 416 417 418 419 420 421 422 423 424

int clp_rescan_pci_devices_simple(void)
{
	struct clp_req_rsp_list_pci *rrb;
	int rc;

	rrb = clp_alloc_block(GFP_NOWAIT);
	if (!rrb)
		return -ENOMEM;

425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
	rc = clp_list_pci(rrb, NULL, __clp_update);

	clp_free_block(rrb);
	return rc;
}

struct clp_state_data {
	u32 fid;
	enum zpci_state state;
};

static void __clp_get_state(struct clp_fh_list_entry *entry, void *data)
{
	struct clp_state_data *sd = data;

	if (entry->fid != sd->fid)
		return;

	sd->state = entry->config_state;
}

int clp_get_state(u32 fid, enum zpci_state *state)
{
	struct clp_req_rsp_list_pci *rrb;
	struct clp_state_data sd = {fid, ZPCI_FN_STATE_RESERVED};
	int rc;

452
	rrb = clp_alloc_block(GFP_ATOMIC);
453 454 455 456 457 458
	if (!rrb)
		return -ENOMEM;

	rc = clp_list_pci(rrb, &sd, __clp_get_state);
	if (!rc)
		*state = sd.state;
459 460 461 462

	clp_free_block(rrb);
	return rc;
}
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657

static int clp_base_slpc(struct clp_req *req, struct clp_req_rsp_slpc *lpcb)
{
	unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);

	if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
	    lpcb->response.hdr.len > limit)
		return -EINVAL;
	return clp_req(lpcb, CLP_LPS_BASE) ? -EOPNOTSUPP : 0;
}

static int clp_base_command(struct clp_req *req, struct clp_req_hdr *lpcb)
{
	switch (lpcb->cmd) {
	case 0x0001: /* store logical-processor characteristics */
		return clp_base_slpc(req, (void *) lpcb);
	default:
		return -EINVAL;
	}
}

static int clp_pci_slpc(struct clp_req *req, struct clp_req_rsp_slpc *lpcb)
{
	unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);

	if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
	    lpcb->response.hdr.len > limit)
		return -EINVAL;
	return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
}

static int clp_pci_list(struct clp_req *req, struct clp_req_rsp_list_pci *lpcb)
{
	unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);

	if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
	    lpcb->response.hdr.len > limit)
		return -EINVAL;
	if (lpcb->request.reserved2 != 0)
		return -EINVAL;
	return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
}

static int clp_pci_query(struct clp_req *req,
			 struct clp_req_rsp_query_pci *lpcb)
{
	unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);

	if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
	    lpcb->response.hdr.len > limit)
		return -EINVAL;
	if (lpcb->request.reserved2 != 0 || lpcb->request.reserved3 != 0)
		return -EINVAL;
	return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
}

static int clp_pci_query_grp(struct clp_req *req,
			     struct clp_req_rsp_query_pci_grp *lpcb)
{
	unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);

	if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
	    lpcb->response.hdr.len > limit)
		return -EINVAL;
	if (lpcb->request.reserved2 != 0 || lpcb->request.reserved3 != 0 ||
	    lpcb->request.reserved4 != 0)
		return -EINVAL;
	return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
}

static int clp_pci_command(struct clp_req *req, struct clp_req_hdr *lpcb)
{
	switch (lpcb->cmd) {
	case 0x0001: /* store logical-processor characteristics */
		return clp_pci_slpc(req, (void *) lpcb);
	case 0x0002: /* list PCI functions */
		return clp_pci_list(req, (void *) lpcb);
	case 0x0003: /* query PCI function */
		return clp_pci_query(req, (void *) lpcb);
	case 0x0004: /* query PCI function group */
		return clp_pci_query_grp(req, (void *) lpcb);
	default:
		return -EINVAL;
	}
}

static int clp_normal_command(struct clp_req *req)
{
	struct clp_req_hdr *lpcb;
	void __user *uptr;
	int rc;

	rc = -EINVAL;
	if (req->lps != 0 && req->lps != 2)
		goto out;

	rc = -ENOMEM;
	lpcb = clp_alloc_block(GFP_KERNEL);
	if (!lpcb)
		goto out;

	rc = -EFAULT;
	uptr = (void __force __user *)(unsigned long) req->data_p;
	if (copy_from_user(lpcb, uptr, PAGE_SIZE) != 0)
		goto out_free;

	rc = -EINVAL;
	if (lpcb->fmt != 0 || lpcb->reserved1 != 0 || lpcb->reserved2 != 0)
		goto out_free;

	switch (req->lps) {
	case 0:
		rc = clp_base_command(req, lpcb);
		break;
	case 2:
		rc = clp_pci_command(req, lpcb);
		break;
	}
	if (rc)
		goto out_free;

	rc = -EFAULT;
	if (copy_to_user(uptr, lpcb, PAGE_SIZE) != 0)
		goto out_free;

	rc = 0;

out_free:
	clp_free_block(lpcb);
out:
	return rc;
}

static int clp_immediate_command(struct clp_req *req)
{
	void __user *uptr;
	unsigned long ilp;
	int exists;

	if (req->cmd > 1 || clp_get_ilp(&ilp) != 0)
		return -EINVAL;

	uptr = (void __force __user *)(unsigned long) req->data_p;
	if (req->cmd == 0) {
		/* Command code 0: test for a specific processor */
		exists = test_bit_inv(req->lps, &ilp);
		return put_user(exists, (int __user *) uptr);
	}
	/* Command code 1: return bit mask of installed processors */
	return put_user(ilp, (unsigned long __user *) uptr);
}

static long clp_misc_ioctl(struct file *filp, unsigned int cmd,
			   unsigned long arg)
{
	struct clp_req req;
	void __user *argp;

	if (cmd != CLP_SYNC)
		return -EINVAL;

	argp = is_compat_task() ? compat_ptr(arg) : (void __user *) arg;
	if (copy_from_user(&req, argp, sizeof(req)))
		return -EFAULT;
	if (req.r != 0)
		return -EINVAL;
	return req.c ? clp_immediate_command(&req) : clp_normal_command(&req);
}

static int clp_misc_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static const struct file_operations clp_misc_fops = {
	.owner = THIS_MODULE,
	.open = nonseekable_open,
	.release = clp_misc_release,
	.unlocked_ioctl = clp_misc_ioctl,
	.compat_ioctl = clp_misc_ioctl,
	.llseek = no_llseek,
};

static struct miscdevice clp_misc_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "clp",
	.fops = &clp_misc_fops,
};

static int __init clp_misc_init(void)
{
	return misc_register(&clp_misc_device);
}

device_initcall(clp_misc_init);