hvcs.c

来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 1,667 行 · 第 1/4 页

C
1,667
字号
	 * If there wasn't any pi when the device was added it doesn't meant	 * there isn't any now.  This driver isn't notified when a new partner	 * vty is added to a vty-server so we discover changes on our own.	 * Please see comments in hvcs_register_connection() for justification	 * of this bizarre code.	 */	retval = hvcs_register_connection(unit_address,			hvcsd->p_partition_ID,			hvcsd->p_unit_address);	if (!retval) {		hvcsd->connected = 1;		return 0;	} else if (retval != -EINVAL)		return retval;	/*	 * As per the spec re-get the pi and try again if -EINVAL after the	 * first connection attempt.	 */	if (hvcs_get_pi(hvcsd))		return -ENOMEM;	if (!hvcs_has_pi(hvcsd))		return -ENODEV;	retval = hvcs_register_connection(unit_address,			hvcsd->p_partition_ID,			hvcsd->p_unit_address);	if (retval != -EINVAL) {		hvcsd->connected = 1;		return retval;	}	/*	 * EBUSY is the most likely scenario though the vty could have been	 * removed or there really could be an hcall error due to the parameter	 * data but thanks to ambiguous firmware return codes we can't really	 * tell.	 */	printk(KERN_INFO "HVCS: vty-server or partner"			" vty is busy.  Try again later.\n");	return -EBUSY;}/* This function must be called with the hvcsd->lock held */static void hvcs_partner_free(struct hvcs_struct *hvcsd){	int retval;	do {		retval = hvcs_free_connection(hvcsd->vdev->unit_address);	} while (retval == -EBUSY);	hvcsd->connected = 0;}/* This helper function must be called WITHOUT the hvcsd->lock held */static int hvcs_enable_device(struct hvcs_struct *hvcsd, uint32_t unit_address,		unsigned int irq, struct vio_dev *vdev){	unsigned long flags;	int rc;	/*	 * It is possible that the vty-server was removed between the time that	 * the conn was registered and now.	 */	if (!(rc = request_irq(irq, &hvcs_handle_interrupt,				SA_INTERRUPT, "ibmhvcs", hvcsd))) {		/*		 * It is possible the vty-server was removed after the irq was		 * requested but before we have time to enable interrupts.		 */		if (vio_enable_interrupts(vdev) == H_Success)			return 0;		else {			printk(KERN_ERR "HVCS: int enable failed for"					" vty-server@%X.\n", unit_address);			free_irq(irq, hvcsd);		}	} else		printk(KERN_ERR "HVCS: irq req failed for"				" vty-server@%X.\n", unit_address);	spin_lock_irqsave(&hvcsd->lock, flags);	hvcs_partner_free(hvcsd);	spin_unlock_irqrestore(&hvcsd->lock, flags);	return rc;}/* * This always increments the kobject ref count if the call is successful. * Please remember to dec when you are done with the instance. * * NOTICE: Do NOT hold either the hvcs_struct.lock or hvcs_structs_lock when * calling this function or you will get deadlock. */struct hvcs_struct *hvcs_get_by_index(int index){	struct hvcs_struct *hvcsd = NULL;	unsigned long flags;	spin_lock(&hvcs_structs_lock);	/* We can immediately discard OOB requests */	if (index >= 0 && index < HVCS_MAX_SERVER_ADAPTERS) {		list_for_each_entry(hvcsd, &hvcs_structs, next) {			spin_lock_irqsave(&hvcsd->lock, flags);			if (hvcsd->index == index) {				kobject_get(&hvcsd->kobj);				spin_unlock_irqrestore(&hvcsd->lock, flags);				spin_unlock(&hvcs_structs_lock);				return hvcsd;			}			spin_unlock_irqrestore(&hvcsd->lock, flags);		}		hvcsd = NULL;	}	spin_unlock(&hvcs_structs_lock);	return hvcsd;}/* * This is invoked via the tty_open interface when a user app connects to the * /dev node. */static int hvcs_open(struct tty_struct *tty, struct file *filp){	struct hvcs_struct *hvcsd;	int rc, retval = 0;	unsigned long flags;	unsigned int irq;	struct vio_dev *vdev;	unsigned long unit_address;	struct kobject *kobjp;	if (tty->driver_data)		goto fast_open;	/*	 * Is there a vty-server that shares the same index?	 * This function increments the kobject index.	 */	if (!(hvcsd = hvcs_get_by_index(tty->index))) {		printk(KERN_WARNING "HVCS: open failed, no device associated"				" with tty->index %d.\n", tty->index);		return -ENODEV;	}	spin_lock_irqsave(&hvcsd->lock, flags);	if (hvcsd->connected == 0)		if ((retval = hvcs_partner_connect(hvcsd)))			goto error_release;	hvcsd->open_count = 1;	hvcsd->tty = tty;	tty->driver_data = hvcsd;	/*	 * Set this driver to low latency so that we actually have a chance at	 * catching a throttled TTY after we flip_buffer_push.  Otherwise the	 * flush_to_async may not execute until after the kernel_thread has	 * yielded and resumed the next flip_buffer_push resulting in data	 * loss.	 */	tty->low_latency = 1;	memset(&hvcsd->buffer[0], 0x00, HVCS_BUFF_LEN);	/*	 * Save these in the spinlock for the enable operations that need them	 * outside of the spinlock.	 */	irq = hvcsd->vdev->irq;	vdev = hvcsd->vdev;	unit_address = hvcsd->vdev->unit_address;	hvcsd->todo_mask |= HVCS_SCHED_READ;	spin_unlock_irqrestore(&hvcsd->lock, flags);	/*	 * This must be done outside of the spinlock because it requests irqs	 * and will grab the spinlock and free the connection if it fails.	 */	if (((rc = hvcs_enable_device(hvcsd, unit_address, irq, vdev)))) {		kobject_put(&hvcsd->kobj);		printk(KERN_WARNING "HVCS: enable device failed.\n");		return rc;	}	goto open_success;fast_open:	hvcsd = tty->driver_data;	spin_lock_irqsave(&hvcsd->lock, flags);	if (!kobject_get(&hvcsd->kobj)) {		spin_unlock_irqrestore(&hvcsd->lock, flags);		printk(KERN_ERR "HVCS: Kobject of open"			" hvcs doesn't exist.\n");		return -EFAULT; /* Is this the right return value? */	}	hvcsd->open_count++;	hvcsd->todo_mask |= HVCS_SCHED_READ;	spin_unlock_irqrestore(&hvcsd->lock, flags);open_success:	hvcs_kick();	printk(KERN_INFO "HVCS: vty-server@%X connection opened.\n",		hvcsd->vdev->unit_address );	return 0;error_release:	kobjp = &hvcsd->kobj;	spin_unlock_irqrestore(&hvcsd->lock, flags);	kobject_put(&hvcsd->kobj);	printk(KERN_WARNING "HVCS: partner connect failed.\n");	return retval;}static void hvcs_close(struct tty_struct *tty, struct file *filp){	struct hvcs_struct *hvcsd;	unsigned long flags;	struct kobject *kobjp;	int irq = NO_IRQ;	/*	 * Is someone trying to close the file associated with this device after	 * we have hung up?  If so tty->driver_data wouldn't be valid.	 */	if (tty_hung_up_p(filp))		return;	/*	 * No driver_data means that this close was probably issued after a	 * failed hvcs_open by the tty layer's release_dev() api and we can just	 * exit cleanly.	 */	if (!tty->driver_data)		return;	hvcsd = tty->driver_data;	spin_lock_irqsave(&hvcsd->lock, flags);	kobjp = &hvcsd->kobj;	if (--hvcsd->open_count == 0) {		vio_disable_interrupts(hvcsd->vdev);		/*		 * NULL this early so that the kernel_thread doesn't try to		 * execute any operations on the TTY even though it is obligated		 * to deliver any pending I/O to the hypervisor.		 */		hvcsd->tty = NULL;		irq = hvcsd->vdev->irq;		spin_unlock_irqrestore(&hvcsd->lock, flags);		tty_wait_until_sent(tty, HVCS_CLOSE_WAIT);		/*		 * This line is important because it tells hvcs_open that this		 * device needs to be re-configured the next time hvcs_open is		 * called.		 */		tty->driver_data = NULL;		free_irq(irq, hvcsd);		kobject_put(kobjp);		return;	} else if (hvcsd->open_count < 0) {		printk(KERN_ERR "HVCS: vty-server@%X open_count: %d"				" is missmanaged.\n",		hvcsd->vdev->unit_address, hvcsd->open_count);	}	spin_unlock_irqrestore(&hvcsd->lock, flags);	kobject_put(kobjp);}static void hvcs_hangup(struct tty_struct * tty){	struct hvcs_struct *hvcsd = tty->driver_data;	unsigned long flags;	int temp_open_count;	struct kobject *kobjp;	int irq = NO_IRQ;	spin_lock_irqsave(&hvcsd->lock, flags);	/* Preserve this so that we know how many kobject refs to put */	temp_open_count = hvcsd->open_count;	/*	 * Don't kobject put inside the spinlock because the destruction	 * callback may use the spinlock and it may get called before the	 * spinlock has been released.  Get a pointer to the kobject and	 * kobject_put on that after releasing the spinlock.	 */	kobjp = &hvcsd->kobj;	vio_disable_interrupts(hvcsd->vdev);	hvcsd->todo_mask = 0;	/* I don't think the tty needs the hvcs_struct pointer after a hangup */	hvcsd->tty->driver_data = NULL;	hvcsd->tty = NULL;	hvcsd->open_count = 0;	/* This will drop any buffered data on the floor which is OK in a hangup	 * scenario. */	memset(&hvcsd->buffer[0], 0x00, HVCS_BUFF_LEN);	hvcsd->chars_in_buffer = 0;	irq = hvcsd->vdev->irq;	spin_unlock_irqrestore(&hvcsd->lock, flags);	free_irq(irq, hvcsd);	/*	 * We need to kobject_put() for every open_count we have since the	 * tty_hangup() function doesn't invoke a close per open connection on a	 * non-console device.	 */	while(temp_open_count) {		--temp_open_count;		/*		 * The final put will trigger destruction of the hvcs_struct.		 * NOTE:  If this hangup was signaled from user space then the		 * final put will never happen.		 */		kobject_put(kobjp);	}}/* * NOTE: This is almost always from_user since user level apps interact with the * /dev nodes. I'm trusting that if hvcs_write gets called and interrupted by * hvcs_remove (which removes the target device and executes tty_hangup()) that * tty_hangup will allow hvcs_write time to complete execution before it * terminates our device. */static int hvcs_write(struct tty_struct *tty, int from_user,		const unsigned char *buf, int count){	struct hvcs_struct *hvcsd = tty->driver_data;	unsigned int unit_address;	unsigned char *charbuf;	unsigned long flags;	int total_sent = 0;	int tosend = 0;	int result = 0;	/*	 * If they don't check the return code off of their open they may	 * attempt this even if there is no connected device.	 */	if (!hvcsd)		return -ENODEV;	/* Reasonable size to prevent user level flooding */	if (count > HVCS_MAX_FROM_USER) {		printk(KERN_WARNING "HVCS write: count being truncated to"				" HVCS_MAX_FROM_USER.\n");		count = HVCS_MAX_FROM_USER;	}	if (!from_user)		charbuf = (unsigned char *)buf;	else {		charbuf = kmalloc(count, GFP_KERNEL);		if (!charbuf) {			printk(KERN_WARNING "HVCS: write -ENOMEM.\n");			return -ENOMEM;		}		if (copy_from_user(charbuf, buf, count)) {			kfree(charbuf);			printk(KERN_WARNING "HVCS: write -EFAULT.\n");			return -EFAULT;		}	}	spin_lock_irqsave(&hvcsd->lock, flags);	/*	 * Somehow an open succedded but the device was removed or the	 * connection terminated between the vty-server and partner vty during	 * the middle of a write operation?  This is a crummy place to do this	 * but we want to keep it all in the spinlock.	 */	if (hvcsd->open_count <= 0) {		spin_unlock_irqrestore(&hvcsd->lock, flags);		if (from_user)			kfree(charbuf);		return -ENODEV;	}	unit_address = hvcsd->vdev->unit_address;	while (count > 0) {		tosend = min(count, (HVCS_BUFF_LEN - hvcsd->chars_in_buffer));		/*		 * No more space, this probably means that the last call to		 * hvcs_write() didn't succeed and the buffer was filled up.		 */		if (!tosend)			break;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?