brintos

brintos / linux-shallow public Read only

0
0
Text · 13.0 KiB · 701db2c Raw
565 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 *  Copyright (C) 2005-2007 Jiri Slaby <jirislaby@gmail.com>4 *5 *  You need a userspace library to cooperate with this driver. It (and other6 *  info) may be obtained here:7 *  http://www.fi.muni.cz/~xslaby/phantom.html8 *  or alternatively, you might use OpenHaptics provided by Sensable.9 */10 11#include <linux/compat.h>12#include <linux/kernel.h>13#include <linux/module.h>14#include <linux/device.h>15#include <linux/pci.h>16#include <linux/fs.h>17#include <linux/poll.h>18#include <linux/interrupt.h>19#include <linux/cdev.h>20#include <linux/slab.h>21#include <linux/phantom.h>22#include <linux/sched.h>23#include <linux/mutex.h>24 25#include <linux/atomic.h>26#include <asm/io.h>27 28#define PHANTOM_VERSION		"n0.9.8"29 30#define PHANTOM_MAX_MINORS	831 32#define PHN_IRQCTL		0x4c    /* irq control in caddr space */33 34#define PHB_RUNNING		135#define PHB_NOT_OH		236 37static DEFINE_MUTEX(phantom_mutex);38static int phantom_major;39 40static const struct class phantom_class = {41	.name = "phantom",42};43 44struct phantom_device {45	unsigned int opened;46	void __iomem *caddr;47	u32 __iomem *iaddr;48	u32 __iomem *oaddr;49	unsigned long status;50	atomic_t counter;51 52	wait_queue_head_t wait;53	struct cdev cdev;54 55	struct mutex open_lock;56	spinlock_t regs_lock;57 58	/* used in NOT_OH mode */59	struct phm_regs oregs;60	u32 ctl_reg;61};62 63static unsigned char phantom_devices[PHANTOM_MAX_MINORS];64 65static int phantom_status(struct phantom_device *dev, unsigned long newstat)66{67	pr_debug("phantom_status %lx %lx\n", dev->status, newstat);68 69	if (!(dev->status & PHB_RUNNING) && (newstat & PHB_RUNNING)) {70		atomic_set(&dev->counter, 0);71		iowrite32(PHN_CTL_IRQ, dev->iaddr + PHN_CONTROL);72		iowrite32(0x43, dev->caddr + PHN_IRQCTL);73		ioread32(dev->caddr + PHN_IRQCTL); /* PCI posting */74	} else if ((dev->status & PHB_RUNNING) && !(newstat & PHB_RUNNING)) {75		iowrite32(0, dev->caddr + PHN_IRQCTL);76		ioread32(dev->caddr + PHN_IRQCTL); /* PCI posting */77	}78 79	dev->status = newstat;80 81	return 0;82}83 84/*85 * File ops86 */87 88static long phantom_ioctl(struct file *file, unsigned int cmd,89		unsigned long arg)90{91	struct phantom_device *dev = file->private_data;92	struct phm_regs rs;93	struct phm_reg r;94	void __user *argp = (void __user *)arg;95	unsigned long flags;96	unsigned int i;97 98	switch (cmd) {99	case PHN_SETREG:100	case PHN_SET_REG:101		if (copy_from_user(&r, argp, sizeof(r)))102			return -EFAULT;103 104		if (r.reg > 7)105			return -EINVAL;106 107		spin_lock_irqsave(&dev->regs_lock, flags);108		if (r.reg == PHN_CONTROL && (r.value & PHN_CTL_IRQ) &&109				phantom_status(dev, dev->status | PHB_RUNNING)){110			spin_unlock_irqrestore(&dev->regs_lock, flags);111			return -ENODEV;112		}113 114		pr_debug("phantom: writing %x to %u\n", r.value, r.reg);115 116		/* preserve amp bit (don't allow to change it when in NOT_OH) */117		if (r.reg == PHN_CONTROL && (dev->status & PHB_NOT_OH)) {118			r.value &= ~PHN_CTL_AMP;119			r.value |= dev->ctl_reg & PHN_CTL_AMP;120			dev->ctl_reg = r.value;121		}122 123		iowrite32(r.value, dev->iaddr + r.reg);124		ioread32(dev->iaddr); /* PCI posting */125 126		if (r.reg == PHN_CONTROL && !(r.value & PHN_CTL_IRQ))127			phantom_status(dev, dev->status & ~PHB_RUNNING);128		spin_unlock_irqrestore(&dev->regs_lock, flags);129		break;130	case PHN_SETREGS:131	case PHN_SET_REGS:132		if (copy_from_user(&rs, argp, sizeof(rs)))133			return -EFAULT;134 135		pr_debug("phantom: SRS %u regs %x\n", rs.count, rs.mask);136		spin_lock_irqsave(&dev->regs_lock, flags);137		if (dev->status & PHB_NOT_OH)138			memcpy(&dev->oregs, &rs, sizeof(rs));139		else {140			u32 m = min(rs.count, 8U);141			for (i = 0; i < m; i++)142				if (rs.mask & BIT(i))143					iowrite32(rs.values[i], dev->oaddr + i);144			ioread32(dev->iaddr); /* PCI posting */145		}146		spin_unlock_irqrestore(&dev->regs_lock, flags);147		break;148	case PHN_GETREG:149	case PHN_GET_REG:150		if (copy_from_user(&r, argp, sizeof(r)))151			return -EFAULT;152 153		if (r.reg > 7)154			return -EINVAL;155 156		r.value = ioread32(dev->iaddr + r.reg);157 158		if (copy_to_user(argp, &r, sizeof(r)))159			return -EFAULT;160		break;161	case PHN_GETREGS:162	case PHN_GET_REGS: {163		u32 m;164 165		if (copy_from_user(&rs, argp, sizeof(rs)))166			return -EFAULT;167 168		m = min(rs.count, 8U);169 170		pr_debug("phantom: GRS %u regs %x\n", rs.count, rs.mask);171		spin_lock_irqsave(&dev->regs_lock, flags);172		for (i = 0; i < m; i++)173			if (rs.mask & BIT(i))174				rs.values[i] = ioread32(dev->iaddr + i);175		atomic_set(&dev->counter, 0);176		spin_unlock_irqrestore(&dev->regs_lock, flags);177 178		if (copy_to_user(argp, &rs, sizeof(rs)))179			return -EFAULT;180		break;181	} case PHN_NOT_OH:182		spin_lock_irqsave(&dev->regs_lock, flags);183		if (dev->status & PHB_RUNNING) {184			printk(KERN_ERR "phantom: you need to set NOT_OH "185					"before you start the device!\n");186			spin_unlock_irqrestore(&dev->regs_lock, flags);187			return -EINVAL;188		}189		dev->status |= PHB_NOT_OH;190		spin_unlock_irqrestore(&dev->regs_lock, flags);191		break;192	default:193		return -ENOTTY;194	}195 196	return 0;197}198 199#ifdef CONFIG_COMPAT200static long phantom_compat_ioctl(struct file *filp, unsigned int cmd,201		unsigned long arg)202{203	if (_IOC_NR(cmd) <= 3 && _IOC_SIZE(cmd) == sizeof(compat_uptr_t)) {204		cmd &= ~(_IOC_SIZEMASK << _IOC_SIZESHIFT);205		cmd |= sizeof(void *) << _IOC_SIZESHIFT;206	}207	return phantom_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));208}209#else210#define phantom_compat_ioctl NULL211#endif212 213static int phantom_open(struct inode *inode, struct file *file)214{215	struct phantom_device *dev = container_of(inode->i_cdev,216			struct phantom_device, cdev);217 218	mutex_lock(&phantom_mutex);219	nonseekable_open(inode, file);220 221	if (mutex_lock_interruptible(&dev->open_lock)) {222		mutex_unlock(&phantom_mutex);223		return -ERESTARTSYS;224	}225 226	if (dev->opened) {227		mutex_unlock(&dev->open_lock);228		mutex_unlock(&phantom_mutex);229		return -EINVAL;230	}231 232	WARN_ON(dev->status & PHB_NOT_OH);233 234	file->private_data = dev;235 236	atomic_set(&dev->counter, 0);237	dev->opened++;238	mutex_unlock(&dev->open_lock);239	mutex_unlock(&phantom_mutex);240	return 0;241}242 243static int phantom_release(struct inode *inode, struct file *file)244{245	struct phantom_device *dev = file->private_data;246 247	mutex_lock(&dev->open_lock);248 249	dev->opened = 0;250	phantom_status(dev, dev->status & ~PHB_RUNNING);251	dev->status &= ~PHB_NOT_OH;252 253	mutex_unlock(&dev->open_lock);254 255	return 0;256}257 258static __poll_t phantom_poll(struct file *file, poll_table *wait)259{260	struct phantom_device *dev = file->private_data;261	__poll_t mask = 0;262 263	pr_debug("phantom_poll: %d\n", atomic_read(&dev->counter));264	poll_wait(file, &dev->wait, wait);265 266	if (!(dev->status & PHB_RUNNING))267		mask = EPOLLERR;268	else if (atomic_read(&dev->counter))269		mask = EPOLLIN | EPOLLRDNORM;270 271	pr_debug("phantom_poll end: %x/%d\n", mask, atomic_read(&dev->counter));272 273	return mask;274}275 276static const struct file_operations phantom_file_ops = {277	.open = phantom_open,278	.release = phantom_release,279	.unlocked_ioctl = phantom_ioctl,280	.compat_ioctl = phantom_compat_ioctl,281	.poll = phantom_poll,282};283 284static irqreturn_t phantom_isr(int irq, void *data)285{286	struct phantom_device *dev = data;287	unsigned int i;288	u32 ctl;289 290	spin_lock(&dev->regs_lock);291	ctl = ioread32(dev->iaddr + PHN_CONTROL);292	if (!(ctl & PHN_CTL_IRQ)) {293		spin_unlock(&dev->regs_lock);294		return IRQ_NONE;295	}296 297	iowrite32(0, dev->iaddr);298	iowrite32(0xc0, dev->iaddr);299 300	if (dev->status & PHB_NOT_OH) {301		struct phm_regs *r = &dev->oregs;302		u32 m = min(r->count, 8U);303 304		for (i = 0; i < m; i++)305			if (r->mask & BIT(i))306				iowrite32(r->values[i], dev->oaddr + i);307 308		dev->ctl_reg ^= PHN_CTL_AMP;309		iowrite32(dev->ctl_reg, dev->iaddr + PHN_CONTROL);310	}311	spin_unlock(&dev->regs_lock);312 313	ioread32(dev->iaddr); /* PCI posting */314 315	atomic_inc(&dev->counter);316	wake_up_interruptible(&dev->wait);317 318	return IRQ_HANDLED;319}320 321/*322 * Init and deinit driver323 */324 325static unsigned int phantom_get_free(void)326{327	unsigned int i;328 329	for (i = 0; i < PHANTOM_MAX_MINORS; i++)330		if (phantom_devices[i] == 0)331			break;332 333	return i;334}335 336static int phantom_probe(struct pci_dev *pdev,337	const struct pci_device_id *pci_id)338{339	struct phantom_device *pht;340	unsigned int minor;341	int retval;342 343	retval = pci_enable_device(pdev);344	if (retval) {345		dev_err(&pdev->dev, "pci_enable_device failed!\n");346		goto err;347	}348 349	minor = phantom_get_free();350	if (minor == PHANTOM_MAX_MINORS) {351		dev_err(&pdev->dev, "too many devices found!\n");352		retval = -EIO;353		goto err_dis;354	}355 356	phantom_devices[minor] = 1;357 358	retval = pci_request_regions(pdev, "phantom");359	if (retval) {360		dev_err(&pdev->dev, "pci_request_regions failed!\n");361		goto err_null;362	}363 364	retval = -ENOMEM;365	pht = kzalloc(sizeof(*pht), GFP_KERNEL);366	if (pht == NULL) {367		dev_err(&pdev->dev, "unable to allocate device\n");368		goto err_reg;369	}370 371	pht->caddr = pci_iomap(pdev, 0, 0);372	if (pht->caddr == NULL) {373		dev_err(&pdev->dev, "can't remap conf space\n");374		goto err_fr;375	}376	pht->iaddr = pci_iomap(pdev, 2, 0);377	if (pht->iaddr == NULL) {378		dev_err(&pdev->dev, "can't remap input space\n");379		goto err_unmc;380	}381	pht->oaddr = pci_iomap(pdev, 3, 0);382	if (pht->oaddr == NULL) {383		dev_err(&pdev->dev, "can't remap output space\n");384		goto err_unmi;385	}386 387	mutex_init(&pht->open_lock);388	spin_lock_init(&pht->regs_lock);389	init_waitqueue_head(&pht->wait);390	cdev_init(&pht->cdev, &phantom_file_ops);391	pht->cdev.owner = THIS_MODULE;392 393	iowrite32(0, pht->caddr + PHN_IRQCTL);394	ioread32(pht->caddr + PHN_IRQCTL); /* PCI posting */395	retval = request_irq(pdev->irq, phantom_isr,396			IRQF_SHARED, "phantom", pht);397	if (retval) {398		dev_err(&pdev->dev, "can't establish ISR\n");399		goto err_unmo;400	}401 402	retval = cdev_add(&pht->cdev, MKDEV(phantom_major, minor), 1);403	if (retval) {404		dev_err(&pdev->dev, "chardev registration failed\n");405		goto err_irq;406	}407 408	if (IS_ERR(device_create(&phantom_class, &pdev->dev,409				 MKDEV(phantom_major, minor), NULL,410				 "phantom%u", minor)))411		dev_err(&pdev->dev, "can't create device\n");412 413	pci_set_drvdata(pdev, pht);414 415	return 0;416err_irq:417	free_irq(pdev->irq, pht);418err_unmo:419	pci_iounmap(pdev, pht->oaddr);420err_unmi:421	pci_iounmap(pdev, pht->iaddr);422err_unmc:423	pci_iounmap(pdev, pht->caddr);424err_fr:425	kfree(pht);426err_reg:427	pci_release_regions(pdev);428err_null:429	phantom_devices[minor] = 0;430err_dis:431	pci_disable_device(pdev);432err:433	return retval;434}435 436static void phantom_remove(struct pci_dev *pdev)437{438	struct phantom_device *pht = pci_get_drvdata(pdev);439	unsigned int minor = MINOR(pht->cdev.dev);440 441	device_destroy(&phantom_class, MKDEV(phantom_major, minor));442 443	cdev_del(&pht->cdev);444 445	iowrite32(0, pht->caddr + PHN_IRQCTL);446	ioread32(pht->caddr + PHN_IRQCTL); /* PCI posting */447	free_irq(pdev->irq, pht);448 449	pci_iounmap(pdev, pht->oaddr);450	pci_iounmap(pdev, pht->iaddr);451	pci_iounmap(pdev, pht->caddr);452 453	kfree(pht);454 455	pci_release_regions(pdev);456 457	phantom_devices[minor] = 0;458 459	pci_disable_device(pdev);460}461 462static int __maybe_unused phantom_suspend(struct device *dev_d)463{464	struct phantom_device *dev = dev_get_drvdata(dev_d);465 466	iowrite32(0, dev->caddr + PHN_IRQCTL);467	ioread32(dev->caddr + PHN_IRQCTL); /* PCI posting */468 469	synchronize_irq(to_pci_dev(dev_d)->irq);470 471	return 0;472}473 474static int __maybe_unused phantom_resume(struct device *dev_d)475{476	struct phantom_device *dev = dev_get_drvdata(dev_d);477 478	iowrite32(0, dev->caddr + PHN_IRQCTL);479 480	return 0;481}482 483static struct pci_device_id phantom_pci_tbl[] = {484	{ .vendor = PCI_VENDOR_ID_PLX, .device = PCI_DEVICE_ID_PLX_9050,485	  .subvendor = PCI_VENDOR_ID_PLX, .subdevice = PCI_DEVICE_ID_PLX_9050,486	  .class = PCI_CLASS_BRIDGE_OTHER << 8, .class_mask = 0xffff00 },487	{ 0, }488};489MODULE_DEVICE_TABLE(pci, phantom_pci_tbl);490 491static SIMPLE_DEV_PM_OPS(phantom_pm_ops, phantom_suspend, phantom_resume);492 493static struct pci_driver phantom_pci_driver = {494	.name = "phantom",495	.id_table = phantom_pci_tbl,496	.probe = phantom_probe,497	.remove = phantom_remove,498	.driver.pm = &phantom_pm_ops,499};500 501static CLASS_ATTR_STRING(version, 0444, PHANTOM_VERSION);502 503static int __init phantom_init(void)504{505	int retval;506	dev_t dev;507 508	retval = class_register(&phantom_class);509	if (retval) {510		printk(KERN_ERR "phantom: can't register phantom class\n");511		goto err;512	}513	retval = class_create_file(&phantom_class, &class_attr_version.attr);514	if (retval) {515		printk(KERN_ERR "phantom: can't create sysfs version file\n");516		goto err_class;517	}518 519	retval = alloc_chrdev_region(&dev, 0, PHANTOM_MAX_MINORS, "phantom");520	if (retval) {521		printk(KERN_ERR "phantom: can't register character device\n");522		goto err_attr;523	}524	phantom_major = MAJOR(dev);525 526	retval = pci_register_driver(&phantom_pci_driver);527	if (retval) {528		printk(KERN_ERR "phantom: can't register pci driver\n");529		goto err_unchr;530	}531 532	printk(KERN_INFO "Phantom Linux Driver, version " PHANTOM_VERSION ", "533			"init OK\n");534 535	return 0;536err_unchr:537	unregister_chrdev_region(dev, PHANTOM_MAX_MINORS);538err_attr:539	class_remove_file(&phantom_class, &class_attr_version.attr);540err_class:541	class_unregister(&phantom_class);542err:543	return retval;544}545 546static void __exit phantom_exit(void)547{548	pci_unregister_driver(&phantom_pci_driver);549 550	unregister_chrdev_region(MKDEV(phantom_major, 0), PHANTOM_MAX_MINORS);551 552	class_remove_file(&phantom_class, &class_attr_version.attr);553	class_unregister(&phantom_class);554 555	pr_debug("phantom: module successfully removed\n");556}557 558module_init(phantom_init);559module_exit(phantom_exit);560 561MODULE_AUTHOR("Jiri Slaby <jirislaby@gmail.com>");562MODULE_DESCRIPTION("Sensable Phantom driver (PCI devices)");563MODULE_LICENSE("GPL");564MODULE_VERSION(PHANTOM_VERSION);565