static int xillyusb_discovery()

in xillybus/xillyusb.c [1971:2112]


static int xillyusb_discovery(struct usb_interface *interface)
{
	int rc;
	struct xillyusb_dev *xdev = usb_get_intfdata(interface);
	__le16 bogus_chandesc[2];
	struct xillyfifo idt_fifo;
	struct xillyusb_channel *chan;
	unsigned int idt_len, names_offset;
	unsigned char *idt;
	int num_channels;

	rc = xillyusb_send_opcode(xdev, ~0, OPCODE_QUIESCE, 0);

	if (rc) {
		dev_err(&interface->dev, "Failed to send quiesce request. Aborting.\n");
		return rc;
	}

	/* Phase I: Set up one fake upstream channel and obtain IDT */

	/* Set up a fake IDT with one async IN stream */
	bogus_chandesc[0] = cpu_to_le16(0x80);
	bogus_chandesc[1] = cpu_to_le16(0);

	rc = setup_channels(xdev, bogus_chandesc, 1);

	if (rc)
		return rc;

	rc = fifo_init(&idt_fifo, LOG2_IDT_FIFO_SIZE);

	if (rc)
		return rc;

	chan = xdev->channels;

	chan->in_fifo = &idt_fifo;
	chan->read_data_ok = 1;

	xdev->num_channels = 1;

	rc = xillyusb_send_opcode(xdev, ~0, OPCODE_REQ_IDT, 0);

	if (rc) {
		dev_err(&interface->dev, "Failed to send IDT request. Aborting.\n");
		goto unfifo;
	}

	rc = wait_event_interruptible_timeout(idt_fifo.waitq,
					      !chan->read_data_ok,
					      XILLY_RESPONSE_TIMEOUT);

	if (xdev->error) {
		rc = xdev->error;
		goto unfifo;
	}

	if (rc < 0) {
		rc = -EINTR; /* Interrupt on probe method? Interesting. */
		goto unfifo;
	}

	if (chan->read_data_ok) {
		rc = -ETIMEDOUT;
		dev_err(&interface->dev, "No response from FPGA. Aborting.\n");
		goto unfifo;
	}

	idt_len = READ_ONCE(idt_fifo.fill);
	idt = kmalloc(idt_len, GFP_KERNEL);

	if (!idt) {
		rc = -ENOMEM;
		goto unfifo;
	}

	fifo_read(&idt_fifo, idt, idt_len, xilly_memcpy);

	if (crc32_le(~0, idt, idt_len) != 0) {
		dev_err(&interface->dev, "IDT failed CRC check. Aborting.\n");
		rc = -ENODEV;
		goto unidt;
	}

	if (*idt > 0x90) {
		dev_err(&interface->dev, "No support for IDT version 0x%02x. Maybe the xillyusb driver needs an upgrade. Aborting.\n",
			(int)*idt);
		rc = -ENODEV;
		goto unidt;
	}

	/* Phase II: Set up the streams as defined in IDT */

	num_channels = le16_to_cpu(*((__le16 *)(idt + 1)));
	names_offset = 3 + num_channels * 4;
	idt_len -= 4; /* Exclude CRC */

	if (idt_len < names_offset) {
		dev_err(&interface->dev, "IDT too short. This is exceptionally weird, because its CRC is OK\n");
		rc = -ENODEV;
		goto unidt;
	}

	rc = setup_channels(xdev, (void *)idt + 3, num_channels);

	if (rc)
		goto unidt;

	/*
	 * Except for wildly misbehaving hardware, or if it was disconnected
	 * just after responding with the IDT, there is no reason for any
	 * work item to be running now. To be sure that xdev->channels
	 * is updated on anything that might run in parallel, flush the
	 * workqueue, which rarely does anything.
	 */
	flush_workqueue(xdev->workq);

	xdev->num_channels = num_channels;

	fifo_mem_release(&idt_fifo);
	kfree(chan);

	rc = xillybus_init_chrdev(&interface->dev, &xillyusb_fops,
				  THIS_MODULE, xdev,
				  idt + names_offset,
				  idt_len - names_offset,
				  num_channels,
				  xillyname, true);

	kfree(idt);

	return rc;

unidt:
	kfree(idt);

unfifo:
	safely_assign_in_fifo(chan, NULL);
	fifo_mem_release(&idt_fifo);

	return rc;
}