VME Bus drivers and framework for Linux
cover VME Bus drivers and framework for Linux
Hi all,
Here's 5 patches from Martyn Welch that adds VME Bus support to the kernel.
Martyn took the time and merged the three different existing implementations for Linux together into something that works, and got the proper legal agreements from all of the affected parties to release the code properly.
I'd like to thank him for taking the time to do this work, it is much appreciated.
These patches all add the code under the drivers/staging/ directory, as there is still a number of things that needs to be done to clean up the code to get it into "proper" mergable state, but it's a great first cut.
Thanks again Martyn!
greg k-h
PATCH 1/5 Staging: VME Framework for the Linux Kernel
From: Martyn Welch <[email protected]>
This framework aims to colelese, extend and improve the VME Linux drivers found at vmelinux.org, universe2.sourceforge.net and openfmi.net/frs/?group_id=144. The last 2 drivers appear to be forks of the original code found at vmelinux.org though have extended the codebase.
--- drivers/staging/Kconfig | 2 drivers/staging/Makefile | 1 drivers/staging/vme/Kconfig | 17 drivers/staging/vme/Makefile | 7 drivers/staging/vme/vme.c | 1371 +++++++++++++++++++++++++++++++++++++++ drivers/staging/vme/vme.h | 153 ++++ drivers/staging/vme/vme_bridge.h | 249 +++++++ 7 files changed, 1800 insertions(+)
drivers/staging/Kconfig +2 -0
@@ -139,5 +139,7 @@ source "drivers/staging/udlfb/Kconfig" source "drivers/staging/hv/Kconfig" +source "drivers/staging/vme/Kconfig" + endif # !STAGING_EXCLUDE_BUILD endif # STAGING
drivers/staging/Makefile +1 -0
@@ -51,3 +51,4 @@ obj-$(CONFIG_FB_UDL) += udlfb/ obj-$(CONFIG_MSM_ADSP) += dream/qdsp5/ dream/smd/ obj-$(CONFIG_MSM_CAMERA) += dream/camera/ obj-$(CONFIG_HYPERV) += hv/ +obj-$(CONFIG_VME) += vme/
drivers/staging/vme/Kconfig new file +17 -0
@@ -0,0 +1,17 @@ +# +# VME configuration. +# + +menuconfig VME + tristate "VME bridge support" + depends on PCI + ---help--- + If you say Y here you get support for the VME bridge Framework. + +if VME + +#source "drivers/staging/vme/bridges/Kconfig" +# +#source "drivers/staging/vme/devices/Kconfig" + +endif # VME
drivers/staging/vme/Makefile new file +7 -0
@@ -0,0 +1,7 @@ +# +# Makefile for the VME bridge device drivers. +# +obj-$(CONFIG_VME) += vme.o + +#obj-y += bridges/ +#obj-y += devices/
drivers/staging/vme/vme_bridge.h new file +249 -0
249 lines not on this page · raw message
drivers/staging/vme/vme.c new file +1371 -0
1,371 lines not on this page · raw message
drivers/staging/vme/vme.h new file +153 -0
153 lines not on this page · raw message
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
A few comments after a quick glance, will need to dig deeper though:
- semaphores? isn't it screaming for mutexes?
- some function signatures (get_whatever) pass too many pointers; it's rather ugly. Passing a pointer to an appropriate struct seems a better solution.
- vme_alloc_consistent looks pretty fishy; why don't you pass that responsibility to the specific master? There you obviously know if you're bridging over PCI or whatever. Or even better; why is this needed at all?
- Please explain me what all the DMA functions do; are they meant to be used by master or slaves?
Have a look at the interface for slaves we've got for our tsi148 driver, available at:
http://repo.or.cz/w/tsi148vmebridge.git
/* API for new drivers */
extern int vme_request_irq(unsigned int, int (*)(void *),
void *, const char *);
extern int vme_free_irq(unsigned int );
extern int vme_generate_interrupt(int, int, signed long); extern struct vme_mapping* find_vme_mapping_from_addr(unsigned); extern int vme_get_window_attr(struct vme_mapping *); extern int vme_create_window(struct vme_mapping *); extern int vme_destroy_window(int);
extern int vme_find_mapping(struct vme_mapping *, int); extern int vme_release_mapping(struct vme_mapping *, int); extern int vme_do_dma(struct vme_dma *);
extern int vme_bus_error_check(int);
That's pretty thin and it covers our slaves' needs. Do you see anything missing there?
For masters there's no interface there because it was the master's driver who directly provided these calls to slaves. I had it in my to-do list to split that from the tsi148, in the same fashion as you've done with this work.
- Note above the interrupt handler; simply needs the cookie. Also, shouldn't your vme_request_irq() just require the IRQ vector?
- I'd like to see the whole picture, or 'vertical slice', i.e. the bus interface + a master + a slave driver. How would the slave's driver get the addresses and sizes of the mappings, interrupt lines, etc. for each of the devices it controls? For the time being we quickly hacked an xml-based scheme to get this info upon installation, but it's clearly not suitable for mainline.
Will probably have more comments when I get some time for further inspection.
E.
(I'll leave this here because I've Cc'ed Sebastien Dugue)
Quoted 1,852 lines · Greg K-H
From: Martyn Welch <[email protected]>
This framework aims to colelese, extend and improve the VME Linux drivers found at vmelinux.org, universe2.sourceforge.net and openfmi.net/frs/?group_id=144. The last 2 drivers appear to be forks of the original code found at vmelinux.org though have extended the codebase.
Signed-off-by Martyn Welch <[email protected]>Signed-off-by Greg Kroah-Hartman <[email protected]>--- drivers/staging/Kconfig | 2 drivers/staging/Makefile | 1 drivers/staging/vme/Kconfig | 17 drivers/staging/vme/Makefile | 7 drivers/staging/vme/vme.c | 1371 +++++++++++++++++++++++++++++++++++++++ drivers/staging/vme/vme.h | 153 ++++ drivers/staging/vme/vme_bridge.h | 249 +++++++ 7 files changed, 1800 insertions(+)drivers/staging/Kconfig +2
-0@@ -139,5 +139,7 @@ source "drivers/staging/udlfb/Kconfig" source "drivers/staging/hv/Kconfig" +source "drivers/staging/vme/Kconfig" + endif # !STAGING_EXCLUDE_BUILD endif # STAGINGdrivers/staging/Makefile +1
-0@@ -51,3 +51,4 @@ obj-$(CONFIG_FB_UDL) += udlfb/ obj-$(CONFIG_MSM_ADSP) += dream/qdsp5/ dream/smd/ obj-$(CONFIG_MSM_CAMERA) += dream/camera/ obj-$(CONFIG_HYPERV) += hv/ +obj-$(CONFIG_VME) += vme/drivers/staging/vme/Kconfig new file +17
-0@@ -0,0 +1,17 @@ +# +# VME configuration. +# + +menuconfig VME + tristate "VME bridge support" + depends on PCI + ---help--- + If you say Y here you get support for the VME bridge Framework. + +if VME + +#source "drivers/staging/vme/bridges/Kconfig" +# +#source "drivers/staging/vme/devices/Kconfig" + +endif # VMEdrivers/staging/vme/Makefile new file +7
-0@@ -0,0 +1,7 @@ +# +# Makefile for the VME bridge device drivers. +# +obj-$(CONFIG_VME) += vme.o + +#obj-y += bridges/ +#obj-y += devices/drivers/staging/vme/vme_bridge.h new file +249
-0@@ -0,0 +1,249 @@ +#ifndef _VME_BRIDGE_H_ +#define _VME_BRIDGE_H_ + +#define VME_CRCSR_BUF_SIZE (508*1024) +#define VME_SLOTS_MAX 32 +/* +* Resource structures +*/ +struct vme_master_resource { +struct list_head list; +struct vme_bridge *parent; +/* +* We are likely to need to access the VME bus in interrupt context, so +* protect master routines with a spinlock rather than a semaphore. +*/ +spinlock_t lock; +int locked; +int number; +vme_address_t address_attr; +vme_cycle_t cycle_attr; +vme_width_t width_attr; +struct resource pci_resource;/* XXX Rename to be bus agnostic */ +void *kern_base; +}; + +struct vme_slave_resource { +struct list_head list; +struct vme_bridge *parent; +struct semaphore sem; +int locked; +int number; +vme_address_t address_attr; +vme_cycle_t cycle_attr; +}; + +struct vme_dma_pattern { +u32 pattern; +vme_pattern_t type; +}; + +struct vme_dma_pci { +dma_addr_t address; +}; + +struct vme_dma_vme { +unsigned long long address; +vme_address_t aspace; +vme_cycle_t cycle; +vme_width_t dwidth; +}; + +struct vme_dma_list { +struct list_head list; +struct vme_dma_resource *parent; +struct list_head entries; +struct semaphore sem; +}; + +struct vme_dma_resource { +struct list_head list; +struct vme_bridge *parent; +struct semaphore sem; +int locked; +int number; +struct list_head pending; +struct list_head running; +}; + +struct vme_bus_error { +struct list_head list; +unsigned long long address; +u32 attributes; +}; + +struct vme_callback { +void (*func)(int ,int ,void *); +void *priv_data; +}; + +struct vme_irq { +int count; +struct vme_callback callback[255 ]; +}; + +/* Allow 16 characters for name (including null character) */ +#define VMENAMSIZ 16 + +/* This structure stores all the information about one bridge +* The structure should be dynamically allocated by the driver and one instance +* of the structure should be present for each VME chip present in the system. +* +* Currently we assume that all chips are PCI-based +*/ +struct vme_bridge { +char name[VMENAMSIZ ]; +int num; +struct list_head master_resources; +struct list_head slave_resources; +struct list_head dma_resources; + +struct list_head vme_errors;/* List for errors generated on VME */ + +/* Bridge Info - XXX Move to private structure? */ +struct device *parent;/* Generic device struct (pdev->dev for PCI) */ +void * base;/* Base Address of device registers */ + +struct device dev[VME_SLOTS_MAX ];/* Device registered with +* device model on VME bus +*/ + +/* Interrupt callbacks */ +struct vme_irq irq[7 ]; + +/* Slave Functions */ +int (*slave_get) (struct vme_slave_resource *,int *, +unsigned long long *,unsigned long long *,dma_addr_t *, +vme_address_t *,vme_cycle_t *); +int (*slave_set) (struct vme_slave_resource *,int ,unsigned long long , +unsigned long long ,dma_addr_t ,vme_address_t ,vme_cycle_t ); + +/* Master Functions */ +int (*master_get) (struct vme_master_resource *,int *, +unsigned long long *,unsigned long long *,vme_address_t *, +vme_cycle_t *,vme_width_t *); +int (*master_set) (struct vme_master_resource *,int , +unsigned long long ,unsigned long long ,vme_address_t , +vme_cycle_t ,vme_width_t ); +ssize_t (*master_read) (struct vme_master_resource *,void *,size_t , +loff_t ); +ssize_t (*master_write) (struct vme_master_resource *,void *,size_t , +loff_t ); +unsigned int (*master_rmw) (struct vme_master_resource *,unsigned int , +unsigned int ,unsigned int ,loff_t ); + +/* DMA Functions */ +int (*dma_list_add) (struct vme_dma_list *,struct vme_dma_attr *, +struct vme_dma_attr *,size_t ); +int (*dma_list_exec) (struct vme_dma_list *); +int (*dma_list_empty) (struct vme_dma_list *); + +/* Interrupt Functions */ +int (*request_irq) (int ,int ,void (*cback)(int ,int ,void *),void *); +void (*free_irq) (int ,int ); +int (*generate_irq) (int ,int ); + +/* Location monitor functions */ +int (*lm_set) (unsigned long long ,vme_address_t ,vme_cycle_t ); +int (*lm_get) (unsigned long long *,vme_address_t *,vme_cycle_t *); +int (*lm_attach) (int ,void (*callback)(int )); +int (*lm_detach) (int ); + +/* CR/CSR space functions */ +int (*slot_get) (void ); +/* Use standard master read and write functions to access CR/CSR */ + +#if 0 +int (*set_prefetch) (void ); +int (*get_prefetch) (void ); +int (*set_arbiter) (void ); +int (*get_arbiter) (void ); +int (*set_requestor) (void ); +int (*get_requestor) (void ); +#endif +}; + +int vme_register_bridge (struct vme_bridge *); +void vme_unregister_bridge (struct vme_bridge *); + +#endif /* _VME_BRIDGE_H_ */ + +#if 0 +/* +* VMEbus GET INFO Arg Structure +*/ +struct vmeInfoCfg { +int vmeSlotNum;/* VME slot number of interest */ +int boardResponded;/* Board responded */ +char sysConFlag;/* System controller flag */ +int vmeControllerID;/* Vendor/device ID of VME bridge */ +int vmeControllerRev;/* Revision of VME bridge */ +char osName[8 ];/* Name of OS e.g. "Linux" */ +int vmeSharedDataValid;/* Validity of data struct */ +int vmeDriverRev;/* Revision of VME driver */ +unsigned int vmeAddrHi[8 ];/* Address on VME bus */ +unsigned int vmeAddrLo[8 ];/* Address on VME bus */ +unsigned int vmeSize[8 ];/* Size on VME bus */ +unsigned int vmeAm[8 ];/* Address modifier on VME bus */ +int reserved;/* For future use */ +}; +typedef struct vmeInfoCfg vmeInfoCfg_t ; + +/* +* VMEbus Requester Arg Structure +*/ +struct vmeRequesterCfg { +int requestLevel;/* Requester Bus Request Level */ +char fairMode;/* Requester Fairness Mode Indicator */ +int releaseMode;/* Requester Bus Release Mode */ +int timeonTimeoutTimer;/* Master Time-on Time-out Timer */ +int timeoffTimeoutTimer;/* Master Time-off Time-out Timer */ +int reserved;/* For future use */ +}; +typedef struct vmeRequesterCfg vmeRequesterCfg_t ; + +/* +* VMEbus Arbiter Arg Structure +*/ +struct vmeArbiterCfg { +vme_arbitration_t arbiterMode;/* Arbitration Scheduling Algorithm */ +char arbiterTimeoutFlag;/* Arbiter Time-out Timer Indicator */ +int globalTimeoutTimer;/* VMEbus Global Time-out Timer */ +char noEarlyReleaseFlag;/* No Early Release on BBUSY */ +int reserved;/* For future use */ +}; +typedef struct vmeArbiterCfg vmeArbiterCfg_t ; + + +/* +* VMEbus RMW Configuration Data +*/ +struct vmeRmwCfg { +unsigned int targetAddrU;/* VME Address (Upper) to trigger RMW cycle */ +unsigned int targetAddr;/* VME Address (Lower) to trigger RMW cycle */ +vme_address_t addrSpace;/* VME Address Space */ +int enableMask;/* Bit mask defining the bits of interest */ +int compareData;/* Data to be compared with the data read */ +int swapData;/* Data written to the VMEbus on success */ +int maxAttempts;/* Maximum times to try */ +int numAttempts;/* Number of attempts before success */ +int reserved;/* For future use */ + +}; +typedef struct vmeRmwCfg vmeRmwCfg_t ; + +/* +* VMEbus Location Monitor Arg Structure +*/ +struct vmeLmCfg { +unsigned int addrU;/* Location Monitor Address upper */ +unsigned int addr;/* Location Monitor Address lower */ +vme_address_t addrSpace;/* Address Space */ +int userAccessType;/* User/Supervisor Access Type */ +int dataAccessType;/* Data/Program Access Type */ +int lmWait;/* Time to wait for access */ +int lmEvents;/* Lm event mask */ +int reserved;/* For future use */ +}; +typedef struct vmeLmCfg vmeLmCfg_t ; +#endif drivers/staging/vme/vme.c new file +1371
-0@@ -0,0 +1,1371 @@ +/* +* VME Bridge Framework +* +* Author: Martyn Welch <[email protected]> +* Copyright 2008 GE Fanuc Intelligent Platforms Embedded Systems, Inc. +* +* Based on work by Tom Armistead and Ajit Prem +* Copyright 2004 Motorola Inc. +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mm.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/poll.h> +#include <linux/highmem.h> +#include <linux/interrupt.h> +#include <linux/pagemap.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/syscalls.h> +#include <linux/semaphore.h> +#include <linux/spinlock.h> + +#include "vme.h" +#include "vme_bridge.h" + +/* Bitmask and semaphore to keep track of bridge numbers */ +static unsigned int vme_bus_numbers; +DECLARE_MUTEX (vme_bus_num_sem); + +static void __exit vme_exit (void); +static int __init vme_init (void ); + + +/* +* Find the bridge resource associated with a specific device resource +*/ +static struct vme_bridge *dev_to_bridge (struct device *dev) +{ +return dev->platform_data; +} + +/* +* Find the bridge that the resource is associated with. +*/ +static struct vme_bridge *find_bridge (struct vme_resource *resource) +{ +/* Get list to search */ +switch (resource->type) { +case VME_MASTER : + return list_entry(resource->entry, struct vme_master_resource, + list)->parent; +break ; +case VME_SLAVE : + return list_entry(resource->entry, struct vme_slave_resource, + list)->parent; +break ; +case VME_DMA : + return list_entry(resource->entry, struct vme_dma_resource, + list)->parent; +break ; +default : +printk (KERN_ERR "Unknown resource type\n" ); +return NULL ; +break ; + } +} + +/* +* Allocate a contiguous block of memory for use by the driver. This is used to +* create the buffers for the slave windows. +* +* XXX VME bridges could be available on buses other than PCI. At the momment +* this framework only supports PCI devices. +*/ +void *vme_alloc_consistent (struct vme_resource *resource,size_t size, +dma_addr_t *dma) +{ +struct vme_bridge *bridge; +struct pci_dev *pdev; + +if (resource ==NULL ) { +printk ("No resource\n" ); +return NULL ; + } + + bridge =find_bridge (resource); +if (bridge ==NULL ) { +printk ("Can't find bridge\n" ); +return NULL ; + } + +/* Find pci_dev container of dev */ +if (bridge->parent ==NULL ) { +printk ("Dev entry NULL\n" ); +return NULL ; + } + pdev = container_of(bridge->parent, struct pci_dev, dev); + +return pci_alloc_consistent (pdev, size, dma); +} +EXPORT_SYMBOL (vme_alloc_consistent); + +/* +* Free previously allocated contiguous block of memory. +* +* XXX VME bridges could be available on buses other than PCI. At the momment +* this framework only supports PCI devices. +*/ +void vme_free_consistent (struct vme_resource *resource,size_t size, +void *vaddr,dma_addr_t dma) +{ +struct vme_bridge *bridge; +struct pci_dev *pdev; + +if (resource ==NULL ) { +printk ("No resource\n" ); +return ; + } + + bridge =find_bridge (resource); +if (bridge ==NULL ) { +printk ("Can't find bridge\n" ); +return ; + } + +/* Find pci_dev container of dev */ + pdev = container_of(bridge->parent, struct pci_dev, dev); + +pci_free_consistent (pdev, size, vaddr, dma); +} +EXPORT_SYMBOL (vme_free_consistent); + +size_t vme_get_size (struct vme_resource *resource) +{ +int enabled, retval; +unsigned long long base, size; +dma_addr_t buf_base; +vme_address_t aspace; +vme_cycle_t cycle; +vme_width_t dwidth; + +switch (resource->type) { +case VME_MASTER : + retval =vme_master_get (resource, &enabled, &base, &size, + &aspace, &cycle, &dwidth); + +return size; +break ; +case VME_SLAVE : + retval =vme_slave_get (resource, &enabled, &base, &size, + &buf_base, &aspace, &cycle); + +return size; +break ; +case VME_DMA : +return 0 ; +break ; +default : +printk (KERN_ERR "Unknown resource type\n" ); +return 0 ; +break ; + } +} +EXPORT_SYMBOL (vme_get_size); + +static int vme_check_window (vme_address_t aspace,unsigned long long vme_base, +unsigned long long size) +{ +int retval =0 ; + +switch (aspace) { +case VME_A16 : +if (((vme_base + size) >VME_A16_MAX ) || + (vme_base >VME_A16_MAX )) + retval = -EFAULT ; +break ; +case VME_A24 : +if (((vme_base + size) >VME_A24_MAX ) || + (vme_base >VME_A24_MAX )) + retval = -EFAULT ; +break ; +case VME_A32 : +if (((vme_base + size) >VME_A32_MAX ) || + (vme_base >VME_A32_MAX )) + retval = -EFAULT ; +break ; +case VME_A64 : +/* +* Any value held in an unsigned long long can be used as the +* base +*/ +break ; +case VME_CRCSR : +if (((vme_base + size) >VME_CRCSR_MAX ) || + (vme_base >VME_CRCSR_MAX )) + retval = -EFAULT ; +break ; +case VME_USER1 : +case VME_USER2 : +case VME_USER3 : +case VME_USER4 : +/* User Defined */ +break ; +default : +printk ("Invalid address space\n" ); + retval = -EINVAL ; +break ; + } + +return retval; +} + +/* +* Request a slave image with specific attributes, return some unique +* identifier. +*/ +struct vme_resource *vme_slave_request (struct device *dev, +vme_address_t address,vme_cycle_t cycle) +{ +struct vme_bridge *bridge; +struct list_head *slave_pos =NULL ; +struct vme_slave_resource *allocated_image =NULL ; +struct vme_slave_resource *slave_image =NULL ; +struct vme_resource *resource =NULL ; + + bridge =dev_to_bridge (dev); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); + goto err_bus; + } + +/* Loop through slave resources */ +list_for_each (slave_pos, &(bridge->slave_resources)) { + slave_image =list_entry (slave_pos, + struct vme_slave_resource, list); + +if (slave_image ==NULL ) { +printk ("Registered NULL Slave resource\n" ); +continue ; + } + +/* Find an unlocked and compatible image */ +down (&(slave_image->sem)); +if (((slave_image->address_attr & address) == address) && + ((slave_image->cycle_attr & cycle) == cycle) && + (slave_image->locked ==0 )) { + + slave_image->locked =1 ; +up (&(slave_image->sem)); + allocated_image = slave_image; +break ; + } +up (&(slave_image->sem)); + } + +/* No free image */ +if (allocated_image ==NULL ) + goto err_image; + + resource =kmalloc (sizeof (struct vme_resource ),GFP_KERNEL ); +if (resource ==NULL ) { +printk (KERN_WARNING "Unable to allocate resource structure\n" ); + goto err_alloc; + } + resource->type =VME_SLAVE ; + resource->entry = &(allocated_image->list); + +return resource; + +err_alloc: +/* Unlock image */ +down (&(slave_image->sem)); + slave_image->locked =0 ; +up (&(slave_image->sem)); +err_image: +err_bus: +return NULL ; +} +EXPORT_SYMBOL (vme_slave_request); + +int vme_slave_set (struct vme_resource *resource,int enabled, +unsigned long long vme_base,unsigned long long size, +dma_addr_t buf_base,vme_address_t aspace,vme_cycle_t cycle) +{ +struct vme_bridge *bridge =find_bridge (resource); +struct vme_slave_resource *image; +int retval; + +if (resource->type !=VME_SLAVE ) { +printk ("Not a slave resource\n" ); +return -EINVAL ; + } + + image = list_entry(resource->entry, struct vme_slave_resource, list); + +if (bridge->slave_set ==NULL ) { +printk ("Function not supported\n" ); +return -ENOSYS ; + } + +if (!(((image->address_attr & aspace) == aspace) && + ((image->cycle_attr & cycle) == cycle))) { +printk ("Invalid attributes\n" ); +return -EINVAL ; + } + + retval =vme_check_window (aspace, vme_base, size); +if (retval) +return retval; + +return bridge->slave_set (image, enabled, vme_base, size, buf_base, + aspace, cycle); +} +EXPORT_SYMBOL (vme_slave_set); + +int vme_slave_get (struct vme_resource *resource,int *enabled, +unsigned long long *vme_base,unsigned long long *size, +dma_addr_t *buf_base,vme_address_t *aspace,vme_cycle_t *cycle) +{ +struct vme_bridge *bridge =find_bridge (resource); +struct vme_slave_resource *image; + +if (resource->type !=VME_SLAVE ) { +printk ("Not a slave resource\n" ); +return -EINVAL ; + } + + image = list_entry(resource->entry, struct vme_slave_resource, list); + +if (bridge->slave_set ==NULL ) { +printk ("vme_slave_get not supported\n" ); +return -EINVAL ; + } + +return bridge->slave_get (image, enabled, vme_base, size, buf_base, + aspace, cycle); +} +EXPORT_SYMBOL (vme_slave_get); + +void vme_slave_free (struct vme_resource *resource) +{ +struct vme_slave_resource *slave_image; + +if (resource->type !=VME_SLAVE ) { +printk ("Not a slave resource\n" ); +return ; + } + + slave_image = list_entry(resource->entry, struct vme_slave_resource, + list); +if (slave_image ==NULL ) { +printk ("Can't find slave resource\n" ); +return ; + } + +/* Unlock image */ +down (&(slave_image->sem)); +if (slave_image->locked ==0 ) +printk (KERN_ERR "Image is already free\n" ); + + slave_image->locked =0 ; +up (&(slave_image->sem)); + +/* Free up resource memory */ +kfree (resource); +} +EXPORT_SYMBOL (vme_slave_free); + +/* +* Request a master image with specific attributes, return some unique +* identifier. +*/ +struct vme_resource *vme_master_request (struct device *dev, +vme_address_t address,vme_cycle_t cycle,vme_width_t dwidth) +{ +struct vme_bridge *bridge; +struct list_head *master_pos =NULL ; +struct vme_master_resource *allocated_image =NULL ; +struct vme_master_resource *master_image =NULL ; +struct vme_resource *resource =NULL ; + + bridge =dev_to_bridge (dev); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); + goto err_bus; + } + +/* Loop through master resources */ +list_for_each (master_pos, &(bridge->master_resources)) { + master_image =list_entry (master_pos, + struct vme_master_resource, list); + +if (master_image ==NULL ) { +printk (KERN_WARNING "Registered NULL master resource\n" ); +continue ; + } + +/* Find an unlocked and compatible image */ +spin_lock (&(master_image->lock)); +if (((master_image->address_attr & address) == address) && + ((master_image->cycle_attr & cycle) == cycle) && + ((master_image->width_attr & dwidth) == dwidth) && + (master_image->locked ==0 )) { + + master_image->locked =1 ; +spin_unlock (&(master_image->lock)); + allocated_image = master_image; +break ; + } +spin_unlock (&(master_image->lock)); + } + +/* Check to see if we found a resource */ +if (allocated_image ==NULL ) { +printk (KERN_ERR "Can't find a suitable resource\n" ); + goto err_image; + } + + resource =kmalloc (sizeof (struct vme_resource ),GFP_KERNEL ); +if (resource ==NULL ) { +printk (KERN_ERR "Unable to allocate resource structure\n" ); + goto err_alloc; + } + resource->type =VME_MASTER ; + resource->entry = &(allocated_image->list); + +return resource; + +kfree (resource); +err_alloc: +/* Unlock image */ +spin_lock (&(master_image->lock)); + master_image->locked =0 ; +spin_unlock (&(master_image->lock)); +err_image: +err_bus: +return NULL ; +} +EXPORT_SYMBOL (vme_master_request); + +int vme_master_set (struct vme_resource *resource,int enabled, +unsigned long long vme_base,unsigned long long size, +vme_address_t aspace,vme_cycle_t cycle,vme_width_t dwidth) +{ +struct vme_bridge *bridge =find_bridge (resource); +struct vme_master_resource *image; +int retval; + +if (resource->type !=VME_MASTER ) { +printk ("Not a master resource\n" ); +return -EINVAL ; + } + + image = list_entry(resource->entry, struct vme_master_resource, list); + +if (bridge->master_set ==NULL ) { +printk ("vme_master_set not supported\n" ); +return -EINVAL ; + } + +if (!(((image->address_attr & aspace) == aspace) && + ((image->cycle_attr & cycle) == cycle) && + ((image->width_attr & dwidth) == dwidth))) { +printk ("Invalid attributes\n" ); +return -EINVAL ; + } + + retval =vme_check_window (aspace, vme_base, size); +if (retval) +return retval; + +return bridge->master_set (image, enabled, vme_base, size, aspace, + cycle, dwidth); +} +EXPORT_SYMBOL (vme_master_set); + +int vme_master_get (struct vme_resource *resource,int *enabled, +unsigned long long *vme_base,unsigned long long *size, +vme_address_t *aspace,vme_cycle_t *cycle,vme_width_t *dwidth) +{ +struct vme_bridge *bridge =find_bridge (resource); +struct vme_master_resource *image; + +if (resource->type !=VME_MASTER ) { +printk ("Not a master resource\n" ); +return -EINVAL ; + } + + image = list_entry(resource->entry, struct vme_master_resource, list); + +if (bridge->master_set ==NULL ) { +printk ("vme_master_set not supported\n" ); +return -EINVAL ; + } + +return bridge->master_get (image, enabled, vme_base, size, aspace, + cycle, dwidth); +} +EXPORT_SYMBOL (vme_master_get); + +/* +* Read data out of VME space into a buffer. +*/ +ssize_t vme_master_read (struct vme_resource *resource,void *buf,size_t count, +loff_t offset) +{ +struct vme_bridge *bridge =find_bridge (resource); +struct vme_master_resource *image; +size_t length; + +if (bridge->master_read ==NULL ) { +printk ("Reading from resource not supported\n" ); +return -EINVAL ; + } + +if (resource->type !=VME_MASTER ) { +printk ("Not a master resource\n" ); +return -EINVAL ; + } + + image = list_entry(resource->entry, struct vme_master_resource, list); + + length =vme_get_size (resource); + +if (offset > length) { +printk ("Invalid Offset\n" ); +return -EFAULT ; + } + +if ((offset + count) > length) + count = length - offset; + +return bridge->master_read (image, buf, count, offset); + +} +EXPORT_SYMBOL (vme_master_read); + +/* +* Write data out to VME space from a buffer. +*/ +ssize_t vme_master_write (struct vme_resource *resource,void *buf, +size_t count,loff_t offset) +{ +struct vme_bridge *bridge =find_bridge (resource); +struct vme_master_resource *image; +size_t length; + +if (bridge->master_write ==NULL ) { +printk ("Writing to resource not supported\n" ); +return -EINVAL ; + } + +if (resource->type !=VME_MASTER ) { +printk ("Not a master resource\n" ); +return -EINVAL ; + } + + image = list_entry(resource->entry, struct vme_master_resource, list); + + length =vme_get_size (resource); + +if (offset > length) { +printk ("Invalid Offset\n" ); +return -EFAULT ; + } + +if ((offset + count) > length) + count = length - offset; + +return bridge->master_write (image, buf, count, offset); +} +EXPORT_SYMBOL (vme_master_write); + +/* +* Perform RMW cycle to provided location. +*/ +unsigned int vme_master_rmw (struct vme_resource *resource,unsigned int mask, +unsigned int compare,unsigned int swap,loff_t offset) +{ +struct vme_bridge *bridge =find_bridge (resource); +struct vme_master_resource *image; + +if (bridge->master_rmw ==NULL ) { +printk ("Writing to resource not supported\n" ); +return -EINVAL ; + } + +if (resource->type !=VME_MASTER ) { +printk ("Not a master resource\n" ); +return -EINVAL ; + } + + image = list_entry(resource->entry, struct vme_master_resource, list); + +return bridge->master_rmw (image, mask, compare, swap, offset); +} +EXPORT_SYMBOL (vme_master_rmw); + +void vme_master_free (struct vme_resource *resource) +{ +struct vme_master_resource *master_image; + +if (resource->type !=VME_MASTER ) { +printk ("Not a master resource\n" ); +return ; + } + + master_image = list_entry(resource->entry, struct vme_master_resource, + list); +if (master_image ==NULL ) { +printk ("Can't find master resource\n" ); +return ; + } + +/* Unlock image */ +spin_lock (&(master_image->lock)); +if (master_image->locked ==0 ) +printk (KERN_ERR "Image is already free\n" ); + + master_image->locked =0 ; +spin_unlock (&(master_image->lock)); + +/* Free up resource memory */ +kfree (resource); +} +EXPORT_SYMBOL (vme_master_free); + +/* +* Request a DMA controller with specific attributes, return some unique +* identifier. +*/ +struct vme_resource *vme_request_dma (struct device *dev) +{ +struct vme_bridge *bridge; +struct list_head *dma_pos =NULL ; +struct vme_dma_resource *allocated_ctrlr =NULL ; +struct vme_dma_resource *dma_ctrlr =NULL ; +struct vme_resource *resource =NULL ; + +/* XXX Not checking resource attributes */ +printk (KERN_ERR "No VME resource Attribute tests done\n" ); + + bridge =dev_to_bridge (dev); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); + goto err_bus; + } + +/* Loop through DMA resources */ +list_for_each (dma_pos, &(bridge->dma_resources)) { + dma_ctrlr =list_entry (dma_pos, + struct vme_dma_resource, list); + +if (dma_ctrlr ==NULL ) { +printk ("Registered NULL DMA resource\n" ); +continue ; + } + +/* Find an unlocked controller */ +down (&(dma_ctrlr->sem)); +if (dma_ctrlr->locked ==0 ) { + dma_ctrlr->locked =1 ; +up (&(dma_ctrlr->sem)); + allocated_ctrlr = dma_ctrlr; +break ; + } +up (&(dma_ctrlr->sem)); + } + +/* Check to see if we found a resource */ +if (allocated_ctrlr ==NULL ) + goto err_ctrlr; + + resource =kmalloc (sizeof (struct vme_resource ),GFP_KERNEL ); +if (resource ==NULL ) { +printk (KERN_WARNING "Unable to allocate resource structure\n" ); + goto err_alloc; + } + resource->type =VME_DMA ; + resource->entry = &(allocated_ctrlr->list); + +return resource; + +err_alloc: +/* Unlock image */ +down (&(dma_ctrlr->sem)); + dma_ctrlr->locked =0 ; +up (&(dma_ctrlr->sem)); +err_ctrlr: +err_bus: +return NULL ; +} +EXPORT_SYMBOL (vme_request_dma); + +/* +* Start new list +*/ +struct vme_dma_list *vme_new_dma_list (struct vme_resource *resource) +{ +struct vme_dma_resource *ctrlr; +struct vme_dma_list *dma_list; + +if (resource->type !=VME_DMA ) { +printk ("Not a DMA resource\n" ); +return NULL ; + } + + ctrlr = list_entry(resource->entry, struct vme_dma_resource, list); + + dma_list = (struct vme_dma_list *)kmalloc ( +sizeof (struct vme_dma_list ),GFP_KERNEL ); +if (dma_list ==NULL ) { +printk ("Unable to allocate memory for new dma list\n" ); +return NULL ; + } +INIT_LIST_HEAD (&(dma_list->entries)); + dma_list->parent = ctrlr; +init_MUTEX (&(dma_list->sem)); + +return dma_list; +} +EXPORT_SYMBOL (vme_new_dma_list); + +/* +* Create "Pattern" type attributes +*/ +struct vme_dma_attr *vme_dma_pattern_attribute (u32 pattern, +vme_pattern_t type) +{ +struct vme_dma_attr *attributes; +struct vme_dma_pattern *pattern_attr; + + attributes = (struct vme_dma_attr *)kmalloc ( +sizeof (struct vme_dma_attr ),GFP_KERNEL ); +if (attributes ==NULL ) { +printk ("Unable to allocate memory for attributes structure\n" ); + goto err_attr; + } + + pattern_attr = (struct vme_dma_pattern *)kmalloc ( +sizeof (struct vme_dma_pattern ),GFP_KERNEL ); +if (pattern_attr ==NULL ) { +printk ("Unable to allocate memory for pattern attributes\n" ); + goto err_pat; + } + + attributes->type =VME_DMA_PATTERN ; + attributes->private = (void *)pattern_attr; + + pattern_attr->pattern = pattern; + pattern_attr->type = type; + +return attributes; + +kfree (pattern_attr); +err_pat: +kfree (attributes); +err_attr: +return NULL ; +} +EXPORT_SYMBOL (vme_dma_pattern_attribute); + +/* +* Create "PCI" type attributes +*/ +struct vme_dma_attr *vme_dma_pci_attribute (dma_addr_t address) +{ +struct vme_dma_attr *attributes; +struct vme_dma_pci *pci_attr; + +/* XXX Run some sanity checks here */ + + attributes = (struct vme_dma_attr *)kmalloc ( +sizeof (struct vme_dma_attr ),GFP_KERNEL ); +if (attributes ==NULL ) { +printk ("Unable to allocate memory for attributes structure\n" ); + goto err_attr; + } + + pci_attr = (struct vme_dma_pci *)kmalloc (sizeof (struct vme_dma_pci ), +GFP_KERNEL ); +if (pci_attr ==NULL ) { +printk ("Unable to allocate memory for pci attributes\n" ); + goto err_pci; + } + + + + attributes->type =VME_DMA_PCI ; + attributes->private = (void *)pci_attr; + + pci_attr->address = address; + +return attributes; + +kfree (pci_attr); +err_pci: +kfree (attributes); +err_attr: +return NULL ; +} +EXPORT_SYMBOL (vme_dma_pci_attribute); + +/* +* Create "VME" type attributes +*/ +struct vme_dma_attr *vme_dma_vme_attribute (unsigned long long address, +vme_address_t aspace,vme_cycle_t cycle,vme_width_t dwidth) +{ +struct vme_dma_attr *attributes; +struct vme_dma_vme *vme_attr; + +/* XXX Run some sanity checks here */ + + attributes = (struct vme_dma_attr *)kmalloc ( +sizeof (struct vme_dma_attr ),GFP_KERNEL ); +if (attributes ==NULL ) { +printk ("Unable to allocate memory for attributes structure\n" ); + goto err_attr; + } + + vme_attr = (struct vme_dma_vme *)kmalloc (sizeof (struct vme_dma_vme ), +GFP_KERNEL ); +if (vme_attr ==NULL ) { +printk ("Unable to allocate memory for vme attributes\n" ); + goto err_vme; + } + + attributes->type =VME_DMA_VME ; + attributes->private = (void *)vme_attr; + + vme_attr->address = address; + vme_attr->aspace = aspace; + vme_attr->cycle = cycle; + vme_attr->dwidth = dwidth; + +return attributes; + +kfree (vme_attr); +err_vme: +kfree (attributes); +err_attr: +return NULL ; +} +EXPORT_SYMBOL (vme_dma_vme_attribute); + +/* +* Free attribute +*/ +void vme_dma_free_attribute (struct vme_dma_attr *attributes) +{ +kfree (attributes->private); +kfree (attributes); +} +EXPORT_SYMBOL (vme_dma_free_attribute); + +int vme_dma_list_add (struct vme_dma_list *list,struct vme_dma_attr *src, +struct vme_dma_attr *dest,size_t count) +{ +struct vme_bridge *bridge = list->parent->parent; +int retval; + +if (bridge->dma_list_add ==NULL ) { +printk ("Link List DMA generation not supported\n" ); +return -EINVAL ; + } + +if (down_trylock (&(list->sem))) { +printk ("Link List already submitted\n" ); +return -EINVAL ; + } + + retval = bridge->dma_list_add (list, src, dest, count); + +up (&(list->sem)); + +return retval; +} +EXPORT_SYMBOL (vme_dma_list_add); + +int vme_dma_list_exec (struct vme_dma_list *list) +{ +struct vme_bridge *bridge = list->parent->parent; +int retval; + +if (bridge->dma_list_exec ==NULL ) { +printk ("Link List DMA execution not supported\n" ); +return -EINVAL ; + } + +down (&(list->sem)); + + retval = bridge->dma_list_exec (list); + +up (&(list->sem)); + +return retval; +} +EXPORT_SYMBOL (vme_dma_list_exec); + +int vme_dma_list_free (struct vme_dma_list *list) +{ +struct vme_bridge *bridge = list->parent->parent; +int retval; + +if (bridge->dma_list_empty ==NULL ) { +printk ("Emptying of Link Lists not supported\n" ); +return -EINVAL ; + } + +if (down_trylock (&(list->sem))) { +printk ("Link List in use\n" ); +return -EINVAL ; + } + +/* +* Empty out all of the entries from the dma list. We need to go to the +* low level driver as dma entries are driver specific. +*/ + retval = bridge->dma_list_empty (list); +if (retval) { +printk ("Unable to empty link-list entries\n" ); +up (&(list->sem)); +return retval; + } +up (&(list->sem)); +kfree (list); + +return retval; +} +EXPORT_SYMBOL (vme_dma_list_free); + +int vme_dma_free (struct vme_resource *resource) +{ +struct vme_dma_resource *ctrlr; + +if (resource->type !=VME_DMA ) { +printk ("Not a DMA resource\n" ); +return -EINVAL ; + } + + ctrlr = list_entry(resource->entry, struct vme_dma_resource, list); + +if (down_trylock (&(ctrlr->sem))) { +printk ("Resource busy, can't free\n" ); +return -EBUSY ; + } + +if (!(list_empty (&(ctrlr->pending)) &&list_empty (&(ctrlr->running)))) { +printk ("Resource still processing transfers\n" ); +up (&(ctrlr->sem)); +return -EBUSY ; + } + + ctrlr->locked =0 ; + +up (&(ctrlr->sem)); + +return 0 ; +} +EXPORT_SYMBOL (vme_dma_free); + +int vme_request_irq (struct device *dev,int level,int statid, +void (*callback)(int level,int vector,void *priv_data), +void *priv_data) +{ +struct vme_bridge *bridge; + + bridge =dev_to_bridge (dev); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); +return -EINVAL ; + } + +if ((level <1 ) || (level >7 )) { +printk (KERN_WARNING "Invalid interrupt level\n" ); +return -EINVAL ; + } + +if (bridge->request_irq ==NULL ) { +printk ("Registering interrupts not supported\n" ); +return -EINVAL ; + } + +return bridge->request_irq (level, statid, callback, priv_data); +} +EXPORT_SYMBOL (vme_request_irq); + +void vme_free_irq (struct device *dev,int level,int statid) +{ +struct vme_bridge *bridge; + + bridge =dev_to_bridge (dev); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); +return ; + } + +if ((level <1 ) || (level >7 )) { +printk (KERN_WARNING "Invalid interrupt level\n" ); +return ; + } + +if (bridge->free_irq ==NULL ) { +printk ("Freeing interrupts not supported\n" ); +return ; + } + + bridge->free_irq (level, statid); +} +EXPORT_SYMBOL (vme_free_irq); + +int vme_generate_irq (struct device *dev,int level,int statid) +{ +struct vme_bridge *bridge; + + bridge =dev_to_bridge (dev); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); +return -EINVAL ; + } + +if ((level <1 ) || (level >7 )) { +printk (KERN_WARNING "Invalid interrupt level\n" ); +return -EINVAL ; + } + +if (bridge->generate_irq ==NULL ) { +printk ("Interrupt generation not supported\n" ); +return -EINVAL ; + } + +return bridge->generate_irq (level, statid); +} +EXPORT_SYMBOL (vme_generate_irq); + +int vme_lm_set (struct device *dev,unsigned long long lm_base,vme_address_t aspace, +vme_cycle_t cycle) +{ +struct vme_bridge *bridge; + + bridge =dev_to_bridge (dev); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); +return -EINVAL ; + } + +if (bridge->lm_set ==NULL ) { +printk ("vme_lm_set not supported\n" ); +return -EINVAL ; + } + +return bridge->lm_set (lm_base, aspace, cycle); +} +EXPORT_SYMBOL (vme_lm_set); + +int vme_lm_get (struct device *dev,unsigned long long *lm_base,vme_address_t *aspace, +vme_cycle_t *cycle) +{ +struct vme_bridge *bridge; + + bridge =dev_to_bridge (dev); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); +return -EINVAL ; + } + +if (bridge->lm_get ==NULL ) { +printk ("vme_lm_get not supported\n" ); +return -EINVAL ; + } + +return bridge->lm_get (lm_base, aspace, cycle); +} +EXPORT_SYMBOL (vme_lm_get); + +int vme_lm_attach (struct device *dev,int monitor,void (*callback)(int )) +{ +struct vme_bridge *bridge; + + bridge =dev_to_bridge (dev); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); +return -EINVAL ; + } + +if (bridge->lm_attach ==NULL ) { +printk ("vme_lm_attach not supported\n" ); +return -EINVAL ; + } + +return bridge->lm_attach (monitor, callback); +} +EXPORT_SYMBOL (vme_lm_attach); + +int vme_lm_detach (struct device *dev,int monitor) +{ +struct vme_bridge *bridge; + + bridge =dev_to_bridge (dev); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); +return -EINVAL ; + } + +if (bridge->lm_detach ==NULL ) { +printk ("vme_lm_detach not supported\n" ); +return -EINVAL ; + } + +return bridge->lm_detach (monitor); +} +EXPORT_SYMBOL (vme_lm_detach); + +int vme_slot_get (struct device *bus) +{ +struct vme_bridge *bridge; + + bridge =dev_to_bridge (bus); +if (bridge ==NULL ) { +printk (KERN_ERR "Can't find VME bus\n" ); +return -EINVAL ; + } + +if (bridge->slot_get ==NULL ) { +printk ("vme_slot_get not supported\n" ); +return -EINVAL ; + } + +return bridge->slot_get (); +} +EXPORT_SYMBOL (vme_slot_get); + + +/* - Bridge Registration --------------------------------------------------- */ + +static int vme_alloc_bus_num (void ) +{ +int i; + +down (&vme_bus_num_sem); +for (i =0 ; i <sizeof (vme_bus_numbers) *8 ; i++) { +if (((vme_bus_numbers >> i) &0x1 ) ==0 ) { + vme_bus_numbers |= (0x1 << i); +break ; + } + } +up (&vme_bus_num_sem); + +return i; +} + +static void vme_free_bus_num (int bus) +{ +down (&vme_bus_num_sem); + vme_bus_numbers |= ~(0x1 << bus); +up (&vme_bus_num_sem); +} + +int vme_register_bridge (struct vme_bridge *bridge) +{ +struct device *dev; +int retval; +int i; + + bridge->num =vme_alloc_bus_num (); + +/* This creates 32 vme "slot" devices. This equates to a slot for each +* ID available in a system conforming to the ANSI/VITA 1-1994 +* specification. +*/ +for (i =0 ; i <VME_SLOTS_MAX ; i++) { + dev = &(bridge->dev[i]); +memset (dev,0 ,sizeof (struct device )); + + dev->parent = bridge->parent; + dev->bus = &(vme_bus_type); +/* +* We save a pointer to the bridge in platform_data so that we +* can get to it later. We keep driver_data for use by the +* driver that binds against the slot +*/ + dev->platform_data = bridge; +dev_set_name (dev,"vme-%x.%x" , bridge->num, i +1 ); + + retval =device_register (dev); +if (retval) + goto err_reg; + } + +return retval; + + i =VME_SLOTS_MAX ; +err_reg: +while (i >-1 ) { + dev = &(bridge->dev[i]); +device_unregister (dev); + } +vme_free_bus_num (bridge->num); +return retval; +} +EXPORT_SYMBOL (vme_register_bridge); + +void vme_unregister_bridge (struct vme_bridge *bridge) +{ +int i; +struct device *dev; + + +for (i =0 ; i <VME_SLOTS_MAX ; i++) { + dev = &(bridge->dev[i]); +device_unregister (dev); + } +vme_free_bus_num (bridge->num); +} +EXPORT_SYMBOL (vme_unregister_bridge); + + +/* - Driver Registration --------------------------------------------------- */ + +int vme_register_driver (struct vme_driver *drv) +{ + drv->driver.name = drv->name; + drv->driver.bus = &vme_bus_type; + +return driver_register (&drv->driver); +} +EXPORT_SYMBOL (vme_register_driver); + +void vme_unregister_driver (struct vme_driver *drv) +{ +driver_unregister (&drv->driver); +} +EXPORT_SYMBOL (vme_unregister_driver); + +/* - Bus Registration ------------------------------------------------------ */ + +int vme_calc_slot (struct device *dev) +{ +struct vme_bridge *bridge; +int num; + + bridge =dev_to_bridge (dev); + +/* Determine slot number */ + num =0 ; +while (num <VME_SLOTS_MAX ) { +if (&(bridge->dev[num]) == dev) { +break ; + } + num++; + } +if (num ==VME_SLOTS_MAX ) { +dev_err (dev,"Failed to identify slot\n" ); + num =0 ; + goto err_dev; + } + num++; + +err_dev: +return num; +} + +static struct vme_driver *dev_to_vme_driver (struct device *dev) +{ +if (dev->driver ==NULL ) +printk ("Bugger dev->driver is NULL\n" ); + + return container_of(dev->driver, struct vme_driver, driver); +} + +static int vme_bus_match (struct device *dev,struct device_driver *drv) +{ +struct vme_bridge *bridge; +struct vme_driver *driver; +int i, num; + + bridge =dev_to_bridge (dev); + driver = container_of(drv, struct vme_driver, driver); + + num =vme_calc_slot (dev); +if (!num) + goto err_dev; + +if (driver->bind_table ==NULL ) { +dev_err (dev,"Bind table NULL\n" ); + goto err_table; + } + + i =0 ; +while ((driver->bind_table[i].bus !=0 ) || + (driver->bind_table[i].slot !=0 )) { + +if ((bridge->num == driver->bind_table[i].bus) && + (num == driver->bind_table[i].slot)) +return 1 ; + i++; + } + +err_dev: +err_table: +return 0 ; +} + +static int vme_bus_probe (struct device *dev) +{ +struct vme_bridge *bridge; +struct vme_driver *driver; +int retval = -ENODEV ; + + driver =dev_to_vme_driver (dev); + bridge =dev_to_bridge (dev); + +if (driver->probe !=NULL ) { + retval = driver->probe (dev, bridge->num,vme_calc_slot (dev)); + } + +return retval; +} + +static int vme_bus_remove (struct device *dev) +{ +struct vme_bridge *bridge; +struct vme_driver *driver; +int retval = -ENODEV ; + + driver =dev_to_vme_driver (dev); + bridge =dev_to_bridge (dev); + +if (driver->remove !=NULL ) { + retval = driver->remove (dev, bridge->num,vme_calc_slot (dev)); + } + +return retval; +} + +struct bus_type vme_bus_type = { + .name ="vme" , + .match = vme_bus_match, + .probe = vme_bus_probe, + .remove = vme_bus_remove, +}; +EXPORT_SYMBOL (vme_bus_type); + +static int __init vme_init (void ) +{ +return bus_register (&vme_bus_type); +} + +static void __exit vme_exit (void) +{ +bus_unregister (&vme_bus_type); +} + +MODULE_DESCRIPTION ("VME bridge driver framework" ); +MODULE_AUTHOR ("Martyn Welch <[email protected]" ); +MODULE_LICENSE ("GPL" ); + +module_init (vme_init); +module_exit (vme_exit);drivers/staging/vme/vme.h new file +153
-0@@ -0,0 +1,153 @@ +#ifndef _VME_H_ +#define _VME_H_ + +/* Resource Type */ +enum vme_resource_type { +VME_MASTER , +VME_SLAVE , +VME_DMA +}; + +/* VME Address Spaces */ +typedef u32 vme_address_t ; +#define VME_A16 0x1 +#define VME_A24 0x2 +#define VME_A32 0x4 +#define VME_A64 0x8 +#define VME_CRCSR 0x10 +#define VME_USER1 0x20 +#define VME_USER2 0x40 +#define VME_USER3 0x80 +#define VME_USER4 0x100 + +#define VME_A16_MAX 0x10000ULL +#define VME_A24_MAX 0x1000000ULL +#define VME_A32_MAX 0x100000000ULL +#define VME_A64_MAX 0x10000000000000000ULL +#define VME_CRCSR_MAX 0x1000000ULL + + +/* VME Cycle Types */ +typedef u32 vme_cycle_t ; +#define VME_SCT 0x1 +#define VME_BLT 0x2 +#define VME_MBLT 0x4 +#define VME_2eVME 0x8 +#define VME_2eSST 0x10 +#define VME_2eSSTB 0x20 + +#define VME_2eSST160 0x100 +#define VME_2eSST267 0x200 +#define VME_2eSST320 0x400 + +#define VME_SUPER 0x1000 +#define VME_USER 0x2000 +#define VME_PROG 0x4000 +#define VME_DATA 0x8000 + +/* VME Data Widths */ +typedef u32 vme_width_t ; +#define VME_D8 0x1 +#define VME_D16 0x2 +#define VME_D32 0x4 +#define VME_D64 0x8 + +/* Arbitration Scheduling Modes */ +typedef u32 vme_arbitration_t ; +#define VME_R_ROBIN_MODE 0x1 +#define VME_PRIORITY_MODE 0x2 + +typedef u32 vme_dma_t ; +#define VME_DMA_PATTERN (1<<0) +#define VME_DMA_PCI (1<<1) +#define VME_DMA_VME (1<<2) + +typedef u32 vme_pattern_t ; +#define VME_DMA_PATTERN_BYTE (1<<0) +#define VME_DMA_PATTERN_WORD (1<<1) +#define VME_DMA_PATTERN_INCREMENT (1<<2) + +struct vme_dma_attr { +vme_dma_t type; +void *private; +}; + +struct vme_resource { +enum vme_resource_type type; +struct list_head *entry; +}; + +extern struct bus_type vme_bus_type; + +struct vme_device_id { +int bus; +int slot; +}; + +struct vme_driver { +struct list_head node; +char *name; +const struct vme_device_id *bind_table; +int (*probe) (struct device *,int ,int ); +int (*remove) (struct device *,int ,int ); +void (*shutdown) (void ); +struct device_driver driver; +}; + +void *vme_alloc_consistent (struct vme_resource *,size_t ,dma_addr_t *); +void vme_free_consistent (struct vme_resource *,size_t ,void *, +dma_addr_t ); + +size_t vme_get_size (struct vme_resource *); + +struct vme_resource *vme_slave_request (struct device *,vme_address_t ,vme_cycle_t ); +int vme_slave_set (struct vme_resource *,int ,unsigned long long , +unsigned long long ,dma_addr_t ,vme_address_t ,vme_cycle_t ); +int vme_slave_get (struct vme_resource *,int *,unsigned long long *, +unsigned long long *,dma_addr_t *,vme_address_t *,vme_cycle_t *); +void vme_slave_free (struct vme_resource *); + +struct vme_resource *vme_master_request (struct device *,vme_address_t ,vme_cycle_t , +vme_width_t ); +int vme_master_set (struct vme_resource *,int ,unsigned long long , +unsigned long long ,vme_address_t ,vme_cycle_t ,vme_width_t ); +int vme_master_get (struct vme_resource *,int *,unsigned long long *, +unsigned long long *,vme_address_t *,vme_cycle_t *,vme_width_t *); +ssize_t vme_master_read (struct vme_resource *,void *,size_t ,loff_t ); +ssize_t vme_master_write (struct vme_resource *,void *,size_t ,loff_t ); +unsigned int vme_master_rmw (struct vme_resource *,unsigned int ,unsigned int , +unsigned int ,loff_t ); +void vme_master_free (struct vme_resource *); + +struct vme_resource *vme_request_dma (struct device *); +struct vme_dma_list *vme_new_dma_list (struct vme_resource *); +struct vme_dma_attr *vme_dma_pattern_attribute (u32 ,vme_pattern_t ); +struct vme_dma_attr *vme_dma_pci_attribute (dma_addr_t ); +struct vme_dma_attr *vme_dma_vme_attribute (unsigned long long ,vme_address_t , +vme_cycle_t ,vme_width_t ); +void vme_dma_free_attribute (struct vme_dma_attr *); +int vme_dma_list_add (struct vme_dma_list *,struct vme_dma_attr *, +struct vme_dma_attr *,size_t ); +int vme_dma_list_exec (struct vme_dma_list *); +int vme_dma_list_free (struct vme_dma_list *); +int vme_dma_free (struct vme_resource *); + +int vme_request_irq (struct device *,int ,int , +void (*callback)(int ,int ,void *),void *); +void vme_free_irq (struct device *,int ,int ); +int vme_generate_irq (struct device *,int ,int ); + +int vme_lm_set (struct device *,unsigned long long ,vme_address_t ,vme_cycle_t ); +int vme_lm_get (struct device *,unsigned long long *,vme_address_t *, +vme_cycle_t *); +int vme_lm_attach (struct device *,int ,void (*callback)(int )); +int vme_lm_detach (struct device *,int ); + +int vme_slot_get (struct device *); + +int vme_register_driver (struct vme_driver *); +void vme_unregister_driver (struct vme_driver *); + + +#endif /* _VME_H_ */ +
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Hi Emilio,
Quoted 4 lines · Emilio G. Cota
A few comments after a quick glance, will need to dig deeper though:
- semaphores? isn't it screaming for mutexes?
The semaphores are initialized in mutex mode.
Quoted 4 lines
- some function signatures (
get_whatever) pass too many pointers; it's rather ugly. Passing a pointer to an appropriate struct seems a better solution.
When implementing these functions it seemed like a simpler option to pass the parameters rather than defining yet another structure and using it for a single call. If the *_get functions are changed then the *_set functions should also be changed for consistency. I'm not overly bothered either way - if the consensus is that a structure would be better then we can go with a structure.
Quoted 5 lines
-
vme_alloc_consistentlooks pretty fishy; why don't you pass that responsibility to the specific master? There you obviously know if you're bridging over PCI or whatever. Or even better; why is this needed at all?
In the model I have chosen it is up to the VME device driver to create buffers rather than the VME bridge drivers. After all it is the device drivers for the specific hardware found on the VME bus that knows what it is going to do with these buffers. This method is provided as a helper. It ensures that a VME device driver (not to be confused with the VME bridge driver) is able to allocate a buffer suitable for utilizing the DMA engines found in the bridge or indeed to be mapped onto VME though a slave window without explicitly knowing which VME bridge is being used.
Master windows don't require a contiguous buffer, are you referring to something else when you say master?
Quoted 3 lines
- Please explain me what all the DMA functions do; are they meant to be used by master or slaves?
The TODO file (drivers/staging/vme/TODO) contains a description of the current API including the DMA functions, does that provide enough of a description?
I am not aware of a VME bridge where the DMA controllers are specifically linked to the master or slave windows, they operate independently of either though during DMA operation are acting as bus master. In the tsi-148 the DMA transfers can use PCI space both as a source and destination, so I guess you could use them to transfer data into the slave buffer. Other chipsets, such as the ca91c142 can't do this, the DMA engines can only transfer PCI-to-VME or VME-to_PCI.
Quoted 18 lines
Have a look at the interface for slaves we've got for our tsi148 driver, available at:
http://repo.or.cz/w/tsi148vmebridge.git/* API for new drivers */
extern int vme_request_irq(unsigned int, int (*)(void *),void *, const char *);extern int
vme_free_irq(unsigned int );
extern intvme_generate_interrupt(int, int, signed long); extern struct vme_mapping*find_vme_mapping_from_addr(unsigned); extern intvme_get_window_attr(struct vme_mapping *); extern intvme_create_window(struct vme_mapping *); extern intvme_destroy_window(int);
extern intvme_find_mapping(struct vme_mapping *, int); extern intvme_release_mapping(struct vme_mapping *, int); extern intvme_do_dma(struct vme_dma *);
extern intvme_bus_error_check(int);
Quoted 3 lines
That's pretty thin and it covers our slaves' needs. Do you see anything missing there?
This interface, especially struct vme_mapping seems to have been written solely for the tsi-148 bridge. The API I have defined aims to provide a consistent API across multiple VME bridges. Whilst this API clearly works for you with the tsi-148 I am unsure how suitable it will be for other bridge chips.
The API I have proposed is designed to support more than just the tsi148 chipset. Have you thought about how the above API will be supported on other VME bridges?
Quoted 8 lines
For masters there's no interface there because it was the master's driver who directly provided these calls to slaves. I had it in my to-do list to split that from the tsi148, in the same fashion as you've done with this work.
- Note above the interrupt handler; simply needs the cookie. Also, shouldn't your
vme_request_irq()just require the IRQ vector?
No. I believe that you are using the term IRQ vector where we would use the term status ID, the value which is returned from an IACK cycle. Your interrupt handling code assigns a single interrupt handler to all interrupt levels, purely using the interrupt vector/status ID to determine which interrupt handler will be used. This adds an artificial limitation and would not work in some instances that we have seen. Our framework provides the ability to attach an interrupt handler to each combination of IRQ level and Status ID/vector.
Quoted 8 lines
- I'd like to see the whole picture, or 'vertical slice', i.e. the bus interface + a master + a slave driver. How would the slave's driver get the addresses and sizes of the mappings, interrupt lines, etc. for each of the devices it controls? For the time being we quickly hacked an xml-based scheme to get this info upon installation, but it's clearly not suitable for mainline.
I have written a test driver for a very old slave card we had lying around. In that case we used module parameters to determine the location of the device on the bus (it used a fixed VME address space). In this instance the interrupt level and ID could be configured in the registers available in the VME address space, hence I added module parameters to allow these to be configured. In this respect configuration of VME devices is very similar to ISA devices - neither of the buses has supported discovery mechanism from the outset and thus old cards. I therefore implemented a mechanism similar to how I believe ISA approaches this.
The framework that I have proposed aims to provide a consistent API to manage allowing the resources provided by the VME bridges to be managed in as a consistent a manner as possible. I believe it is up to the device drivers for the devices found on the VME bus to determine the best way to configure this as it is not provided by the VME specifications.
For using the VME bus for communication between two SBCs, I think we could use the CRCSR space to provide some information about the resources used on each board for say, a virtual network driver like the rapidIO subsystem provides. Possibly using the "device tree" stuff used on PowerPC, Blackfin and Sparc archs (I believe) for passing device layout between the firmware and kernel at boot.
Quoted 4 lines
Will probably have more comments when I get some time for further inspection.
Sure, np,
Martyn
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 4 lines · Martyn Welch
Quoted 1 line · Emilio G. Cota
- semaphores? isn't it screaming for mutexes?
The semaphores are initialized in mutex mode.
I know, but then please use mutexes.
Quoted 11 lines
Quoted 4 lines
- some function signatures (
get_whatever) pass too many pointers; it's rather ugly. Passing a pointer to an appropriate struct seems a better solution.When implementing these functions it seemed like a simpler option to pass the parameters rather than defining yet another structure and using it for a single call. If the
*_getfunctions are changed then the*_setfunctions should also be changed for consistency. I'm not overly bothered either way - if the consensus is that a structure would be better then we can go with a structure.
Yeh, In my opinion a structure there would be prettier. However let's put this off a bit, I'm currently re-working this a bit.
Quoted 6 lines
Quoted 5 lines
-
vme_alloc_consistentlooks pretty fishy; why don't you pass that responsibility to the specific master? There you obviously know if you're bridging over PCI or whatever. Or even better; why is this needed at all?
Quoted 9 lines
In the model I have chosen it is up to the VME device driver to create buffers rather than the VME bridge drivers. After all it is the device drivers for the specific hardware found on the VME bus that knows what it is going to do with these buffers. This method is provided as a helper. It ensures that a VME device driver (not to be confused with the VME bridge driver) is able to allocate a buffer suitable for utilizing the DMA engines found in the bridge or indeed to be mapped onto VME though a slave window without explicitly knowing which VME bridge is being used.
hmm I see. The way you provide coherent DMA mappings for VME is through providing coherent (consistent) mappings for PCI, and then mapping the VME space onto those. I haven't tested if this works, but sounds reasonable.
Quoted 2 lines
Master windows don't require a contiguous buffer, are you referring to something else when you say master?
I was thinking of the hypothetical case where you'd have a bridge not over PCI; but anyway since quite a few things seem tied to PCI this is fine.
Quoted 6 lines
Quoted 3 lines
- Please explain me what all the DMA functions do; are they meant to be used by master or slaves?
The TODO file (
drivers/staging/vme/TODO) contains a description of the current API including the DMA functions, does that provide enough of a description?
I've had a closer look; it seems to me that most of it is unnecessary; there's no show those lists to a driver. I'd just provide a single 'do_dma(attributes)' call that sleeps until it's done (or similar).
Quoted 7 lines
Quoted 7 lines
Have a look at the interface for slaves we've got for our tsi148 driver, available at:
http://repo.or.cz/w/tsi148vmebridge.git/* API for new drivers */
extern int vme_request_irq(unsigned int, int (*)(void *),void *, const char *);
[snip]
Quoted 12 lines
Quoted 5 lines
extern int
vme_bus_error_check(int);That's pretty thin and it covers our slaves' needs. Do you see anything missing there?
This interface, especially struct
vme_mappingseems to have been written solely for the tsi-148 bridge. The API I have defined aims to provide a consistent API across multiple VME bridges. Whilst this API clearly works for you with the tsi-148 I am unsure how suitable it will be for other bridge chips.
Quoted 3 lines
The API I have proposed is designed to support more than just the tsi148 chipset. Have you thought about how the above API will be supported on other VME bridges?
We only have access to tsi148 chips. Since apparently there aren't many bridges around, I think is saner to just provide an interface that works well with the devices we have access to (i.e. tsi148 and Tundra Universe in your case), and when new chips come along, we simply modify the interface as needed. Of course this doesn't mean we shouldn't try to make it generic, but within reason.
Quoted 9 lines
Quoted 8 lines
For masters there's no interface there because it was the master's driver who directly provided these calls to slaves. I had it in my to-do list to split that from the tsi148, in the same fashion as you've done with this work.
- Note above the interrupt handler; simply needs the cookie. Also, shouldn't your
vme_request_irq()just require the IRQ vector?
Quoted 8 lines
No. I believe that you are using the term IRQ vector where we would use the term status ID, the value which is returned from an IACK cycle. Your interrupt handling code assigns a single interrupt handler to all interrupt levels, purely using the interrupt vector/status ID to determine which interrupt handler will be used. This adds an artificial limitation and would not work in some instances that we have seen. Our framework provides the ability to attach an interrupt handler to each combination of IRQ level and Status ID/vector.
Fair enough. For sanity we tend not to share IRQ vectors among different modules, but yes, the interface should know about this.
Quoted 19 lines
Quoted 8 lines
- I'd like to see the whole picture, or 'vertical slice', i.e. the bus interface + a master + a slave driver. How would the slave's driver get the addresses and sizes of the mappings, interrupt lines, etc. for each of the devices it controls? For the time being we quickly hacked an xml-based scheme to get this info upon installation, but it's clearly not suitable for mainline.
I have written a test driver for a very old slave card we had lying around. In that case we used module parameters to determine the location of the device on the bus (it used a fixed VME address space). In this instance the interrupt level and ID could be configured in the registers available in the VME address space, hence I added module parameters to allow these to be configured. In this respect configuration of VME devices is very similar to ISA devices - neither of the buses has supported discovery mechanism from the outset and thus old cards. I therefore implemented a mechanism similar to how I believe ISA approaches this.
Quoted 6 lines
The framework that I have proposed aims to provide a consistent API to manage allowing the resources provided by the VME bridges to be managed in as a consistent a manner as possible. I believe it is up to the device drivers for the devices found on the VME bus to determine the best way to configure this as it is not provided by the VME specifications.
The problem is how to manage several (say 10) devices that have to be controlled with the same driver; passing parameters upon loading the driver's module doesn't seem to scale beyond one device..
At the moment I see no clean way of doing this; right now we're doing it through an IOCTL, copying from user-space a tree that describes the devices to install, namely mappings and IRQs for each of them.
Greg, any ideas on how to deal with this?
Quoted 6 lines
For using the VME bus for communication between two SBCs, I think we could use the CRCSR space to provide some information about the resources used on each board for say, a virtual network driver like the rapidIO subsystem provides. Possibly using the "device tree" stuff used on PowerPC, Blackfin and Sparc archs (I believe) for passing device layout between the firmware and kernel at boot.
hmm I don't see how we could implement this common consistent view of the bus in a way that could work with every bridge. For the time being I'd defer this and simply pass that responsibility to user-space.
Quoted 3 lines
Sure, np,
Martyn
Thanks,
E.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 3 lines · Emilio G. Cota
I've had a closer look; it seems to me that most of it is unnecessary; there's no show those lists to a driver. I'd just provide a single '
do_dma(attributes)' call that sleeps until it's done (or similar).
s/there's no show/there's no point in showing/
Basically the driver simply shouldn't care about these DMA lists. It just wants to get the work done, no matter how.
E.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 7 lines · Emilio G. Cota
Quoted 4 lines · Emilio G. Cota
I've had a closer look; it seems to me that most of it is unnecessary; there's no show those lists to a driver. I'd just provide a single '
do_dma(attributes)' call that sleeps until it's done (or similar).
Quoted 7 lines
s/there's no show/there's no point in showing/
Basically the driver simply shouldn't care about these DMA lists. It just wants to get the work done, no matter how.
E.
I have provided helper functions to aid with filling out structures that define the attributes, these are then pointed to by a generic DMA transfer structure. Bar the helper functions to aid in completing these commonly filled structures, at this level I can't see much difference. However rather than targeting a the "single shot" operation of the DMA controller I have provided the ability for the driver writer to use the link-list mode.
If the driver writer wants a "single shot" effect, there's little overhead in creating a one item link-list and executing it. However, without support for this mode the author is stuck with single shot transfers. If the author wants to perform scatter gather type transfers, without this functionality in the API - which is supported by all the bridges I have seen - he won't be able to easily avail himself of this functionality and get "work done".
Martyn
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 7 lines · Martyn Welch
If the driver writer wants a "single shot" effect, there's little overhead in creating a one item link-list and executing it. However, without support for this mode the author is stuck with single shot transfers. If the author wants to perform scatter gather type transfers, without this functionality in the API - which is supported by all the bridges I have seen - he won't be able to easily avail himself of this functionality and get "work done".
Again, he (the driver writer) should know *nothing* about that.
As for the scatter-gather case, what should happen is:
User: hey, there's a buffer I want to transfer. here's
a pointer and size
VME layer: ok, passing it to the bridge
VME bridge: I know I'm over PCI, so I'll get the pages
from memory and map the sg list over PCI, and then will tell the bridge to perform from there.
That's just a particular example, but for the user (read driver writer) it's much simpler this way. Doesn't need to know about lists or whatever.
E.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 5 lines · Emilio G. Cota
Quoted 2 lines · Martyn Welch
If the driver writer wants a "single shot" effect, there's little overhead in creating a one item link-list and executing it. However, without support for this mode the author is stuck with single shot transfers. If the author wants to perform scatter gather type transfers, without this functionality in the API - which is supported by all the bridges I have seen - he won't be able to easily avail himself of this functionality and get "work done".
Quoted 7 lines
Again, he (the driver writer) should know *nothing* about that.
As for the scatter-gather case, what should happen is:
User: hey, there's a buffer I want to transfer. here's
a pointer and size
Buffer where? On VME or PCI, where are you transferring it to? The API I have will allow scattered locations on VME to be copied to PCI and visa-versa, scattered locations on PCI to be copied to VME and visa-versa. Even scattered locations on VME to scattered locations on PCI with a bit of effort.
Quoted 9 lines
VME layer: ok, passing it to the bridge
VME bridge: I know I'm over PCI, so I'll get the pages
from memory and map the sg list over PCI, and then will tell the bridge to perform from there.That's just a particular example, but for the user (read driver writer) it's much simpler this way. Doesn't need to know about lists or whatever.
But seems to fail to provide the flexibility that I outlined above.
Martyn
Quoted 2 lines
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 5 lines · Martyn Welch
Quoted 4 lines · Emilio G. Cota
Again, he (the driver writer) should know *nothing* about that.
As for the scatter-gather case, what should happen is:
Quoted 7 lines
Quoted 2 lines
User: hey, there's a buffer I want to transfer. here's
a pointer and sizeBuffer where? On VME or PCI, where are you transferring it to? The API I have will allow scattered locations on VME to be copied to PCI and visa-versa, scattered locations on PCI to be copied to VME and visa-versa. Even scattered locations on VME to scattered locations on PCI with a bit of effort.
Admittedly our current tsi148 driver deals with the most common
situation: DMA from main (master) CPU RAM to/from a VME slave.
I'm not claiming we shouldn't support sg lists; however the implementation should resemble the existing sg interfaces. I'm currently working on something along those lines.
Thanks,
E.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
In reply to Emilio G. CotaQuoted 12 lines · Emilio G. Cota
Quoted 6 lines · Martyn Welch
Quoted 2 lines · Emilio G. Cota
- semaphores? isn't it screaming for mutexes?
The semaphores are initialized in mutex mode.
I know, but then please use mutexes.
I'm clearly missing something here - can you provide me with an example?
Quoted 26 lines
Quoted 7 lines
Quoted 5 lines
- some function signatures (
get_whatever) pass too many pointers; it's rather ugly. Passing a pointer to an appropriate struct seems a better solution.When implementing these functions it seemed like a simpler option to pass the parameters rather than defining yet another structure and using it for a single call. If the
*_getfunctions are changed then the*_setfunctions should also be changed for consistency. I'm not overly bothered either way - if the consensus is that a structure would be better then we can go with a structure.Yeh, In my opinion a structure there would be prettier. However let's put this off a bit, I'm currently re-working this a bit.
Quoted 8 lines
Quoted 6 lines
-
vme_alloc_consistentlooks pretty fishy; why don't you pass that responsibility to the specific master? There you obviously know if you're bridging over PCI or whatever. Or even better; why is this needed at all?In the model I have chosen it is up to the VME device driver to create buffers rather than the VME bridge drivers. After all it is the device drivers for the specific hardware found on the VME bus that knows what it is going to do with these buffers. This method is provided as a helper. It ensures that a VME device driver (not to be confused with the VME bridge driver) is able to allocate a buffer suitable for utilizing the DMA engines found in the bridge or indeed to be mapped onto VME though a slave window without explicitly knowing which VME bridge is being used.
hmm I see. The way you provide coherent DMA mappings for VME is through providing coherent (consistent) mappings for PCI, and then mapping the VME space onto those. I haven't tested if this works, but sounds reasonable.
Quoted 6 lines
Quoted 2 lines
Master windows don't require a contiguous buffer, are you referring to something else when you say master?
I was thinking of the hypothetical case where you'd have a bridge not over PCI; but anyway since quite a few things seem tied to PCI this is fine.
That's one reason for the helper function. If VME bridges are added which sit of on other buses then vme_alloc_consistent can be extended to support this without requiring VME device drivers to be altered to reflect the fact that pci_alloc_consistent might not work for all VME bridges.
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
- Please explain me what all the DMA functions do; are they meant to be used by master or slaves?
Quoted 8 lines
Quoted 2 lines
The TODO file (
drivers/staging/vme/TODO) contains a description of the current API including the DMA functions, does that provide enough of a description?I've had a closer look; it seems to me that most of it is unnecessary; there's no show those lists to a driver. I'd just provide a single '
do_dma(attributes)' call that sleeps until it's done (or similar).
The DMA controller in the tsi-148 can do PCI to PCI; PCI to VME; VME to PCI; VME to VME; Patterns to VME and Patterns to PCI transfers. How do you specify all those options without providing a structure where over 50% of the fields aren't used for any given transfer?
Every bridge I have seen is capable of link-list execution. The API provides the ability to do scatter-gather style DMA transfers, this could be used as part of a "zero-copy" type driver for high speed VME capture devices. How would you support the link-list execution with a single call?
I was also looking at the ability to queue DMA transfers if the controller was currently busy - if the consensus is that this is overkill I will happily remove this.
Quoted 21 lines
Quoted 8 lines
Quoted 8 lines
Have a look at the interface for slaves we've got for our tsi148 driver, available at:
http://repo.or.cz/w/tsi148vmebridge.git/* API for new drivers */
extern int vme_request_irq(unsigned int, int (*)(void *),void *, const char *);[snip]
Quoted 10 lines
Quoted 6 lines
extern int
vme_bus_error_check(int);That's pretty thin and it covers our slaves' needs. Do you see anything missing there?
This interface, especially struct
vme_mappingseems to have been written solely for the tsi-148 bridge. The API I have defined aims to provide a consistent API across multiple VME bridges. Whilst this API clearly works for you with the tsi-148 I am unsure how suitable it will be for other bridge chips.The API I have proposed is designed to support more than just the tsi148 chipset. Have you thought about how the above API will be supported on other VME bridges?
Quoted 8 lines
We only have access to tsi148 chips. Since apparently there aren't many bridges around, I think is saner to just provide an interface that works well with the devices we have access to (i.e. tsi148 and Tundra Universe in your case), and when new chips come along, we simply modify the interface as needed. Of course this doesn't mean we shouldn't try to make it generic, but within reason.
Agreed - which I believe is the approach I've taken.
Quoted 24 lines
Quoted 11 lines
Quoted 9 lines
For masters there's no interface there because it was the master's driver who directly provided these calls to slaves. I had it in my to-do list to split that from the tsi148, in the same fashion as you've done with this work.
- Note above the interrupt handler; simply needs the cookie. Also, shouldn't your
vme_request_irq()just require the IRQ vector?No. I believe that you are using the term IRQ vector where we would use the term status ID, the value which is returned from an IACK cycle. Your interrupt handling code assigns a single interrupt handler to all interrupt levels, purely using the interrupt vector/status ID to determine which interrupt handler will be used. This adds an artificial limitation and would not work in some instances that we have seen. Our framework provides the ability to attach an interrupt handler to each combination of IRQ level and Status ID/vector.
Fair enough. For sanity we tend not to share IRQ vectors among different modules, but yes, the interface should know about this.
Quoted 9 lines
Quoted 9 lines
- I'd like to see the whole picture, or 'vertical slice', i.e. the bus interface + a master + a slave driver. How would the slave's driver get the addresses and sizes of the mappings, interrupt lines, etc. for each of the devices it controls? For the time being we quickly hacked an xml-based scheme to get this info upon installation, but it's clearly not suitable for mainline.
Quoted 8 lines
Quoted 4 lines
I have written a test driver for a very old slave card we had lying around. In that case we used module parameters to determine the location of the device on the bus (it used a fixed VME address space). In this instance the interrupt level and ID could be configured in the registers available in the VME address space, hence I added module parameters to allow these to be configured. In this respect configuration of VME devices is very similar to ISA devices - neither of the buses has supported discovery mechanism from the outset and thus old cards. I therefore implemented a mechanism similar to how I believe ISA approaches this.
The framework that I have proposed aims to provide a consistent API to manage allowing the resources provided by the VME bridges to be managed in as a consistent a manner as possible. I believe it is up to the device drivers for the devices found on the VME bus to determine the best way to configure this as it is not provided by the VME specifications.
The problem is how to manage several (say 10) devices that have to be controlled with the same driver; passing parameters upon loading the driver's module doesn't seem to scale beyond one device..
I implemented it using param array, I agree that the arguments might get quite long with 10 devices, especially if multiple parameters are required, but I don't see why it shouldn't work.
Quoted 7 lines
At the moment I see no clean way of doing this; right now we're doing it through an IOCTL, copying from user-space a tree that describes the devices to install, namely mappings and IRQs for each of them.
Greg, any ideas on how to deal with this?
Quoted 8 lines
Quoted 2 lines
For using the VME bus for communication between two SBCs, I think we could use the CRCSR space to provide some information about the resources used on each board for say, a virtual network driver like the rapidIO subsystem provides. Possibly using the "device tree" stuff used on PowerPC, Blackfin and Sparc archs (I believe) for passing device layout between the firmware and kernel at boot.
hmm I don't see how we could implement this common consistent view of the bus in a way that could work with every bridge. For the time being I'd defer this and simply pass that responsibility to user-space.
That's the conclusion I sort of came to. I guess how the VME address space(s) are used is a system integration issue.
Quoted 8 lines
Quoted 4 lines
Sure, np,
Martyn
Thanks,
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 7 lines · Martyn Welch
Quoted 6 lines · Emilio G. Cota
Quoted 4 lines · Martyn Welch
Quoted 2 lines · Emilio G. Cota
- semaphores? isn't it screaming for mutexes?
Quoted 7 lines
Quoted 6 lines
Quoted 2 lines
The semaphores are initialized in mutex mode.
I know, but then please use mutexes.
I'm clearly missing something here - can you provide me with an example?
The kernel now has native mutexes so there is no need to use semaphores as mutexes. Take a look at:
kernel/mutex.c
--
Shawn
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
In reply to Martyn WelchQuoted 5 lines · Martyn Welch
That's one reason for the helper function. If VME bridges are added which sit of on other buses then
vme_alloc_consistentcan be extended to support this without requiring VME device drivers to be altered to reflect the fact thatpci_alloc_consistentmight not work for all VME bridges.
But it corresponds to the bridge's code (not to the generic layer) to know whether the bridge sits on top of PCI or not; hence my comment.
Quoted 4 lines
The DMA controller in the tsi-148 can do PCI to PCI; PCI to VME; VME to PCI; VME to VME; Patterns to VME and Patterns to PCI transfers. How do you specify all those options without providing a structure where over 50% of the fields aren't used for any given transfer?
I think a struct where not _every_ field is *always* used is fine. However requiring your drivers to know about what your bridge looks like is a mistake; just give them something thin that doesn't assume anything about what's behind it, and if it's not supported, return an appropriate error message.
Quoted 5 lines
Every bridge I have seen is capable of link-list execution. The API provides the ability to do scatter-gather style DMA transfers, this could be used as part of a "zero-copy" type driver for high speed VME capture devices. How would you support the link-list execution with a single call?
Let me say it again: "Drivers should know *nothing* about the underlying bridge".
They should work with *any* bridge; or do we care about the PCI bridge we're plug our PCI device on when writing a driver for it?
Quoted 3 lines
I was also looking at the ability to queue DMA transfers if the controller was currently busy - if the consensus is that this is overkill I will happily remove this.
AFAICT the tsi148 has several DMA channels; in our driver we just try to find a free channel to proceed with the transfer; if there aren't any then we return EBUSY.
Drawing an analogy with current 'streaming DMA' transfers, we're told we should check whether they failed or not, so I think trying to be too clever here it's not worth it.
Quoted 3 lines
I implemented it using param array, I agree that the arguments might get quite long with 10 devices, especially if multiple parameters are required, but I don't see why it shouldn't work.
The fact that 'it works' doesn't make it less ugly or messy. When referring to code, these two words usually mean "there's a better way"; so let's just find it.
E.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 9 lines · Emilio G. Cota
Quoted 2 lines · Martyn Welch
That's one reason for the helper function. If VME bridges are added which sit of on other buses then
vme_alloc_consistentcan be extended to support this without requiring VME device drivers to be altered to reflect the fact thatpci_alloc_consistentmight not work for all VME bridges.But it corresponds to the bridge's code (not to the generic layer) to know whether the bridge sits on top of PCI or not; hence my comment.
Quoted 9 lines
Quoted 2 lines
The DMA controller in the tsi-148 can do PCI to PCI; PCI to VME; VME to PCI; VME to VME; Patterns to VME and Patterns to PCI transfers. How do you specify all those options without providing a structure where over 50% of the fields aren't used for any given transfer?
I think a struct where not _every_ field is *always* used is fine. However requiring your drivers to know about what your bridge looks like is a mistake; just give them something thin that doesn't assume anything about what's behind it, and if it's not supported, return an appropriate error message.
That's what the API does - how does it require the driver writer to know about what the bridge look like. All that it requires is the the hardware supports the transfer mode that the driver requests, however that's always going to be an issue with multiple devices with different properties.
Quoted 9 lines
Quoted 2 lines
Every bridge I have seen is capable of link-list execution. The API provides the ability to do scatter-gather style DMA transfers, this could be used as part of a "zero-copy" type driver for high speed VME capture devices. How would you support the link-list execution with a single call?
Let me say it again: "Drivers should know *nothing* about the underlying bridge".
They should work with *any* bridge; or do we care about the PCI bridge we're plug our PCI device on when writing a driver for it?
Again: what part of the API I have defined forces the driver to know about the underlying bridge?
Quoted 7 lines
Quoted 2 lines
I was also looking at the ability to queue DMA transfers if the controller was currently busy - if the consensus is that this is overkill I will happily remove this.
AFAICT the tsi148 has several DMA channels; in our driver we just try to find a free channel to proceed with the transfer; if there aren't any then we return EBUSY.
Where as I request a DMA channel - a resource is returned, the driver does not need to know which one, he has the resource structure which refers to it. It treats the DMA engines as a resource, just as it treats the windows, both master and slave as resources.
Quoted 5 lines
Drawing an analogy with current 'streaming DMA' transfers, we're told we should check whether they failed or not, so I think trying to be too clever here it's not worth it.
Yes - my driver checks to see of the transfer has succeeded, I don't see quite what this has to do with the API. How's the API being clever?
Quoted 11 lines
Quoted 4 lines
I implemented it using param array, I agree that the arguments might get quite long with 10 devices, especially if multiple parameters are required, but I don't see why it shouldn't work.
The fact that 'it works' doesn't make it less ugly or messy. When referring to code, these two words usually mean "there's a better way"; so let's just find it.
E.
If there's a better way to do this then fine - I'm happy to use it, however I'm not quite sure how this relates to the API, core VME code or low-level drivers. I think it's up to the authors of the VME device drivers themselves how they approach this. The API uses the standard bind-table approach as best as I could. If you can think of a better approach to this, patches will be welcome.
Martyn
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 2 lines · Martyn Welch
Again: what part of the API I have defined forces the driver to know about the underlying bridge?
Let me answer with another question, maybe we get to understand each other this way:
Could you please explain me what happens when 17 different drivers want to control 17 different devices, each on a different slot, same address modifier, only 1MB per device? Apply if necessary to the tsi148 bridge.
NB1. Any given driver knows nothing about the other devices on the crate; each driver only knows about the address, am and size of the mapping for the device it controls--user-space provide this info on a per-driver basis.
NB2. The mapping offsets configured through the cards' pins match the information passed from user space to each of the cards.
Cheers,
E.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 5 lines · Emilio G. Cota
Quoted 2 lines · Martyn Welch
Again: what part of the API I have defined forces the driver to know about the underlying bridge?
Quoted 8 lines
Let me answer with another question, maybe we get to understand each other this way:
Could you please explain me what happens when 17 different drivers want to control 17 different devices, each on a different slot, same address modifier, only 1MB per device? Apply if necessary to the tsi148 bridge.
Not the same question, but I'd agree - that would probably break the current model I have proposed. *However*, providing a resource management layer as you have proposed above the basic resource management my API provides would resolve that without added complexity in the bridge drivers themselves.
Quoted 5 lines
NB1. Any given driver knows nothing about the other devices on the crate; each driver only knows about the address, am and size of the mapping for the device it controls--user-space provide this info on a per-driver basis.
Yes. I agree.
Quoted 4 lines
NB2. The mapping offsets configured through the cards' pins match the information passed from user space to each of the cards.
Yes. If I understand you correctly, your saying that management of the devices in the VME address space is a system configuration issue.
Quoted 3 lines
Cheers,
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 5 lines · Martyn Welch
Not the same question, but I'd agree - that would probably break the current model I have proposed. *However*, providing a resource management layer as you have proposed above the basic resource management my API provides would resolve that without added complexity in the bridge drivers themselves.
It wouldn't break it, the model simply couldn't give you more than 8 windows-->8 devices.
I think it should be the bridge the one that manages its own resources, not someone else.
I'm coding a layer that works this way, we'll see how it looks.
Quoted 2 lines
Yes. If I understand you correctly, your saying that management of the devices in the VME address space is a system configuration issue.
It obviously is. We cannot impose the users where they should plug their devices or which pins on the boards they should tweak. They build their crates --> they tell the kernel about them.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 8 lines · Emilio G. Cota
Quoted 2 lines · Martyn Welch
Not the same question, but I'd agree - that would probably break the current model I have proposed. *However*, providing a resource management layer as you have proposed above the basic resource management my API provides would resolve that without added complexity in the bridge drivers themselves.
It wouldn't break it, the model simply couldn't give you more than 8 windows-->8 devices.
Unless the devices we the same and the driver reused one window.
Quoted 3 lines
I think it should be the bridge the one that manages its own resources, not someone else.
I still think that layering this above the driver is better - it only needs to be written once rather than replicated for each bridge chip.
Quoted 3 lines
I'm coding a layer that works this way, we'll see how it looks.
Much obliged.
Quoted 8 lines
Quoted 2 lines
Yes. If I understand you correctly, your saying that management of the devices in the VME address space is a system configuration issue.
It obviously is. We cannot impose the users where they should plug their devices or which pins on the boards they should tweak. They build their crates --> they tell the kernel about them.
Agreed.
Martyn
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 4 lines · Martyn Welch
Quoted 3 lines
It wouldn't break it, the model simply couldn't give you more than 8 windows-->8 devices.
Unless the devices we the same and the driver reused one window.
No, we agreed on that devices should know *nothing* about windows.
E.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 12 lines · Emilio G. Cota
Quoted 6 lines · Martyn Welch
Quoted 4 lines
It wouldn't break it, the model simply couldn't give you more than 8 windows-->8 devices.
Unless the devices we the same and the driver reused one window.
No, we agreed on that devices should know *nothing* about windows.
Don't think I did. :-)
I agreed that it would be impossible to assign sole use of 8 resources to 17 different drivers. I feel that there could be room and indeed a need for both approaches. It may be more efficient for a driver to use and move 1 window to position it's self over the registers of each of a number of compatible devices as required. Such a driver could exist side-by-side with drivers using your approach, that utilize a layer which removes such control. Whilst I understand you can assign a fairly sizable chunk of memory mapped IO for VME, this is not always the case. In situations where the available memory mapped IO is limited, placing a large window to cover 2 smaller windows with dead space in between them will not be an adequate solution either.
Martyn
Quoted 2 lines
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 3 lines · Martyn Welch
need for both approaches. It may be more efficient for a driver to use and move 1 window to position it's self over the registers of each of a number of compatible devices as required.
No, drivers should know nothing about the bridge. The *very* same driver should run on different crates, different configurations, different bridges.
The bridge should be intelligent enough to handle the driver's mapping requests in an efficient way; it's basically the only job of the bridge: to serve efficiently the driver's needs. And, sorry to repeat myself, he's the only player entitled to do so. "Because he knows better."
FWIW, our driver does exactly that. Will post it fairly soon, but I want to test it with the new changes first.
Best wishes,
E.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
In reply to Martyn WelchQuoted 5 lines · Martyn Welch
There is a further device situated between the two.
Quoted 2 lines
How do you now protect the registers of this device from
Quoted 1 line
accidental/malicous/buggy behaviour from your board, since
this region of the VME bus is now enabled for transfers in the PCI IO Mem space?
The kernel is not designed to work with buggy hardware--that would be insane, we've got more than enough dealing with our own bugs. However when there's no other solution (e.g. hardware can't be fixed for whatever reason), fixes on a per-case basis may be applied.
E.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 12 lines · Emilio G. Cota
Quoted 9 lines · Martyn Welch
There is a further device situated between the two.
Quoted 4 lines
How do you now protect the registers of this device from
Quoted 2 lines
accidental/malicous/buggy behaviour from your board, since
this region of the VME bus is now enabled for transfers in the PCI IO Mem space?
Quoted 7 lines
The kernel is not designed to work with buggy hardware--that would be insane, we've got more than enough dealing with our own bugs. However when there's no other solution (e.g. hardware can't be fixed for whatever reason), fixes on a per-case basis may be applied.
Apart from : not mapping regions of the VME bus that you are not using.
Martyn
Quoted 2 lines
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 1 line · Martyn Welch
Apart from : not mapping regions of the VME bus that you are not using.
The tsi148 mappings have to be on 64k boundaries, so yes, it happens that sometimes you map a bit more than what you need. But there's no way to get around that; it's a hardware thing so we just accept it.
E.
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 11 lines · Emilio G. Cota
Quoted 2 lines · Martyn Welch
Apart from : not mapping regions of the VME bus that you are not using.
The tsi148 mappings have to be on 64k boundaries, so yes, it happens that sometimes you map a bit more than what you need. But there's no way to get around that; it's a hardware thing so we just accept it.
I agree - however you're talking about mapping areas which we don't need to map, to save the use of a window.
Martyn
Quoted 2 lines
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
In reply to Martyn WelchQuoted 6 lines · Martyn Welch
For using the VME bus for communication between two SBCs, I think we could use the CRCSR space to provide some information about the resources used on each board for say, a virtual network driver like the rapidIO subsystem provides. Possibly using the "device tree" stuff used on PowerPC, Blackfin and Sparc archs (I believe) for passing device layout between the firmware and kernel at boot.
Blackfin doesnt use device trees and there are no plans to do so -mike
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
Quoted 10 lines · Mike Frysinger
Quoted 7 lines · Martyn Welch
For using the VME bus for communication between two SBCs, I think we could use the CRCSR space to provide some information about the resources used on each board for say, a virtual network driver like the rapidIO subsystem provides. Possibly using the "device tree" stuff used on PowerPC, Blackfin and Sparc archs (I believe) for passing device layout between the firmware and kernel at boot.
Quoted 3 lines
Blackfin doesnt use device trees and there are no plans to do so -mike
Sorry, my mistake, I thought it did.
Martyn
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 1/5] Staging: VME Framework for the Linux Kernel
In reply to Emilio G. CotaQuoted 3 lines · Emilio G. Cota
A few comments after a quick glance, will need to dig deeper though:
- semaphores? isn't it screaming for mutexes?
Probably, it's old code forward-ported. Patches gladly welcome.
thanks,
greg k-h
PATCH 1/1 Staging: vme: fix {master,slave}_get check bug
In reply to Greg K-HIt's checking the wrong functions; fix it.
--- drivers/staging/vme/vme.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-)
drivers/staging/vme/vme.c +2 -2
@@ -338,7 +338,7 @@ int vme_slave_get (struct vme_resource *resource, int *enabled, image = list_entry(resource->entry, struct vme_slave_resource, list); -if (bridge->slave_set ==NULL ) { +if (bridge->slave_get ==NULL ) {printk ("vme_slave_get not supported\n" );return -EINVAL ; }
@@ -499,7 +499,7 @@ int vme_master_get (struct vme_resource *resource, int *enabled, image = list_entry(resource->entry, struct vme_master_resource, list); -if (bridge->master_set ==NULL ) { +if (bridge->master_get ==NULL ) {printk ("vme_master_set not supported\n" );return -EINVAL ; }
Signature · 1 line
1.6.3.3
Re: [PATCH] Staging: vme: fix {master,slave}_get check bug
Quoted 4 lines · Emilio G. Cota
It's checking the wrong functions; fix it.
Signed-off-by Emilio G. Cota <[email protected]>
Quoted 27 lines
--- drivers/staging/vme/vme.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-)drivers/staging/vme/vme.c +2
-2@@ -338,7 +338,7 @@ int vme_slave_get (struct vme_resource *resource, int *enabled, image = list_entry(resource->entry, struct vme_slave_resource, list); -if (bridge->slave_set ==NULL ) { +if (bridge->slave_get ==NULL ) {printk ("vme_slave_get not supported\n" );return -EINVAL ; }@@ -499,7 +499,7 @@ int vme_master_get (struct vme_resource *resource, int *enabled, image = list_entry(resource->entry, struct vme_master_resource, list); -if (bridge->master_set ==NULL ) { +if (bridge->master_get ==NULL ) {printk ("vme_master_set not supported\n" );return -EINVAL ; }
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
PATCH 2/5 Staging: vme: add VME userspace driver
In reply to Greg K-HFrom: Martyn Welch <[email protected]>
Adds a VME userspace access driver
--- drivers/staging/vme/Kconfig | 4 drivers/staging/vme/Makefile | 2 drivers/staging/vme/devices/Kconfig | 8 drivers/staging/vme/devices/Makefile | 5 drivers/staging/vme/devices/vme_user.c | 771 +++++++++++++++++++++++++++++++++ drivers/staging/vme/devices/vme_user.h | 43 + 6 files changed, 830 insertions(+), 3 deletions(-)
drivers/staging/vme/devices/Kconfig new file +8 -0
@@ -0,0 +1,8 @@ +comment "VME Device Drivers" + +config VME_USER + tristate "VME user space access driver" + help + If you say Y here you want to be able to access a limited number of + VME windows in a manner at least semi-compatible with the interface + provided with the original driver at http://vmelinux.org/.
drivers/staging/vme/devices/Makefile new file +5 -0
@@ -0,0 +1,5 @@ +# +# Makefile for the VME device drivers. +# + +obj-$(CONFIG_VME_USER) += vme_user.o
drivers/staging/vme/devices/vme_user.c new file +771 -0
771 lines not on this page · raw message
drivers/staging/vme/devices/vme_user.h new file +43 -0
43 lines not on this page · raw message
drivers/staging/vme/Kconfig +2 -2
@@ -11,7 +11,7 @@ menuconfig VME if VME #source "drivers/staging/vme/bridges/Kconfig" -# -#source "drivers/staging/vme/devices/Kconfig" + +source "drivers/staging/vme/devices/Kconfig" endif # VME
drivers/staging/vme/Makefile +1 -1
@@ -4,4 +4,4 @@ obj-$(CONFIG_VME) += vme.o #obj-y += bridges/ -#obj-y += devices/ +obj-y += devices/
Re: [patch 2/5] Staging: vme: add VME userspace driver
I remember we had a look at the vmelinux interface, but it really was so limited that we decided to get rid of it altogether. Do you really think it's worth bringing it back to life? Since it's never been merged into the kernel, why don't we just get it right this time and forget about it?
Instead of using that we implemented a heretic IOCTL-based interface for user-space; at least with it you could create a driver (with no interrupt support) for a device in user-space. We actually don't use it, but well, just to get inspiration, here it goes:
/**
* \name Window management ioctl numbers
*/
/** Get a physical window attributes */
#define VME_IOCTL_GET_WINDOW_ATTR _IOWR('V', 0, structvme_mapping)
/** Create a physical window */
#define VME_IOCTL_CREATE_WINDOW _IOW( 'V', 1, structvme_mapping)
/** Destroy a physical window */
#define VME_IOCTL_DESTROY_WINDOW _IOW( 'V', 2, int)
/** Create a mapping over a physical window */
#define VME_IOCTL_FIND_MAPPING _IOWR('V', 3, structvme_mapping)
/** Remove a mapping */
#define VME_IOCTL_RELEASE_MAPPING _IOW( 'V', 4, structvme_mapping)
/** Get the create on find failed flag */
#define VME_IOCTL_GET_CREATE_ON_FIND_FAIL _IOR( 'V', 5, unsigned
int)
/** Set the create on find failed flag */
#define VME_IOCTL_SET_CREATE_ON_FIND_FAIL _IOW( 'V', 6, unsigned
int)
/** Get the destroy on remove flag */
#define VME_IOCTL_GET_DESTROY_ON_REMOVE _IOR( 'V', 7, unsigned int) /** Set the destroy on remove flag */
#define VME_IOCTL_SET_DESTROY_ON_REMOVE _IOW( 'V', 8, unsigned int) /** Get bus error status */
#define VME_IOCTL_GET_BUS_ERROR _IOR( 'V', 9, unsigned int)
/** Start a DMA transfer */
#define VME_IOCTL_START_DMA _IOWR('V', 10, struct vme_dma)
[ grab the code from here: http://repo.or.cz/w/tsi148vmebridge.git .
Please note there's some backwards-compatible crap for old user-space LynxOS code, called CES. Ignore it. ]
By the way, please try to be consistent with naming, e.g. I see window, then see "image", and I have to think hard to see whether they mean the same or not.. err, do they?
E.
(I'll leave this here because of added Cc)
Quoted 877 lines · Greg K-H
From: Martyn Welch <[email protected]>
Adds a VME userspace access driver
Signed-off-by Martyn Welch <[email protected]>Signed-off-by Greg Kroah-Hartman <[email protected]>--- drivers/staging/vme/Kconfig | 4 drivers/staging/vme/Makefile | 2 drivers/staging/vme/devices/Kconfig | 8 drivers/staging/vme/devices/Makefile | 5 drivers/staging/vme/devices/vme_user.c | 771 +++++++++++++++++++++++++++++++++ drivers/staging/vme/devices/vme_user.h | 43 + 6 files changed, 830 insertions(+), 3 deletions(-)drivers/staging/vme/devices/Kconfig new file +8
-0@@ -0,0 +1,8 @@ +comment "VME Device Drivers" + +config VME_USER + tristate "VME user space access driver" + help + If you say Y here you want to be able to access a limited number of + VME windows in a manner at least semi-compatible with the interface + provided with the original driver at http://vmelinux.org/.drivers/staging/vme/devices/Makefile new file +5
-0@@ -0,0 +1,5 @@ +# +# Makefile for the VME device drivers. +# + +obj-$(CONFIG_VME_USER) += vme_user.odrivers/staging/vme/devices/vme_user.c new file +771
-0@@ -0,0 +1,771 @@ +/* +* VMEbus User access driver +* +* Author: Martyn Welch <[email protected]> +* Copyright 2008 GE Fanuc Intelligent Platforms Embedded Systems, Inc. +* +* Based on work by: +* Tom Armistead and Ajit Prem +* Copyright 2004 Motorola Inc. +* +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +#include <linux/cdev.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/pagemap.h> +#include <linux/pci.h> +#include <linux/semaphore.h> +#include <linux/spinlock.h> +#include <linux/syscalls.h> +#include <linux/types.h> +#include <linux/version.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#include "../vme.h" +#include "vme_user.h" + +/* Currently Documentation/devices.txt defines the following for VME: +* +* 221 char VME bus +* 0 = /dev/bus/vme/m0 First master image +* 1 = /dev/bus/vme/m1 Second master image +* 2 = /dev/bus/vme/m2 Third master image +* 3 = /dev/bus/vme/m3 Fourth master image +* 4 = /dev/bus/vme/s0 First slave image +* 5 = /dev/bus/vme/s1 Second slave image +* 6 = /dev/bus/vme/s2 Third slave image +* 7 = /dev/bus/vme/s3 Fourth slave image +* 8 = /dev/bus/vme/ctl Control +* +* It is expected that all VME bus drivers will use the +* same interface. For interface documentation see +* http://www.vmelinux.org/. +* +* However the VME driver at http://www.vmelinux.org/ is rather old and doesn't +* even support the tsi148 chipset (which has 8 master and 8 slave windows). +* We'll run with this or now as far as possible, however it probably makes +* sense to get rid of the old mappings and just do everything dynamically. +* +* So for now, we'll restrict the driver to providing 4 masters and 4 slaves as +* defined above and try to support at least some of the interface from +* http://www.vmelinux.org/ as an alternative drive can be written providing a +* saner interface later. +*/ +#define VME_MAJOR 221/* VME Major Device Number */ +#define VME_DEVS 9/* Number of dev entries */ + +#define MASTER_MINOR 0 +#define MASTER_MAX 3 +#define SLAVE_MINOR 4 +#define SLAVE_MAX 7 +#define CONTROL_MINOR 8 + +#define PCI_BUF_SIZE 0x20000/* Size of one slave image buffer */ + +/* +* Structure to handle image related parameters. +*/ +typedef struct { +void __iomem *kern_buf;/* Buffer address in kernel space */ +dma_addr_t pci_buf;/* Buffer address in PCI address space */ +unsigned long long size_buf;/* Buffer size */ +struct semaphore sem;/* Semaphore for locking image */ +struct device *device;/* Sysfs device */ +struct vme_resource *resource;/* VME resource */ +int users;/* Number of current users */ +}image_desc_t ; +static image_desc_t image[VME_DEVS ]; + +typedef struct { +unsigned long reads; +unsigned long writes; +unsigned long ioctls; +unsigned long irqs; +unsigned long berrs; +unsigned long dmaErrors; +unsigned long timeouts; +unsigned long external; +}driver_stats_t ; +static driver_stats_t statistics; + +struct cdev *vme_user_cdev;/* Character device */ +struct class *vme_user_sysfs_class;/* Sysfs class */ +struct device *vme_user_bridge;/* Pointer to the bridge device */ + +static char driver_name[] ="vme_user" ; + +static const int type[VME_DEVS ] = {MASTER_MINOR ,MASTER_MINOR , +MASTER_MINOR ,MASTER_MINOR , +SLAVE_MINOR ,SLAVE_MINOR , +SLAVE_MINOR ,SLAVE_MINOR , +CONTROL_MINOR + }; + + +static int vme_user_open (struct inode *,struct file *); +static int vme_user_release (struct inode *,struct file *); +static ssize_t vme_user_read (struct file *,char *,size_t ,loff_t *); +static ssize_t vme_user_write (struct file *,const char *,size_t ,loff_t *); +static loff_t vme_user_llseek (struct file *,loff_t ,int ); +static int vme_user_ioctl (struct inode *,struct file *,unsigned int , +unsigned long ); + +static int __init vme_user_probe (struct device *dev); + +static struct file_operations vme_user_fops = { + .open = vme_user_open, + .release = vme_user_release, + .read = vme_user_read, + .write = vme_user_write, + .llseek = vme_user_llseek, + .ioctl = vme_user_ioctl, +}; + + +/* +* Reset all the statistic counters +*/ +static void reset_counters (void ) +{ + statistics.reads =0 ; + statistics.writes =0 ; + statistics.ioctls =0 ; + statistics.irqs =0 ; + statistics.berrs =0 ; + statistics.dmaErrors =0 ; + statistics.timeouts =0 ; +} + +void lmcall (int monitor) +{ +printk ("Caught Location Monitor %d access\n" , monitor); +} + +static void tests (void ) +{ +struct vme_resource *dma_res; +struct vme_dma_list *dma_list; +struct vme_dma_attr *pattern_attr, *vme_attr; + +int retval; +unsigned int data; + +printk ("Running VME DMA test\n" ); + dma_res =vme_request_dma (vme_user_bridge); + dma_list =vme_new_dma_list (dma_res); + pattern_attr =vme_dma_pattern_attribute (0x0 , +VME_DMA_PATTERN_WORD | +VME_DMA_PATTERN_INCREMENT ); + vme_attr =vme_dma_vme_attribute (0x10000 ,VME_A32 , +VME_SCT ,VME_D32 ); + retval =vme_dma_list_add (dma_list, pattern_attr, + vme_attr,0x10000 ); +#if 0 +vme_dma_free_attribute (vme_attr); + vme_attr =vme_dma_vme_attribute (0x20000 ,VME_A32 , +VME_SCT ,VME_D32 ); + retval =vme_dma_list_add (dma_list, pattern_attr, + vme_attr,0x10000 ); +#endif + retval =vme_dma_list_exec (dma_list); +vme_dma_free_attribute (pattern_attr); +vme_dma_free_attribute (vme_attr); +vme_dma_list_free (dma_list); +#if 0 +printk ("Generating a VME interrupt\n" ); +vme_generate_irq (dma_res,0x3 ,0xaa ); +printk ("Interrupt returned\n" ); +#endif +vme_dma_free (dma_res); + +/* Attempt RMW */ + data =vme_master_rmw (image[0 ].resource,0x80000000 ,0x00000000 , +0x80000000 ,0 ); +printk ("RMW returned 0x%8.8x\n" , data); + + +/* Location Monitor */ +printk ("vme_lm_set:%d\n" ,vme_lm_set (vme_user_bridge,0x60000 ,VME_A32 ,VME_SCT |VME_USER |VME_DATA )); +printk ("vme_lm_attach:%d\n" ,vme_lm_attach (vme_user_bridge,0 , lmcall)); + +printk ("Board in VME slot:%d\n" ,vme_slot_get (vme_user_bridge)); +} + +static int vme_user_open (struct inode *inode,struct file *file) +{ +int err; +unsigned int minor =MINOR (inode->i_rdev); + +down (&image[minor].sem); +/* Only allow device to be opened if a resource is allocated */ +if (image[minor].resource ==NULL ) { +printk (KERN_ERR "No resources allocated for device\n" ); + err = -EINVAL ; + goto err_res; + } + +/* Increment user count */ + image[minor].users++; + +up (&image[minor].sem); + +return 0 ; + +err_res: +up (&image[minor].sem); + +return err; +} + +static int vme_user_release (struct inode *inode,struct file *file) +{ +unsigned int minor =MINOR (inode->i_rdev); + +down (&image[minor].sem); + +/* Decrement user count */ + image[minor].users--; + +up (&image[minor].sem); + +return 0 ; +} + +/* +* We are going ot alloc a page during init per window for small transfers. +* Small transfers will go VME -> buffer -> user space. Larger (more than a +* page) transfers will lock the user space buffer into memory and then +* transfer the data directly into the user space buffers. +*/ +static ssize_t resource_to_user (int minor,char __user *buf,size_t count, +loff_t *ppos) +{ +ssize_t retval; +ssize_t copied =0 ; + +if (count <= image[minor].size_buf) { +/* We copy to kernel buffer */ + copied =vme_master_read (image[minor].resource, + image[minor].kern_buf, count, *ppos); +if (copied <0 ) { +return (int )copied; + } + + retval =__copy_to_user (buf, image[minor].kern_buf, + (unsigned long )copied); +if (retval !=0 ) { + copied = (copied - retval); +printk ("User copy failed\n" ); +return -EINVAL ; + } + + }else { +/* XXX Need to write this */ +printk ("Currently don't support large transfers\n" ); +/* Map in pages from userspace */ + +/* Call vme_master_read to do the transfer */ +return -EINVAL ; + } + +return copied; +} + +/* +* We are going ot alloc a page during init per window for small transfers. +* Small transfers will go user space -> buffer -> VME. Larger (more than a +* page) transfers will lock the user space buffer into memory and then +* transfer the data directly from the user space buffers out to VME. +*/ +static ssize_t resource_from_user (unsigned int minor,const char *buf, +size_t count,loff_t *ppos) +{ +ssize_t retval; +ssize_t copied =0 ; + +if (count <= image[minor].size_buf) { + retval =__copy_from_user (image[minor].kern_buf, buf, + (unsigned long )count); +if (retval !=0 ) + copied = (copied - retval); +else + copied = count; + + copied =vme_master_write (image[minor].resource, + image[minor].kern_buf, copied, *ppos); + }else { +/* XXX Need to write this */ +printk ("Currently don't support large transfers\n" ); +/* Map in pages from userspace */ + +/* Call vme_master_write to do the transfer */ +return -EINVAL ; + } + +return copied; +} + +static ssize_t buffer_to_user (unsigned int minor,char __user *buf, +size_t count,loff_t *ppos) +{ +void __iomem *image_ptr; +ssize_t retval; + + image_ptr = image[minor].kern_buf + *ppos; + + retval =__copy_to_user (buf, image_ptr, (unsigned long )count); +if (retval !=0 ) { + retval = (count - retval); +printk (KERN_WARNING "Partial copy to userspace\n" ); + }else + retval = count; + +/* Return number of bytes successfully read */ +return retval; +} + +static ssize_t buffer_from_user (unsigned int minor,const char *buf, +size_t count,loff_t *ppos) +{ +void __iomem *image_ptr; +size_t retval; + + image_ptr = image[minor].kern_buf + *ppos; + + retval =__copy_from_user (image_ptr, buf, (unsigned long )count); +if (retval !=0 ) { + retval = (count - retval); +printk (KERN_WARNING "Partial copy to userspace\n" ); + }else + retval = count; + +/* Return number of bytes successfully read */ +return retval; +} + +static ssize_t vme_user_read (struct file *file,char *buf,size_t count, +loff_t * ppos) +{ +unsigned int minor =MINOR (file->f_dentry->d_inode->i_rdev); +ssize_t retval; +size_t image_size; +size_t okcount; + +down (&image[minor].sem); + +/* XXX Do we *really* want this helper - we can use vme_*_get ? */ + image_size =vme_get_size (image[minor].resource); + +/* Ensure we are starting at a valid location */ +if ((*ppos <0 ) || (*ppos > (image_size -1 ))) { +up (&image[minor].sem); +return 0 ; + } + +/* Ensure not reading past end of the image */ +if (*ppos + count > image_size) + okcount = image_size - *ppos; +else + okcount = count; + +switch (type[minor]){ +case MASTER_MINOR : + retval =resource_to_user (minor, buf, okcount, ppos); +break ; +case SLAVE_MINOR : + retval =buffer_to_user (minor, buf, okcount, ppos); +break ; +default : + retval = -EINVAL ; + } + +up (&image[minor].sem); + +if (retval >0 ) + *ppos += retval; + +return retval; +} + +static ssize_t vme_user_write (struct file *file,const char *buf,size_t count, +loff_t *ppos) +{ +unsigned int minor =MINOR (file->f_dentry->d_inode->i_rdev); +ssize_t retval; +size_t image_size; +size_t okcount; + +down (&image[minor].sem); + + image_size =vme_get_size (image[minor].resource); + +/* Ensure we are starting at a valid location */ +if ((*ppos <0 ) || (*ppos > (image_size -1 ))) { +up (&image[minor].sem); +return 0 ; + } + +/* Ensure not reading past end of the image */ +if (*ppos + count > image_size) + okcount = image_size - *ppos; +else + okcount = count; + +switch (type[minor]){ +case MASTER_MINOR : + retval =resource_from_user (minor, buf, okcount, ppos); +break ; +case SLAVE_MINOR : + retval =buffer_from_user (minor, buf, okcount, ppos); +break ; +default : + retval = -EINVAL ; + } + +up (&image[minor].sem); + +if (retval >0 ) + *ppos += retval; + +return retval; +} + +static loff_t vme_user_llseek (struct file *file,loff_t off,int whence) +{ +printk (KERN_ERR "Llseek currently incomplete\n" ); +return -EINVAL ; +} + +static int vme_user_ioctl (struct inode *inode,struct file *file, +unsigned int cmd,unsigned long arg) +{ +unsigned int minor =MINOR (inode->i_rdev); +#if 0 +int ret_val; +#endif +unsigned long copyRet; +vme_slave_t slave; + + statistics.ioctls++; +switch (type[minor]) { +case CONTROL_MINOR : +break ; +case MASTER_MINOR : +break ; +case SLAVE_MINOR : +switch (cmd) { +case VME_SET_SLAVE : + + copyRet =copy_from_user (&slave, (char *)arg, +sizeof (slave)); +if (copyRet !=0 ) { +printk (KERN_WARNING "Partial copy from " +"userspace\n" ); +return -EFAULT ; + } + +return vme_slave_set (image[minor].resource, + slave.enable, slave.vme_addr, slave.size, + image[minor].pci_buf, slave.aspace, + slave.cycle); + +break ; +#if 0 +case VME_GET_SLAVE : +vme_slave_t slave; + + ret_val =vme_slave_get (minor, &iRegs); + + copyRet =copy_to_user ((char *)arg, &slave, +sizeof (slave)); +if (copyRet !=0 ) { +printk (KERN_WARNING "Partial copy to " +"userspace\n" ); +return -EFAULT ; + } + +return ret_val; +break ; +#endif + } +break ; + } + +return -EINVAL ; +} + + +/* +* Unallocate a previously allocated buffer +*/ +static void buf_unalloc (int num) +{ +if (image[num].kern_buf) { +#ifdef VME_DEBUG +printk (KERN_DEBUG "UniverseII:Releasing buffer at %p\n" , + image[num].pci_buf); +#endif + +vme_free_consistent (image[num].resource, image[num].size_buf, + image[num].kern_buf, image[num].pci_buf); + + image[num].kern_buf =NULL ; + image[num].pci_buf =0 ; + image[num].size_buf =0 ; + +#ifdef VME_DEBUG + }else { +printk (KERN_DEBUG "UniverseII: Buffer not allocated\n" ); +#endif + } +} + +static struct vme_driver vme_user_driver = { + .name = driver_name, + .probe = vme_user_probe, +}; + + +/* +* In this simple access driver, the old behaviour is being preserved as much +* as practical. We will therefore reserve the buffers and request the images +* here so that we don't have to do it later. +*/ +static int __init vme_bridge_init (void ) +{ +int retval; +printk (KERN_INFO "VME User Space Access Driver\n" ); +printk ("vme_user_driver:%p\n" , &vme_user_driver); + retval =vme_register_driver (&vme_user_driver); +printk ("vme_register_driver returned %d\n" , retval); +return retval; +} + +/* +* This structure gets passed a device, this should be the device created at +* registration. +*/ +static int __init vme_user_probe (struct device *dev) +{ +int i, err; +char name[8 ]; + +printk ("Running vme_user_probe()\n" ); + +/* Pointer to the bridge device */ + vme_user_bridge = dev; + +/* Initialise descriptors */ +for (i =0 ; i <VME_DEVS ; i++) { + image[i].kern_buf =NULL ; + image[i].pci_buf =0 ; +init_MUTEX (&(image[i].sem)); + image[i].device =NULL ; + image[i].resource =NULL ; + image[i].users =0 ; + } + +/* Initialise statistics counters */ +reset_counters (); + +/* Assign major and minor numbers for the driver */ + err =register_chrdev_region (MKDEV (VME_MAJOR ,0 ),VME_DEVS , + driver_name); +if (err) { +printk (KERN_WARNING "%s: Error getting Major Number %d for " +"driver.\n" , driver_name,VME_MAJOR ); + goto err_region; + } + +/* Register the driver as a char device */ + vme_user_cdev =cdev_alloc (); + vme_user_cdev->ops = &vme_user_fops; + vme_user_cdev->owner =THIS_MODULE ; + err =cdev_add (vme_user_cdev,MKDEV (VME_MAJOR ,0 ),VME_DEVS ); +if (err) { +printk (KERN_WARNING "%s: cdev_all failed\n" , driver_name); + goto err_char; + } + +/* Request slave resources and allocate buffers (128kB wide) */ +for (i =SLAVE_MINOR ; i < (SLAVE_MAX +1 ); i++) { +/* XXX Need to properly request attributes */ + image[i].resource =vme_slave_request (vme_user_bridge, +VME_A16 ,VME_SCT ); +if (image[i].resource ==NULL ) { +printk (KERN_WARNING "Unable to allocate slave " +"resource\n" ); + goto err_buf; + } + image[i].size_buf =PCI_BUF_SIZE ; + image[i].kern_buf =vme_alloc_consistent (image[i].resource, + image[i].size_buf, &(image[i].pci_buf)); +if (image[i].kern_buf ==NULL ) { +printk (KERN_WARNING "Unable to allocate memory for " +"buffer\n" ); + image[i].pci_buf =0 ; +vme_slave_free (image[i].resource); + err = -ENOMEM ; + goto err_buf; + } + } + +/* +* Request master resources allocate page sized buffers for small +* reads and writes +*/ +for (i =MASTER_MINOR ; i < (MASTER_MAX +1 ); i++) { +/* XXX Need to properly request attributes */ + image[i].resource =vme_master_request (vme_user_bridge, +VME_A32 ,VME_SCT ,VME_D32 ); +if (image[i].resource ==NULL ) { +printk (KERN_WARNING "Unable to allocate master " +"resource\n" ); + goto err_buf; + } + image[i].size_buf =PAGE_SIZE ; + image[i].kern_buf =vme_alloc_consistent (image[i].resource, + image[i].size_buf, &(image[i].pci_buf)); +if (image[i].kern_buf ==NULL ) { +printk (KERN_WARNING "Unable to allocate memory for " +"buffer\n" ); + image[i].pci_buf =0 ; +vme_master_free (image[i].resource); + err = -ENOMEM ; + goto err_buf; + } + } + +/* Setup some debug windows */ +for (i =SLAVE_MINOR ; i < (SLAVE_MAX +1 ); i++) { + err =vme_slave_set (image[i].resource,1 ,0x4000 *(i-4 ), +0x4000 , image[i].pci_buf,VME_A16 , +VME_SCT |VME_SUPER |VME_USER |VME_PROG |VME_DATA ); +if (err !=0 ) { +printk (KERN_WARNING "Failed to configure window\n" ); + goto err_buf; + } + } +for (i =MASTER_MINOR ; i < (MASTER_MAX +1 ); i++) { + err =vme_master_set (image[i].resource,1 , + (0x10000 + (0x10000 *i)),0x10000 , +VME_A32 ,VME_SCT |VME_USER |VME_DATA ,VME_D32 ); +if (err !=0 ) { +printk (KERN_WARNING "Failed to configure window\n" ); + goto err_buf; + } + } + +/* Create sysfs entries - on udev systems this creates the dev files */ + vme_user_sysfs_class =class_create (THIS_MODULE , driver_name); +if (IS_ERR (vme_user_sysfs_class)) { +printk (KERN_ERR "Error creating vme_user class.\n" ); + err =PTR_ERR (vme_user_sysfs_class); + goto err_class; + } + +/* Add sysfs Entries */ +for (i=0 ; i<VME_DEVS ; i++) { +switch (type[i]) { +case MASTER_MINOR : +sprintf (name,"bus/vme/m%%d" ); +break ; +case CONTROL_MINOR : +sprintf (name,"bus/vme/ctl" ); +break ; +case SLAVE_MINOR : +sprintf (name,"bus/vme/s%%d" ); +break ; +default : + err = -EINVAL ; + goto err_sysfs; +break ; + } + + image[i].device = +device_create (vme_user_sysfs_class,NULL , +MKDEV (VME_MAJOR , i),NULL , name, + (type[i] ==SLAVE_MINOR )? i - (MASTER_MAX +1 ) : i); +if (IS_ERR (image[i].device)) { +printk ("%s: Error creating sysfs device\n" , + driver_name); + err =PTR_ERR (image[i].device); + goto err_sysfs; + } + } + +/* XXX Run tests */ +/* +tests(); +*/ + +return 0 ; + +/* Ensure counter set correcty to destroy all sysfs devices */ + i =VME_DEVS ; +err_sysfs: +while (i >0 ){ + i--; +device_destroy (vme_user_sysfs_class,MKDEV (VME_MAJOR , i)); + } +class_destroy (vme_user_sysfs_class); + +/* Ensure counter set correcty to unalloc all slave buffers */ + i =SLAVE_MAX +1 ; +err_buf: +while (i >SLAVE_MINOR ){ + i--; +vme_slave_set (image[i].resource,0 ,0 ,0 ,0 ,VME_A32 ,0 ); +vme_slave_free (image[i].resource); +buf_unalloc (i); + } +err_class: +cdev_del (vme_user_cdev); +err_char: +unregister_chrdev_region (MKDEV (VME_MAJOR ,0 ),VME_DEVS ); +err_region: +return err; +} + +static void __exit vme_bridge_exit(void) +{ +int i; + +/* Remove sysfs Entries */ +for (i=0 ; i<VME_DEVS ; i++) { +device_destroy (vme_user_sysfs_class,MKDEV (VME_MAJOR , i)); + } +class_destroy (vme_user_sysfs_class); + +for (i =SLAVE_MINOR ; i < (SLAVE_MAX +1 ); i++) { +buf_unalloc (i); + } + +/* Unregister device driver */ +cdev_del (vme_user_cdev); + +/* Unregiser the major and minor device numbers */ +unregister_chrdev_region (MKDEV (VME_MAJOR ,0 ),VME_DEVS ); +} + +MODULE_DESCRIPTION ("VME User Space Access Driver" ); +MODULE_AUTHOR ("Martyn Welch <[email protected]" ); +MODULE_LICENSE ("GPL" ); + +module_init (vme_bridge_init); +module_exit (vme_bridge_exit);drivers/staging/vme/devices/vme_user.h new file +43
-0@@ -0,0 +1,43 @@ + +/* +* VMEbus Master Window Configuration Structure +*/ +typedef struct { +char enable;/* State of Window */ +unsigned long long vme_addr;/* Starting Address on the VMEbus */ +unsigned long long size;/* Window Size */ +vme_address_t aspace;/* Address Space */ +vme_cycle_t cycle;/* Cycle properties */ +vme_width_t dwidth;/* Maximum Data Width */ +#if 0 +char prefetchEnable;/* Prefetch Read Enable State */ +int prefetchSize;/* Prefetch Read Size (Cache Lines) */ +char wrPostEnable;/* Write Post State */ +#endif +}vme_master_t ; + + +/* +* IOCTL Commands and structures +*/ + +/* Magic number for use in ioctls */ +#define VME_IOC_MAGIC 0xAE + + +/* VMEbus Slave Window Configuration Structure */ +typedef struct { +char enable;/* State of Window */ +unsigned long long vme_addr;/* Starting Address on the VMEbus */ +unsigned long long size;/* Window Size */ +vme_address_t aspace;/* Address Space */ +vme_cycle_t cycle;/* Cycle properties */ +#if 0 +char wrPostEnable;/* Write Post State */ +char rmwLock;/* Lock PCI during RMW Cycles */ +char data64BitCapable;/* non-VMEbus capable of 64-bit Data */ +#endif +}vme_slave_t ; + +#define VME_SET_SLAVE _IOW(VME_IOC_MAGIC, 10, vme_slave_t) +drivers/staging/vme/Kconfig +2
-2@@ -11,7 +11,7 @@ menuconfig VME if VME #source "drivers/staging/vme/bridges/Kconfig" -# -#source "drivers/staging/vme/devices/Kconfig" + +source "drivers/staging/vme/devices/Kconfig" endif # VMEdrivers/staging/vme/Makefile +1
-1@@ -4,4 +4,4 @@ obj-$(CONFIG_VME) += vme.o #obj-y += bridges/ -#obj-y += devices/ +obj-y += devices/
Re: [patch 2/5] Staging: vme: add VME userspace driver
(oops, just correcting Sebastien's e-mail address)
Quoted 934 lines · Emilio G. Cota
I remember we had a look at the vmelinux interface, but it really was so limited that we decided to get rid of it altogether. Do you really think it's worth bringing it back to life? Since it's never been merged into the kernel, why don't we just get it right this time and forget about it?
Instead of using that we implemented a heretic IOCTL-based interface for user-space; at least with it you could create a driver (with no interrupt support) for a device in user-space. We actually don't use it, but well, just to get inspiration, here it goes:
/**
* \name Window management ioctl numbers
*/
/** Get a physical window attributes */
#defineVME_IOCTL_GET_WINDOW_ATTR_IOWR('V', 0, structvme_mapping)
/** Create a physical window */
#defineVME_IOCTL_CREATE_WINDOW_IOW( 'V', 1, structvme_mapping)
/** Destroy a physical window */
#defineVME_IOCTL_DESTROY_WINDOW_IOW( 'V', 2, int)
/** Create a mapping over a physical window */
#defineVME_IOCTL_FIND_MAPPING_IOWR('V', 3, structvme_mapping)
/** Remove a mapping */
#defineVME_IOCTL_RELEASE_MAPPING_IOW( 'V', 4, structvme_mapping)
/** Get the create on find failed flag */
#defineVME_IOCTL_GET_CREATE_ON_FIND_FAIL_IOR( 'V', 5, unsigned
int)
/** Set the create on find failed flag */
#defineVME_IOCTL_SET_CREATE_ON_FIND_FAIL_IOW( 'V', 6, unsigned
int)
/** Get the destroy on remove flag */
#defineVME_IOCTL_GET_DESTROY_ON_REMOVE_IOR( 'V', 7, unsigned int)/** Set the destroy on remove flag */
#defineVME_IOCTL_SET_DESTROY_ON_REMOVE_IOW( 'V', 8, unsigned int)/** Get bus error status */
#defineVME_IOCTL_GET_BUS_ERROR_IOR( 'V', 9, unsigned int)
/** Start a DMA transfer */
#defineVME_IOCTL_START_DMA_IOWR('V', 10, struct vme_dma)[ grab the code from here: http://repo.or.cz/w/tsi148vmebridge.git .
Please note there's some backwards-compatible crap for old user-space LynxOS code, called CES. Ignore it. ]By the way, please try to be consistent with naming, e.g. I see window, then see "image", and I have to think hard to see whether they mean the same or not.. err, do they?
E.
(I'll leave this here because of added Cc)
Quoted 877 lines · Greg K-H
From: Martyn Welch <[email protected]>
Adds a VME userspace access driver
Signed-off-by Martyn Welch <[email protected]>Signed-off-by Greg Kroah-Hartman <[email protected]>--- drivers/staging/vme/Kconfig | 4 drivers/staging/vme/Makefile | 2 drivers/staging/vme/devices/Kconfig | 8 drivers/staging/vme/devices/Makefile | 5 drivers/staging/vme/devices/vme_user.c | 771 +++++++++++++++++++++++++++++++++ drivers/staging/vme/devices/vme_user.h | 43 + 6 files changed, 830 insertions(+), 3 deletions(-)drivers/staging/vme/devices/Kconfig new file +8
-0@@ -0,0 +1,8 @@ +comment "VME Device Drivers" + +config VME_USER + tristate "VME user space access driver" + help + If you say Y here you want to be able to access a limited number of + VME windows in a manner at least semi-compatible with the interface + provided with the original driver at http://vmelinux.org/.drivers/staging/vme/devices/Makefile new file +5
-0@@ -0,0 +1,5 @@ +# +# Makefile for the VME device drivers. +# + +obj-$(CONFIG_VME_USER) += vme_user.odrivers/staging/vme/devices/vme_user.c new file +771
-0@@ -0,0 +1,771 @@ +/* +* VMEbus User access driver +* +* Author: Martyn Welch <[email protected]> +* Copyright 2008 GE Fanuc Intelligent Platforms Embedded Systems, Inc. +* +* Based on work by: +* Tom Armistead and Ajit Prem +* Copyright 2004 Motorola Inc. +* +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +#include <linux/cdev.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/pagemap.h> +#include <linux/pci.h> +#include <linux/semaphore.h> +#include <linux/spinlock.h> +#include <linux/syscalls.h> +#include <linux/types.h> +#include <linux/version.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#include "../vme.h" +#include "vme_user.h" + +/* Currently Documentation/devices.txt defines the following for VME: +* +* 221 char VME bus +* 0 = /dev/bus/vme/m0 First master image +* 1 = /dev/bus/vme/m1 Second master image +* 2 = /dev/bus/vme/m2 Third master image +* 3 = /dev/bus/vme/m3 Fourth master image +* 4 = /dev/bus/vme/s0 First slave image +* 5 = /dev/bus/vme/s1 Second slave image +* 6 = /dev/bus/vme/s2 Third slave image +* 7 = /dev/bus/vme/s3 Fourth slave image +* 8 = /dev/bus/vme/ctl Control +* +* It is expected that all VME bus drivers will use the +* same interface. For interface documentation see +* http://www.vmelinux.org/. +* +* However the VME driver at http://www.vmelinux.org/ is rather old and doesn't +* even support the tsi148 chipset (which has 8 master and 8 slave windows). +* We'll run with this or now as far as possible, however it probably makes +* sense to get rid of the old mappings and just do everything dynamically. +* +* So for now, we'll restrict the driver to providing 4 masters and 4 slaves as +* defined above and try to support at least some of the interface from +* http://www.vmelinux.org/ as an alternative drive can be written providing a +* saner interface later. +*/ +#define VME_MAJOR 221/* VME Major Device Number */ +#define VME_DEVS 9/* Number of dev entries */ + +#define MASTER_MINOR 0 +#define MASTER_MAX 3 +#define SLAVE_MINOR 4 +#define SLAVE_MAX 7 +#define CONTROL_MINOR 8 + +#define PCI_BUF_SIZE 0x20000/* Size of one slave image buffer */ + +/* +* Structure to handle image related parameters. +*/ +typedef struct { +void __iomem *kern_buf;/* Buffer address in kernel space */ +dma_addr_t pci_buf;/* Buffer address in PCI address space */ +unsigned long long size_buf;/* Buffer size */ +struct semaphore sem;/* Semaphore for locking image */ +struct device *device;/* Sysfs device */ +struct vme_resource *resource;/* VME resource */ +int users;/* Number of current users */ +}image_desc_t ; +static image_desc_t image[VME_DEVS ]; + +typedef struct { +unsigned long reads; +unsigned long writes; +unsigned long ioctls; +unsigned long irqs; +unsigned long berrs; +unsigned long dmaErrors; +unsigned long timeouts; +unsigned long external; +}driver_stats_t ; +static driver_stats_t statistics; + +struct cdev *vme_user_cdev;/* Character device */ +struct class *vme_user_sysfs_class;/* Sysfs class */ +struct device *vme_user_bridge;/* Pointer to the bridge device */ + +static char driver_name[] ="vme_user" ; + +static const int type[VME_DEVS ] = {MASTER_MINOR ,MASTER_MINOR , +MASTER_MINOR ,MASTER_MINOR , +SLAVE_MINOR ,SLAVE_MINOR , +SLAVE_MINOR ,SLAVE_MINOR , +CONTROL_MINOR + }; + + +static int vme_user_open (struct inode *,struct file *); +static int vme_user_release (struct inode *,struct file *); +static ssize_t vme_user_read (struct file *,char *,size_t ,loff_t *); +static ssize_t vme_user_write (struct file *,const char *,size_t ,loff_t *); +static loff_t vme_user_llseek (struct file *,loff_t ,int ); +static int vme_user_ioctl (struct inode *,struct file *,unsigned int , +unsigned long ); + +static int __init vme_user_probe (struct device *dev); + +static struct file_operations vme_user_fops = { + .open = vme_user_open, + .release = vme_user_release, + .read = vme_user_read, + .write = vme_user_write, + .llseek = vme_user_llseek, + .ioctl = vme_user_ioctl, +}; + + +/* +* Reset all the statistic counters +*/ +static void reset_counters (void ) +{ + statistics.reads =0 ; + statistics.writes =0 ; + statistics.ioctls =0 ; + statistics.irqs =0 ; + statistics.berrs =0 ; + statistics.dmaErrors =0 ; + statistics.timeouts =0 ; +} + +void lmcall (int monitor) +{ +printk ("Caught Location Monitor %d access\n" , monitor); +} + +static void tests (void ) +{ +struct vme_resource *dma_res; +struct vme_dma_list *dma_list; +struct vme_dma_attr *pattern_attr, *vme_attr; + +int retval; +unsigned int data; + +printk ("Running VME DMA test\n" ); + dma_res =vme_request_dma (vme_user_bridge); + dma_list =vme_new_dma_list (dma_res); + pattern_attr =vme_dma_pattern_attribute (0x0 , +VME_DMA_PATTERN_WORD | +VME_DMA_PATTERN_INCREMENT ); + vme_attr =vme_dma_vme_attribute (0x10000 ,VME_A32 , +VME_SCT ,VME_D32 ); + retval =vme_dma_list_add (dma_list, pattern_attr, + vme_attr,0x10000 ); +#if 0 +vme_dma_free_attribute (vme_attr); + vme_attr =vme_dma_vme_attribute (0x20000 ,VME_A32 , +VME_SCT ,VME_D32 ); + retval =vme_dma_list_add (dma_list, pattern_attr, + vme_attr,0x10000 ); +#endif + retval =vme_dma_list_exec (dma_list); +vme_dma_free_attribute (pattern_attr); +vme_dma_free_attribute (vme_attr); +vme_dma_list_free (dma_list); +#if 0 +printk ("Generating a VME interrupt\n" ); +vme_generate_irq (dma_res,0x3 ,0xaa ); +printk ("Interrupt returned\n" ); +#endif +vme_dma_free (dma_res); + +/* Attempt RMW */ + data =vme_master_rmw (image[0 ].resource,0x80000000 ,0x00000000 , +0x80000000 ,0 ); +printk ("RMW returned 0x%8.8x\n" , data); + + +/* Location Monitor */ +printk ("vme_lm_set:%d\n" ,vme_lm_set (vme_user_bridge,0x60000 ,VME_A32 ,VME_SCT |VME_USER |VME_DATA )); +printk ("vme_lm_attach:%d\n" ,vme_lm_attach (vme_user_bridge,0 , lmcall)); + +printk ("Board in VME slot:%d\n" ,vme_slot_get (vme_user_bridge)); +} + +static int vme_user_open (struct inode *inode,struct file *file) +{ +int err; +unsigned int minor =MINOR (inode->i_rdev); + +down (&image[minor].sem); +/* Only allow device to be opened if a resource is allocated */ +if (image[minor].resource ==NULL ) { +printk (KERN_ERR "No resources allocated for device\n" ); + err = -EINVAL ; + goto err_res; + } + +/* Increment user count */ + image[minor].users++; + +up (&image[minor].sem); + +return 0 ; + +err_res: +up (&image[minor].sem); + +return err; +} + +static int vme_user_release (struct inode *inode,struct file *file) +{ +unsigned int minor =MINOR (inode->i_rdev); + +down (&image[minor].sem); + +/* Decrement user count */ + image[minor].users--; + +up (&image[minor].sem); + +return 0 ; +} + +/* +* We are going ot alloc a page during init per window for small transfers. +* Small transfers will go VME -> buffer -> user space. Larger (more than a +* page) transfers will lock the user space buffer into memory and then +* transfer the data directly into the user space buffers. +*/ +static ssize_t resource_to_user (int minor,char __user *buf,size_t count, +loff_t *ppos) +{ +ssize_t retval; +ssize_t copied =0 ; + +if (count <= image[minor].size_buf) { +/* We copy to kernel buffer */ + copied =vme_master_read (image[minor].resource, + image[minor].kern_buf, count, *ppos); +if (copied <0 ) { +return (int )copied; + } + + retval =__copy_to_user (buf, image[minor].kern_buf, + (unsigned long )copied); +if (retval !=0 ) { + copied = (copied - retval); +printk ("User copy failed\n" ); +return -EINVAL ; + } + + }else { +/* XXX Need to write this */ +printk ("Currently don't support large transfers\n" ); +/* Map in pages from userspace */ + +/* Call vme_master_read to do the transfer */ +return -EINVAL ; + } + +return copied; +} + +/* +* We are going ot alloc a page during init per window for small transfers. +* Small transfers will go user space -> buffer -> VME. Larger (more than a +* page) transfers will lock the user space buffer into memory and then +* transfer the data directly from the user space buffers out to VME. +*/ +static ssize_t resource_from_user (unsigned int minor,const char *buf, +size_t count,loff_t *ppos) +{ +ssize_t retval; +ssize_t copied =0 ; + +if (count <= image[minor].size_buf) { + retval =__copy_from_user (image[minor].kern_buf, buf, + (unsigned long )count); +if (retval !=0 ) + copied = (copied - retval); +else + copied = count; + + copied =vme_master_write (image[minor].resource, + image[minor].kern_buf, copied, *ppos); + }else { +/* XXX Need to write this */ +printk ("Currently don't support large transfers\n" ); +/* Map in pages from userspace */ + +/* Call vme_master_write to do the transfer */ +return -EINVAL ; + } + +return copied; +} + +static ssize_t buffer_to_user (unsigned int minor,char __user *buf, +size_t count,loff_t *ppos) +{ +void __iomem *image_ptr; +ssize_t retval; + + image_ptr = image[minor].kern_buf + *ppos; + + retval =__copy_to_user (buf, image_ptr, (unsigned long )count); +if (retval !=0 ) { + retval = (count - retval); +printk (KERN_WARNING "Partial copy to userspace\n" ); + }else + retval = count; + +/* Return number of bytes successfully read */ +return retval; +} + +static ssize_t buffer_from_user (unsigned int minor,const char *buf, +size_t count,loff_t *ppos) +{ +void __iomem *image_ptr; +size_t retval; + + image_ptr = image[minor].kern_buf + *ppos; + + retval =__copy_from_user (image_ptr, buf, (unsigned long )count); +if (retval !=0 ) { + retval = (count - retval); +printk (KERN_WARNING "Partial copy to userspace\n" ); + }else + retval = count; + +/* Return number of bytes successfully read */ +return retval; +} + +static ssize_t vme_user_read (struct file *file,char *buf,size_t count, +loff_t * ppos) +{ +unsigned int minor =MINOR (file->f_dentry->d_inode->i_rdev); +ssize_t retval; +size_t image_size; +size_t okcount; + +down (&image[minor].sem); + +/* XXX Do we *really* want this helper - we can use vme_*_get ? */ + image_size =vme_get_size (image[minor].resource); + +/* Ensure we are starting at a valid location */ +if ((*ppos <0 ) || (*ppos > (image_size -1 ))) { +up (&image[minor].sem); +return 0 ; + } + +/* Ensure not reading past end of the image */ +if (*ppos + count > image_size) + okcount = image_size - *ppos; +else + okcount = count; + +switch (type[minor]){ +case MASTER_MINOR : + retval =resource_to_user (minor, buf, okcount, ppos); +break ; +case SLAVE_MINOR : + retval =buffer_to_user (minor, buf, okcount, ppos); +break ; +default : + retval = -EINVAL ; + } + +up (&image[minor].sem); + +if (retval >0 ) + *ppos += retval; + +return retval; +} + +static ssize_t vme_user_write (struct file *file,const char *buf,size_t count, +loff_t *ppos) +{ +unsigned int minor =MINOR (file->f_dentry->d_inode->i_rdev); +ssize_t retval; +size_t image_size; +size_t okcount; + +down (&image[minor].sem); + + image_size =vme_get_size (image[minor].resource); + +/* Ensure we are starting at a valid location */ +if ((*ppos <0 ) || (*ppos > (image_size -1 ))) { +up (&image[minor].sem); +return 0 ; + } + +/* Ensure not reading past end of the image */ +if (*ppos + count > image_size) + okcount = image_size - *ppos; +else + okcount = count; + +switch (type[minor]){ +case MASTER_MINOR : + retval =resource_from_user (minor, buf, okcount, ppos); +break ; +case SLAVE_MINOR : + retval =buffer_from_user (minor, buf, okcount, ppos); +break ; +default : + retval = -EINVAL ; + } + +up (&image[minor].sem); + +if (retval >0 ) + *ppos += retval; + +return retval; +} + +static loff_t vme_user_llseek (struct file *file,loff_t off,int whence) +{ +printk (KERN_ERR "Llseek currently incomplete\n" ); +return -EINVAL ; +} + +static int vme_user_ioctl (struct inode *inode,struct file *file, +unsigned int cmd,unsigned long arg) +{ +unsigned int minor =MINOR (inode->i_rdev); +#if 0 +int ret_val; +#endif +unsigned long copyRet; +vme_slave_t slave; + + statistics.ioctls++; +switch (type[minor]) { +case CONTROL_MINOR : +break ; +case MASTER_MINOR : +break ; +case SLAVE_MINOR : +switch (cmd) { +case VME_SET_SLAVE : + + copyRet =copy_from_user (&slave, (char *)arg, +sizeof (slave)); +if (copyRet !=0 ) { +printk (KERN_WARNING "Partial copy from " +"userspace\n" ); +return -EFAULT ; + } + +return vme_slave_set (image[minor].resource, + slave.enable, slave.vme_addr, slave.size, + image[minor].pci_buf, slave.aspace, + slave.cycle); + +break ; +#if 0 +case VME_GET_SLAVE : +vme_slave_t slave; + + ret_val =vme_slave_get (minor, &iRegs); + + copyRet =copy_to_user ((char *)arg, &slave, +sizeof (slave)); +if (copyRet !=0 ) { +printk (KERN_WARNING "Partial copy to " +"userspace\n" ); +return -EFAULT ; + } + +return ret_val; +break ; +#endif + } +break ; + } + +return -EINVAL ; +} + + +/* +* Unallocate a previously allocated buffer +*/ +static void buf_unalloc (int num) +{ +if (image[num].kern_buf) { +#ifdef VME_DEBUG +printk (KERN_DEBUG "UniverseII:Releasing buffer at %p\n" , + image[num].pci_buf); +#endif + +vme_free_consistent (image[num].resource, image[num].size_buf, + image[num].kern_buf, image[num].pci_buf); + + image[num].kern_buf =NULL ; + image[num].pci_buf =0 ; + image[num].size_buf =0 ; + +#ifdef VME_DEBUG + }else { +printk (KERN_DEBUG "UniverseII: Buffer not allocated\n" ); +#endif + } +} + +static struct vme_driver vme_user_driver = { + .name = driver_name, + .probe = vme_user_probe, +}; + + +/* +* In this simple access driver, the old behaviour is being preserved as much +* as practical. We will therefore reserve the buffers and request the images +* here so that we don't have to do it later. +*/ +static int __init vme_bridge_init (void ) +{ +int retval; +printk (KERN_INFO "VME User Space Access Driver\n" ); +printk ("vme_user_driver:%p\n" , &vme_user_driver); + retval =vme_register_driver (&vme_user_driver); +printk ("vme_register_driver returned %d\n" , retval); +return retval; +} + +/* +* This structure gets passed a device, this should be the device created at +* registration. +*/ +static int __init vme_user_probe (struct device *dev) +{ +int i, err; +char name[8 ]; + +printk ("Running vme_user_probe()\n" ); + +/* Pointer to the bridge device */ + vme_user_bridge = dev; + +/* Initialise descriptors */ +for (i =0 ; i <VME_DEVS ; i++) { + image[i].kern_buf =NULL ; + image[i].pci_buf =0 ; +init_MUTEX (&(image[i].sem)); + image[i].device =NULL ; + image[i].resource =NULL ; + image[i].users =0 ; + } + +/* Initialise statistics counters */ +reset_counters (); + +/* Assign major and minor numbers for the driver */ + err =register_chrdev_region (MKDEV (VME_MAJOR ,0 ),VME_DEVS , + driver_name); +if (err) { +printk (KERN_WARNING "%s: Error getting Major Number %d for " +"driver.\n" , driver_name,VME_MAJOR ); + goto err_region; + } + +/* Register the driver as a char device */ + vme_user_cdev =cdev_alloc (); + vme_user_cdev->ops = &vme_user_fops; + vme_user_cdev->owner =THIS_MODULE ; + err =cdev_add (vme_user_cdev,MKDEV (VME_MAJOR ,0 ),VME_DEVS ); +if (err) { +printk (KERN_WARNING "%s: cdev_all failed\n" , driver_name); + goto err_char; + } + +/* Request slave resources and allocate buffers (128kB wide) */ +for (i =SLAVE_MINOR ; i < (SLAVE_MAX +1 ); i++) { +/* XXX Need to properly request attributes */ + image[i].resource =vme_slave_request (vme_user_bridge, +VME_A16 ,VME_SCT ); +if (image[i].resource ==NULL ) { +printk (KERN_WARNING "Unable to allocate slave " +"resource\n" ); + goto err_buf; + } + image[i].size_buf =PCI_BUF_SIZE ; + image[i].kern_buf =vme_alloc_consistent (image[i].resource, + image[i].size_buf, &(image[i].pci_buf)); +if (image[i].kern_buf ==NULL ) { +printk (KERN_WARNING "Unable to allocate memory for " +"buffer\n" ); + image[i].pci_buf =0 ; +vme_slave_free (image[i].resource); + err = -ENOMEM ; + goto err_buf; + } + } + +/* +* Request master resources allocate page sized buffers for small +* reads and writes +*/ +for (i =MASTER_MINOR ; i < (MASTER_MAX +1 ); i++) { +/* XXX Need to properly request attributes */ + image[i].resource =vme_master_request (vme_user_bridge, +VME_A32 ,VME_SCT ,VME_D32 ); +if (image[i].resource ==NULL ) { +printk (KERN_WARNING "Unable to allocate master " +"resource\n" ); + goto err_buf; + } + image[i].size_buf =PAGE_SIZE ; + image[i].kern_buf =vme_alloc_consistent (image[i].resource, + image[i].size_buf, &(image[i].pci_buf)); +if (image[i].kern_buf ==NULL ) { +printk (KERN_WARNING "Unable to allocate memory for " +"buffer\n" ); + image[i].pci_buf =0 ; +vme_master_free (image[i].resource); + err = -ENOMEM ; + goto err_buf; + } + } + +/* Setup some debug windows */ +for (i =SLAVE_MINOR ; i < (SLAVE_MAX +1 ); i++) { + err =vme_slave_set (image[i].resource,1 ,0x4000 *(i-4 ), +0x4000 , image[i].pci_buf,VME_A16 , +VME_SCT |VME_SUPER |VME_USER |VME_PROG |VME_DATA ); +if (err !=0 ) { +printk (KERN_WARNING "Failed to configure window\n" ); + goto err_buf; + } + } +for (i =MASTER_MINOR ; i < (MASTER_MAX +1 ); i++) { + err =vme_master_set (image[i].resource,1 , + (0x10000 + (0x10000 *i)),0x10000 , +VME_A32 ,VME_SCT |VME_USER |VME_DATA ,VME_D32 ); +if (err !=0 ) { +printk (KERN_WARNING "Failed to configure window\n" ); + goto err_buf; + } + } + +/* Create sysfs entries - on udev systems this creates the dev files */ + vme_user_sysfs_class =class_create (THIS_MODULE , driver_name); +if (IS_ERR (vme_user_sysfs_class)) { +printk (KERN_ERR "Error creating vme_user class.\n" ); + err =PTR_ERR (vme_user_sysfs_class); + goto err_class; + } + +/* Add sysfs Entries */ +for (i=0 ; i<VME_DEVS ; i++) { +switch (type[i]) { +case MASTER_MINOR : +sprintf (name,"bus/vme/m%%d" ); +break ; +case CONTROL_MINOR : +sprintf (name,"bus/vme/ctl" ); +break ; +case SLAVE_MINOR : +sprintf (name,"bus/vme/s%%d" ); +break ; +default : + err = -EINVAL ; + goto err_sysfs; +break ; + } + + image[i].device = +device_create (vme_user_sysfs_class,NULL , +MKDEV (VME_MAJOR , i),NULL , name, + (type[i] ==SLAVE_MINOR )? i - (MASTER_MAX +1 ) : i); +if (IS_ERR (image[i].device)) { +printk ("%s: Error creating sysfs device\n" , + driver_name); + err =PTR_ERR (image[i].device); + goto err_sysfs; + } + } + +/* XXX Run tests */ +/* +tests(); +*/ + +return 0 ; + +/* Ensure counter set correcty to destroy all sysfs devices */ + i =VME_DEVS ; +err_sysfs: +while (i >0 ){ + i--; +device_destroy (vme_user_sysfs_class,MKDEV (VME_MAJOR , i)); + } +class_destroy (vme_user_sysfs_class); + +/* Ensure counter set correcty to unalloc all slave buffers */ + i =SLAVE_MAX +1 ; +err_buf: +while (i >SLAVE_MINOR ){ + i--; +vme_slave_set (image[i].resource,0 ,0 ,0 ,0 ,VME_A32 ,0 ); +vme_slave_free (image[i].resource); +buf_unalloc (i); + } +err_class: +cdev_del (vme_user_cdev); +err_char: +unregister_chrdev_region (MKDEV (VME_MAJOR ,0 ),VME_DEVS ); +err_region: +return err; +} + +static void __exit vme_bridge_exit(void) +{ +int i; + +/* Remove sysfs Entries */ +for (i=0 ; i<VME_DEVS ; i++) { +device_destroy (vme_user_sysfs_class,MKDEV (VME_MAJOR , i)); + } +class_destroy (vme_user_sysfs_class); + +for (i =SLAVE_MINOR ; i < (SLAVE_MAX +1 ); i++) { +buf_unalloc (i); + } + +/* Unregister device driver */ +cdev_del (vme_user_cdev); + +/* Unregiser the major and minor device numbers */ +unregister_chrdev_region (MKDEV (VME_MAJOR ,0 ),VME_DEVS ); +} + +MODULE_DESCRIPTION ("VME User Space Access Driver" ); +MODULE_AUTHOR ("Martyn Welch <[email protected]" ); +MODULE_LICENSE ("GPL" ); + +module_init (vme_bridge_init); +module_exit (vme_bridge_exit);drivers/staging/vme/devices/vme_user.h new file +43
-0@@ -0,0 +1,43 @@ + +/* +* VMEbus Master Window Configuration Structure +*/ +typedef struct { +char enable;/* State of Window */ +unsigned long long vme_addr;/* Starting Address on the VMEbus */ +unsigned long long size;/* Window Size */ +vme_address_t aspace;/* Address Space */ +vme_cycle_t cycle;/* Cycle properties */ +vme_width_t dwidth;/* Maximum Data Width */ +#if 0 +char prefetchEnable;/* Prefetch Read Enable State */ +int prefetchSize;/* Prefetch Read Size (Cache Lines) */ +char wrPostEnable;/* Write Post State */ +#endif +}vme_master_t ; + + +/* +* IOCTL Commands and structures +*/ + +/* Magic number for use in ioctls */ +#define VME_IOC_MAGIC 0xAE + + +/* VMEbus Slave Window Configuration Structure */ +typedef struct { +char enable;/* State of Window */ +unsigned long long vme_addr;/* Starting Address on the VMEbus */ +unsigned long long size;/* Window Size */ +vme_address_t aspace;/* Address Space */ +vme_cycle_t cycle;/* Cycle properties */ +#if 0 +char wrPostEnable;/* Write Post State */ +char rmwLock;/* Lock PCI during RMW Cycles */ +char data64BitCapable;/* non-VMEbus capable of 64-bit Data */ +#endif +}vme_slave_t ; + +#define VME_SET_SLAVE _IOW(VME_IOC_MAGIC, 10, vme_slave_t) +drivers/staging/vme/Kconfig +2
-2@@ -11,7 +11,7 @@ menuconfig VME if VME #source "drivers/staging/vme/bridges/Kconfig" -# -#source "drivers/staging/vme/devices/Kconfig" + +source "drivers/staging/vme/devices/Kconfig" endif # VMEdrivers/staging/vme/Makefile +1
-1@@ -4,4 +4,4 @@ obj-$(CONFIG_VME) += vme.o #obj-y += bridges/ -#obj-y += devices/ +obj-y += devices/
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 10 lines · Emilio G. Cota
(oops, just correcting Sebastien's e-mail address)
Quoted 6 lines · Emilio G. Cota
I remember we had a look at the vmelinux interface, but it really was so limited that we decided to get rid of it altogether. Do you really think it's worth bringing it back to life? Since it's never been merged into the kernel, why don't we just get it right this time and forget about it?
I'm happy to forget about it. I've come to the conclusion that the IOCTLs are unmaintainable anyway. This patch at the moment is acting as a useful tool for exercising the underlying layers and provides a bit of example code (or will as the patches I have recently sent to the Linux Driver Project propagate through).
Quoted 38 lines
Quoted 38 lines
Instead of using that we implemented a heretic IOCTL-based interface for user-space; at least with it you could create a driver (with no interrupt support) for a device in user-space. We actually don't use it, but well, just to get inspiration, here it goes:
/**
* \name Window management ioctl numbers
*/
/** Get a physical window attributes */
#defineVME_IOCTL_GET_WINDOW_ATTR_IOWR('V', 0, structvme_mapping)
/** Create a physical window */
#defineVME_IOCTL_CREATE_WINDOW_IOW( 'V', 1, structvme_mapping)
/** Destroy a physical window */
#defineVME_IOCTL_DESTROY_WINDOW_IOW( 'V', 2, int)
/** Create a mapping over a physical window */
#defineVME_IOCTL_FIND_MAPPING_IOWR('V', 3, structvme_mapping)
/** Remove a mapping */
#defineVME_IOCTL_RELEASE_MAPPING_IOW( 'V', 4, structvme_mapping)
/** Get the create on find failed flag */
#defineVME_IOCTL_GET_CREATE_ON_FIND_FAIL_IOR( 'V', 5, unsigned
int)
/** Set the create on find failed flag */
#defineVME_IOCTL_SET_CREATE_ON_FIND_FAIL_IOW( 'V', 6, unsigned
int)
/** Get the destroy on remove flag */
#defineVME_IOCTL_GET_DESTROY_ON_REMOVE_IOR( 'V', 7, unsigned int)/** Set the destroy on remove flag */
#defineVME_IOCTL_SET_DESTROY_ON_REMOVE_IOW( 'V', 8, unsigned int)/** Get bus error status */
#defineVME_IOCTL_GET_BUS_ERROR_IOR( 'V', 9, unsigned int)
/** Start a DMA transfer */
#defineVME_IOCTL_START_DMA_IOWR('V', 10, struct vme_dma)
Quoted 5 lines
Quoted 5 lines
[ grab the code from here: http://repo.or.cz/w/tsi148vmebridge.git .
Please note there's some backwards-compatible crap for old user-space LynxOS code, called CES. Ignore it. ]
I am moving the interface in that direction, I remain unconvinced about the contents of your vme_mapping structure, it's too tsi-148 specific.
Quoted 4 lines
Quoted 4 lines
By the way, please try to be consistent with naming, e.g. I see window, then see "image", and I have to think hard to see whether they mean the same or not.. err, do they?
Yes - I don't know where image came from, probably one of the old VME drivers I looked at. I will look at getting rid of that term.
Martyn
Quoted 883 lines
Quoted 883 lines
E.
(I'll leave this here because of added Cc)
Quoted 878 lines · Greg K-H
From: Martyn Welch <[email protected]>
Adds a VME userspace access driver
Signed-off-by Martyn Welch <[email protected]>Signed-off-by Greg Kroah-Hartman <[email protected]>--- drivers/staging/vme/Kconfig | 4 drivers/staging/vme/Makefile | 2 drivers/staging/vme/devices/Kconfig | 8 drivers/staging/vme/devices/Makefile | 5 drivers/staging/vme/devices/vme_user.c | 771 +++++++++++++++++++++++++++++++++ drivers/staging/vme/devices/vme_user.h | 43 + 6 files changed, 830 insertions(+), 3 deletions(-)drivers/staging/vme/devices/Kconfig new file +8
-0@@ -0,0 +1,8 @@ +comment "VME Device Drivers" + +config VME_USER + tristate "VME user space access driver" + help + If you say Y here you want to be able to access a limited number of + VME windows in a manner at least semi-compatible with the interface + provided with the original driver at http://vmelinux.org/.drivers/staging/vme/devices/Makefile new file +5
-0@@ -0,0 +1,5 @@ +# +# Makefile for the VME device drivers. +# + +obj-$(CONFIG_VME_USER) += vme_user.odrivers/staging/vme/devices/vme_user.c new file +771
-0@@ -0,0 +1,771 @@ +/* +* VMEbus User access driver +* +* Author: Martyn Welch <[email protected]> +* Copyright 2008 GE Fanuc Intelligent Platforms Embedded Systems, Inc. +* +* Based on work by: +* Tom Armistead and Ajit Prem +* Copyright 2004 Motorola Inc. +* +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +#include <linux/cdev.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/pagemap.h> +#include <linux/pci.h> +#include <linux/semaphore.h> +#include <linux/spinlock.h> +#include <linux/syscalls.h> +#include <linux/types.h> +#include <linux/version.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#include "../vme.h" +#include "vme_user.h" + +/* Currently Documentation/devices.txt defines the following for VME: +* +* 221 char VME bus +* 0 = /dev/bus/vme/m0 First master image +* 1 = /dev/bus/vme/m1 Second master image +* 2 = /dev/bus/vme/m2 Third master image +* 3 = /dev/bus/vme/m3 Fourth master image +* 4 = /dev/bus/vme/s0 First slave image +* 5 = /dev/bus/vme/s1 Second slave image +* 6 = /dev/bus/vme/s2 Third slave image +* 7 = /dev/bus/vme/s3 Fourth slave image +* 8 = /dev/bus/vme/ctl Control +* +* It is expected that all VME bus drivers will use the +* same interface. For interface documentation see +* http://www.vmelinux.org/. +* +* However the VME driver at http://www.vmelinux.org/ is rather old and doesn't +* even support the tsi148 chipset (which has 8 master and 8 slave windows). +* We'll run with this or now as far as possible, however it probably makes +* sense to get rid of the old mappings and just do everything dynamically. +* +* So for now, we'll restrict the driver to providing 4 masters and 4 slaves as +* defined above and try to support at least some of the interface from +* http://www.vmelinux.org/ as an alternative drive can be written providing a +* saner interface later. +*/ +#define VME_MAJOR 221/* VME Major Device Number */ +#define VME_DEVS 9/* Number of dev entries */ + +#define MASTER_MINOR 0 +#define MASTER_MAX 3 +#define SLAVE_MINOR 4 +#define SLAVE_MAX 7 +#define CONTROL_MINOR 8 + +#define PCI_BUF_SIZE 0x20000/* Size of one slave image buffer */ + +/* +* Structure to handle image related parameters. +*/ +typedef struct { +void __iomem *kern_buf;/* Buffer address in kernel space */ +dma_addr_t pci_buf;/* Buffer address in PCI address space */ +unsigned long long size_buf;/* Buffer size */ +struct semaphore sem;/* Semaphore for locking image */ +struct device *device;/* Sysfs device */ +struct vme_resource *resource;/* VME resource */ +int users;/* Number of current users */ +}image_desc_t ; +static image_desc_t image[VME_DEVS ]; + +typedef struct { +unsigned long reads; +unsigned long writes; +unsigned long ioctls; +unsigned long irqs; +unsigned long berrs; +unsigned long dmaErrors; +unsigned long timeouts; +unsigned long external; +}driver_stats_t ; +static driver_stats_t statistics; + +struct cdev *vme_user_cdev;/* Character device */ +struct class *vme_user_sysfs_class;/* Sysfs class */ +struct device *vme_user_bridge;/* Pointer to the bridge device */ + +static char driver_name[] ="vme_user" ; + +static const int type[VME_DEVS ] = {MASTER_MINOR ,MASTER_MINOR , +MASTER_MINOR ,MASTER_MINOR , +SLAVE_MINOR ,SLAVE_MINOR , +SLAVE_MINOR ,SLAVE_MINOR , +CONTROL_MINOR + }; + + +static int vme_user_open (struct inode *,struct file *); +static int vme_user_release (struct inode *,struct file *); +static ssize_t vme_user_read (struct file *,char *,size_t ,loff_t *); +static ssize_t vme_user_write (struct file *,const char *,size_t ,loff_t *); +static loff_t vme_user_llseek (struct file *,loff_t ,int ); +static int vme_user_ioctl (struct inode *,struct file *,unsigned int , +unsigned long ); + +static int __init vme_user_probe (struct device *dev); + +static struct file_operations vme_user_fops = { + .open = vme_user_open, + .release = vme_user_release, + .read = vme_user_read, + .write = vme_user_write, + .llseek = vme_user_llseek, + .ioctl = vme_user_ioctl, +}; + + +/* +* Reset all the statistic counters +*/ +static void reset_counters (void ) +{ + statistics.reads =0 ; + statistics.writes =0 ; + statistics.ioctls =0 ; + statistics.irqs =0 ; + statistics.berrs =0 ; + statistics.dmaErrors =0 ; + statistics.timeouts =0 ; +} + +void lmcall (int monitor) +{ +printk ("Caught Location Monitor %d access\n" , monitor); +} + +static void tests (void ) +{ +struct vme_resource *dma_res; +struct vme_dma_list *dma_list; +struct vme_dma_attr *pattern_attr, *vme_attr; + +int retval; +unsigned int data; + +printk ("Running VME DMA test\n" ); + dma_res =vme_request_dma (vme_user_bridge); + dma_list =vme_new_dma_list (dma_res); + pattern_attr =vme_dma_pattern_attribute (0x0 , +VME_DMA_PATTERN_WORD | +VME_DMA_PATTERN_INCREMENT ); + vme_attr =vme_dma_vme_attribute (0x10000 ,VME_A32 , +VME_SCT ,VME_D32 ); + retval =vme_dma_list_add (dma_list, pattern_attr, + vme_attr,0x10000 ); +#if 0 +vme_dma_free_attribute (vme_attr); + vme_attr =vme_dma_vme_attribute (0x20000 ,VME_A32 , +VME_SCT ,VME_D32 ); + retval =vme_dma_list_add (dma_list, pattern_attr, + vme_attr,0x10000 ); +#endif + retval =vme_dma_list_exec (dma_list); +vme_dma_free_attribute (pattern_attr); +vme_dma_free_attribute (vme_attr); +vme_dma_list_free (dma_list); +#if 0 +printk ("Generating a VME interrupt\n" ); +vme_generate_irq (dma_res,0x3 ,0xaa ); +printk ("Interrupt returned\n" ); +#endif +vme_dma_free (dma_res); + +/* Attempt RMW */ + data =vme_master_rmw (image[0 ].resource,0x80000000 ,0x00000000 , +0x80000000 ,0 ); +printk ("RMW returned 0x%8.8x\n" , data); + + +/* Location Monitor */ +printk ("vme_lm_set:%d\n" ,vme_lm_set (vme_user_bridge,0x60000 ,VME_A32 ,VME_SCT |VME_USER |VME_DATA )); +printk ("vme_lm_attach:%d\n" ,vme_lm_attach (vme_user_bridge,0 , lmcall)); + +printk ("Board in VME slot:%d\n" ,vme_slot_get (vme_user_bridge)); +} + +static int vme_user_open (struct inode *inode,struct file *file) +{ +int err; +unsigned int minor =MINOR (inode->i_rdev); + +down (&image[minor].sem); +/* Only allow device to be opened if a resource is allocated */ +if (image[minor].resource ==NULL ) { +printk (KERN_ERR "No resources allocated for device\n" ); + err = -EINVAL ; + goto err_res; + } + +/* Increment user count */ + image[minor].users++; + +up (&image[minor].sem); + +return 0 ; + +err_res: +up (&image[minor].sem); + +return err; +} + +static int vme_user_release (struct inode *inode,struct file *file) +{ +unsigned int minor =MINOR (inode->i_rdev); + +down (&image[minor].sem); + +/* Decrement user count */ + image[minor].users--; + +up (&image[minor].sem); + +return 0 ; +} + +/* +* We are going ot alloc a page during init per window for small transfers. +* Small transfers will go VME -> buffer -> user space. Larger (more than a +* page) transfers will lock the user space buffer into memory and then +* transfer the data directly into the user space buffers. +*/ +static ssize_t resource_to_user (int minor,char __user *buf,size_t count, +loff_t *ppos) +{ +ssize_t retval; +ssize_t copied =0 ; + +if (count <= image[minor].size_buf) { +/* We copy to kernel buffer */ + copied =vme_master_read (image[minor].resource, + image[minor].kern_buf, count, *ppos); +if (copied <0 ) { +return (int )copied; + } + + retval =__copy_to_user (buf, image[minor].kern_buf, + (unsigned long )copied); +if (retval !=0 ) { + copied = (copied - retval); +printk ("User copy failed\n" ); +return -EINVAL ; + } + + }else { +/* XXX Need to write this */ +printk ("Currently don't support large transfers\n" ); +/* Map in pages from userspace */ + +/* Call vme_master_read to do the transfer */ +return -EINVAL ; + } + +return copied; +} + +/* +* We are going ot alloc a page during init per window for small transfers. +* Small transfers will go user space -> buffer -> VME. Larger (more than a +* page) transfers will lock the user space buffer into memory and then +* transfer the data directly from the user space buffers out to VME. +*/ +static ssize_t resource_from_user (unsigned int minor,const char *buf, +size_t count,loff_t *ppos) +{ +ssize_t retval; +ssize_t copied =0 ; + +if (count <= image[minor].size_buf) { + retval =__copy_from_user (image[minor].kern_buf, buf, + (unsigned long )count); +if (retval !=0 ) + copied = (copied - retval); +else + copied = count; + + copied =vme_master_write (image[minor].resource, + image[minor].kern_buf, copied, *ppos); + }else { +/* XXX Need to write this */ +printk ("Currently don't support large transfers\n" ); +/* Map in pages from userspace */ + +/* Call vme_master_write to do the transfer */ +return -EINVAL ; + } + +return copied; +} + +static ssize_t buffer_to_user (unsigned int minor,char __user *buf, +size_t count,loff_t *ppos) +{ +void __iomem *image_ptr; +ssize_t retval; + + image_ptr = image[minor].kern_buf + *ppos; + + retval =__copy_to_user (buf, image_ptr, (unsigned long )count); +if (retval !=0 ) { + retval = (count - retval); +printk (KERN_WARNING "Partial copy to userspace\n" ); + }else + retval = count; + +/* Return number of bytes successfully read */ +return retval; +} + +static ssize_t buffer_from_user (unsigned int minor,const char *buf, +size_t count,loff_t *ppos) +{ +void __iomem *image_ptr; +size_t retval; + + image_ptr = image[minor].kern_buf + *ppos; + + retval =__copy_from_user (image_ptr, buf, (unsigned long )count); +if (retval !=0 ) { + retval = (count - retval); +printk (KERN_WARNING "Partial copy to userspace\n" ); + }else + retval = count; + +/* Return number of bytes successfully read */ +return retval; +} + +static ssize_t vme_user_read (struct file *file,char *buf,size_t count, +loff_t * ppos) +{ +unsigned int minor =MINOR (file->f_dentry->d_inode->i_rdev); +ssize_t retval; +size_t image_size; +size_t okcount; + +down (&image[minor].sem); + +/* XXX Do we *really* want this helper - we can use vme_*_get ? */ + image_size =vme_get_size (image[minor].resource); + +/* Ensure we are starting at a valid location */ +if ((*ppos <0 ) || (*ppos > (image_size -1 ))) { +up (&image[minor].sem); +return 0 ; + } + +/* Ensure not reading past end of the image */ +if (*ppos + count > image_size) + okcount = image_size - *ppos; +else + okcount = count; + +switch (type[minor]){ +case MASTER_MINOR : + retval =resource_to_user (minor, buf, okcount, ppos); +break ; +case SLAVE_MINOR : + retval =buffer_to_user (minor, buf, okcount, ppos); +break ; +default : + retval = -EINVAL ; + } + +up (&image[minor].sem); + +if (retval >0 ) + *ppos += retval; + +return retval; +} + +static ssize_t vme_user_write (struct file *file,const char *buf,size_t count, +loff_t *ppos) +{ +unsigned int minor =MINOR (file->f_dentry->d_inode->i_rdev); +ssize_t retval; +size_t image_size; +size_t okcount; + +down (&image[minor].sem); + + image_size =vme_get_size (image[minor].resource); + +/* Ensure we are starting at a valid location */ +if ((*ppos <0 ) || (*ppos > (image_size -1 ))) { +up (&image[minor].sem); +return 0 ; + } + +/* Ensure not reading past end of the image */ +if (*ppos + count > image_size) + okcount = image_size - *ppos; +else + okcount = count; + +switch (type[minor]){ +case MASTER_MINOR : + retval =resource_from_user (minor, buf, okcount, ppos); +break ; +case SLAVE_MINOR : + retval =buffer_from_user (minor, buf, okcount, ppos); +break ; +default : + retval = -EINVAL ; + } + +up (&image[minor].sem); + +if (retval >0 ) + *ppos += retval; + +return retval; +} + +static loff_t vme_user_llseek (struct file *file,loff_t off,int whence) +{ +printk (KERN_ERR "Llseek currently incomplete\n" ); +return -EINVAL ; +} + +static int vme_user_ioctl (struct inode *inode,struct file *file, +unsigned int cmd,unsigned long arg) +{ +unsigned int minor =MINOR (inode->i_rdev); +#if 0 +int ret_val; +#endif +unsigned long copyRet; +vme_slave_t slave; + + statistics.ioctls++; +switch (type[minor]) { +case CONTROL_MINOR : +break ; +case MASTER_MINOR : +break ; +case SLAVE_MINOR : +switch (cmd) { +case VME_SET_SLAVE : + + copyRet =copy_from_user (&slave, (char *)arg, +sizeof (slave)); +if (copyRet !=0 ) { +printk (KERN_WARNING "Partial copy from " +"userspace\n" ); +return -EFAULT ; + } + +return vme_slave_set (image[minor].resource, + slave.enable, slave.vme_addr, slave.size, + image[minor].pci_buf, slave.aspace, + slave.cycle); + +break ; +#if 0 +case VME_GET_SLAVE : +vme_slave_t slave; + + ret_val =vme_slave_get (minor, &iRegs); + + copyRet =copy_to_user ((char *)arg, &slave, +sizeof (slave)); +if (copyRet !=0 ) { +printk (KERN_WARNING "Partial copy to " +"userspace\n" ); +return -EFAULT ; + } + +return ret_val; +break ; +#endif + } +break ; + } + +return -EINVAL ; +} + + +/* +* Unallocate a previously allocated buffer +*/ +static void buf_unalloc (int num) +{ +if (image[num].kern_buf) { +#ifdef VME_DEBUG +printk (KERN_DEBUG "UniverseII:Releasing buffer at %p\n" , + image[num].pci_buf); +#endif + +vme_free_consistent (image[num].resource, image[num].size_buf, + image[num].kern_buf, image[num].pci_buf); + + image[num].kern_buf =NULL ; + image[num].pci_buf =0 ; + image[num].size_buf =0 ; + +#ifdef VME_DEBUG + }else { +printk (KERN_DEBUG "UniverseII: Buffer not allocated\n" ); +#endif + } +} + +static struct vme_driver vme_user_driver = { + .name = driver_name, + .probe = vme_user_probe, +}; + + +/* +* In this simple access driver, the old behaviour is being preserved as much +* as practical. We will therefore reserve the buffers and request the images +* here so that we don't have to do it later. +*/ +static int __init vme_bridge_init (void ) +{ +int retval; +printk (KERN_INFO "VME User Space Access Driver\n" ); +printk ("vme_user_driver:%p\n" , &vme_user_driver); + retval =vme_register_driver (&vme_user_driver); +printk ("vme_register_driver returned %d\n" , retval); +return retval; +} + +/* +* This structure gets passed a device, this should be the device created at +* registration. +*/ +static int __init vme_user_probe (struct device *dev) +{ +int i, err; +char name[8 ]; + +printk ("Running vme_user_probe()\n" ); + +/* Pointer to the bridge device */ + vme_user_bridge = dev; + +/* Initialise descriptors */ +for (i =0 ; i <VME_DEVS ; i++) { + image[i].kern_buf =NULL ; + image[i].pci_buf =0 ; +init_MUTEX (&(image[i].sem)); + image[i].device =NULL ; + image[i].resource =NULL ; + image[i].users =0 ; + } + +/* Initialise statistics counters */ +reset_counters (); + +/* Assign major and minor numbers for the driver */ + err =register_chrdev_region (MKDEV (VME_MAJOR ,0 ),VME_DEVS , + driver_name); +if (err) { +printk (KERN_WARNING "%s: Error getting Major Number %d for " +"driver.\n" , driver_name,VME_MAJOR ); + goto err_region; + } + +/* Register the driver as a char device */ + vme_user_cdev =cdev_alloc (); + vme_user_cdev->ops = &vme_user_fops; + vme_user_cdev->owner =THIS_MODULE ; + err =cdev_add (vme_user_cdev,MKDEV (VME_MAJOR ,0 ),VME_DEVS ); +if (err) { +printk (KERN_WARNING "%s: cdev_all failed\n" , driver_name); + goto err_char; + } + +/* Request slave resources and allocate buffers (128kB wide) */ +for (i =SLAVE_MINOR ; i < (SLAVE_MAX +1 ); i++) { +/* XXX Need to properly request attributes */ + image[i].resource =vme_slave_request (vme_user_bridge, +VME_A16 ,VME_SCT ); +if (image[i].resource ==NULL ) { +printk (KERN_WARNING "Unable to allocate slave " +"resource\n" ); + goto err_buf; + } + image[i].size_buf =PCI_BUF_SIZE ; + image[i].kern_buf =vme_alloc_consistent (image[i].resource, + image[i].size_buf, &(image[i].pci_buf)); +if (image[i].kern_buf ==NULL ) { +printk (KERN_WARNING "Unable to allocate memory for " +"buffer\n" ); + image[i].pci_buf =0 ; +vme_slave_free (image[i].resource); + err = -ENOMEM ; + goto err_buf; + } + } + +/* +* Request master resources allocate page sized buffers for small +* reads and writes +*/ +for (i =MASTER_MINOR ; i < (MASTER_MAX +1 ); i++) { +/* XXX Need to properly request attributes */ + image[i].resource =vme_master_request (vme_user_bridge, +VME_A32 ,VME_SCT ,VME_D32 ); +if (image[i].resource ==NULL ) { +printk (KERN_WARNING "Unable to allocate master " +"resource\n" ); + goto err_buf; + } + image[i].size_buf =PAGE_SIZE ; + image[i].kern_buf =vme_alloc_consistent (image[i].resource, + image[i].size_buf, &(image[i].pci_buf)); +if (image[i].kern_buf ==NULL ) { +printk (KERN_WARNING "Unable to allocate memory for " +"buffer\n" ); + image[i].pci_buf =0 ; +vme_master_free (image[i].resource); + err = -ENOMEM ; + goto err_buf; + } + } + +/* Setup some debug windows */ +for (i =SLAVE_MINOR ; i < (SLAVE_MAX +1 ); i++) { + err =vme_slave_set (image[i].resource,1 ,0x4000 *(i-4 ), +0x4000 , image[i].pci_buf,VME_A16 , +VME_SCT |VME_SUPER |VME_USER |VME_PROG |VME_DATA ); +if (err !=0 ) { +printk (KERN_WARNING "Failed to configure window\n" ); + goto err_buf; + } + } +for (i =MASTER_MINOR ; i < (MASTER_MAX +1 ); i++) { + err =vme_master_set (image[i].resource,1 , + (0x10000 + (0x10000 *i)),0x10000 , +VME_A32 ,VME_SCT |VME_USER |VME_DATA ,VME_D32 ); +if (err !=0 ) { +printk (KERN_WARNING "Failed to configure window\n" ); + goto err_buf; + } + } + +/* Create sysfs entries - on udev systems this creates the dev files */ + vme_user_sysfs_class =class_create (THIS_MODULE , driver_name); +if (IS_ERR (vme_user_sysfs_class)) { +printk (KERN_ERR "Error creating vme_user class.\n" ); + err =PTR_ERR (vme_user_sysfs_class); + goto err_class; + } + +/* Add sysfs Entries */ +for (i=0 ; i<VME_DEVS ; i++) { +switch (type[i]) { +case MASTER_MINOR : +sprintf (name,"bus/vme/m%%d" ); +break ; +case CONTROL_MINOR : +sprintf (name,"bus/vme/ctl" ); +break ; +case SLAVE_MINOR : +sprintf (name,"bus/vme/s%%d" ); +break ; +default : + err = -EINVAL ; + goto err_sysfs; +break ; + } + + image[i].device = +device_create (vme_user_sysfs_class,NULL , +MKDEV (VME_MAJOR , i),NULL , name, + (type[i] ==SLAVE_MINOR )? i - (MASTER_MAX +1 ) : i); +if (IS_ERR (image[i].device)) { +printk ("%s: Error creating sysfs device\n" , + driver_name); + err =PTR_ERR (image[i].device); + goto err_sysfs; + } + } + +/* XXX Run tests */ +/* +tests(); +*/ + +return 0 ; + +/* Ensure counter set correcty to destroy all sysfs devices */ + i =VME_DEVS ; +err_sysfs: +while (i >0 ){ + i--; +device_destroy (vme_user_sysfs_class,MKDEV (VME_MAJOR , i)); + } +class_destroy (vme_user_sysfs_class); + +/* Ensure counter set correcty to unalloc all slave buffers */ + i =SLAVE_MAX +1 ; +err_buf: +while (i >SLAVE_MINOR ){ + i--; +vme_slave_set (image[i].resource,0 ,0 ,0 ,0 ,VME_A32 ,0 ); +vme_slave_free (image[i].resource); +buf_unalloc (i); + } +err_class: +cdev_del (vme_user_cdev); +err_char: +unregister_chrdev_region (MKDEV (VME_MAJOR ,0 ),VME_DEVS ); +err_region: +return err; +} + +static void __exit vme_bridge_exit(void) +{ +int i; + +/* Remove sysfs Entries */ +for (i=0 ; i<VME_DEVS ; i++) { +device_destroy (vme_user_sysfs_class,MKDEV (VME_MAJOR , i)); + } +class_destroy (vme_user_sysfs_class); + +for (i =SLAVE_MINOR ; i < (SLAVE_MAX +1 ); i++) { +buf_unalloc (i); + } + +/* Unregister device driver */ +cdev_del (vme_user_cdev); + +/* Unregiser the major and minor device numbers */ +unregister_chrdev_region (MKDEV (VME_MAJOR ,0 ),VME_DEVS ); +} + +MODULE_DESCRIPTION ("VME User Space Access Driver" ); +MODULE_AUTHOR ("Martyn Welch <[email protected]" ); +MODULE_LICENSE ("GPL" ); + +module_init (vme_bridge_init); +module_exit (vme_bridge_exit);drivers/staging/vme/devices/vme_user.h new file +43
-0@@ -0,0 +1,43 @@ + +/* +* VMEbus Master Window Configuration Structure +*/ +typedef struct { +char enable;/* State of Window */ +unsigned long long vme_addr;/* Starting Address on the VMEbus */ +unsigned long long size;/* Window Size */ +vme_address_t aspace;/* Address Space */ +vme_cycle_t cycle;/* Cycle properties */ +vme_width_t dwidth;/* Maximum Data Width */ +#if 0 +char prefetchEnable;/* Prefetch Read Enable State */ +int prefetchSize;/* Prefetch Read Size (Cache Lines) */ +char wrPostEnable;/* Write Post State */ +#endif +}vme_master_t ; + + +/* +* IOCTL Commands and structures +*/ + +/* Magic number for use in ioctls */ +#define VME_IOC_MAGIC 0xAE + + +/* VMEbus Slave Window Configuration Structure */ +typedef struct { +char enable;/* State of Window */ +unsigned long long vme_addr;/* Starting Address on the VMEbus */ +unsigned long long size;/* Window Size */ +vme_address_t aspace;/* Address Space */ +vme_cycle_t cycle;/* Cycle properties */ +#if 0 +char wrPostEnable;/* Write Post State */ +char rmwLock;/* Lock PCI during RMW Cycles */ +char data64BitCapable;/* non-VMEbus capable of 64-bit Data */ +#endif +}vme_slave_t ; + +#define VME_SET_SLAVE _IOW(VME_IOC_MAGIC, 10, vme_slave_t) +drivers/staging/vme/Kconfig +2
-2@@ -11,7 +11,7 @@ menuconfig VME if VME #source "drivers/staging/vme/bridges/Kconfig" -# -#source "drivers/staging/vme/devices/Kconfig" + +source "drivers/staging/vme/devices/Kconfig" endif # VMEdrivers/staging/vme/Makefile +1
-1@@ -4,4 +4,4 @@ obj-$(CONFIG_VME) += vme.o #obj-y += bridges/ -#obj-y += devices/ +obj-y += devices/
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 2 lines · Martyn Welch
Quoted 2 lines
Quoted 2 lines
Instead of using that we implemented a heretic IOCTL-based interface for user-space; at least with it you could create a
[ snip ]
Quoted 4 lines
Quoted 2 lines
Quoted 2 lines
#define
VME_IOCTL_START_DMA_IOWR('V', 10, struct vme_dma)I am moving the interface in that direction, I remain unconvinced about the contents of your
vme_mappingstructure, it's too tsi-148 specific.
Could you please point out why is too tsi148-specific?
The point here is that the driver should know *nothing* about windows, etc. What it should just know is:
- I want a mapping of a certain size to VME address X The struct provides exactly this.
Also, could you please send me (off-list) documentation of the Universe bridge? It'd be useful for the work on the generic layer.
E.
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 10 lines · Emilio G. Cota
Quoted 3 lines · Martyn Welch
Quoted 3 lines
Quoted 3 lines
Instead of using that we implemented a heretic IOCTL-based interface for user-space; at least with it you could create a
[ snip ]
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
#define
VME_IOCTL_START_DMA_IOWR('V', 10, struct vme_dma)
Quoted 9 lines
Quoted 2 lines
I am moving the interface in that direction, I remain unconvinced about the contents of your
vme_mappingstructure, it's too tsi-148 specific.Could you please point out why is too tsi148-specific?
The point here is that the driver should know *nothing* about windows, etc. What it should just know is:
- I want a mapping of a certain size to VME address X
I'm not convinced. Given that each bridge provides a limited number of windows (some more than others), we are limited to how large a window we can produce (we need to map them somewhere) and the potential combinations are so great (independant 16, 32, 40, 64 and CR/CSR address spaces, not to mention access modes) it is important to assign the windows to a driver, so that it may move them as it sees fit. For example, supporting 10 devices (as you have mentioned earier) with a single driver could potentially require a single window that it knows it has exclusive use of to position over each devices register as required without either having to provide a large window (unfeasibly large on most/all platforms if they are scattered across the 64-bit address space) or needing more windows than are available on any of the bridges I have seen (8 being the maximum).
Quoted 2 lines
The struct provides exactly this.
Ah - vme_dma does to some degree. I was talking about vme_mapping for configuring vme windows, from your vmebus.h:
115 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l115> * This data structure is used for describing both a hardware window 116 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l116> * and a logical mapping on top of a hardware window. Therefore some of 117 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l117> * the fields are only relevant to one of those two entities. 118 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l118> */
119 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l119> struct vme_mapping {
120 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l120>
int window_num;
/* Reserved for kernel use */ 123 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l123> void *kernel_va; 124 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l124> 125 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l125> /* Reserved for userspace */ 126 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l126> void *user_va; 127 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l127> int fd;
/* Window settings */ 130 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l130> int window_enabled; 131 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l131> enum vme_data_width data_width; 132 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l132> enum vme_address_modifier am; 133 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l133> int read_prefetch_enabled; 134 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l134> enum vme_read_prefetch_size read_prefetch_size;
Tsi-148 specific.
enum vme_2esst_mode v2esst_mode; 136 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l136> int bcast_select; 137 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l137> unsigned int pci_addru; 138 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l138> unsigned int pci_addrl;
Why are these split, why not a single unsigned long long?
unsigned int sizeu; 140 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l140> unsigned int sizel;
Ditto.
unsigned int vme_addru; 142 <http://repo.or.cz/w/tsi148vmebridge.git?a=blob;f=driver/vmebus.h;h=6f25a7715a973ae07a6d1f71bdb15d638a56e7aa;hb=HEAD#l142> unsigned int vme_addrl;
Ditto.
In addition your enum vme_address_modifier would need extending when specifying slave windows, the tsi-148 can support windows with USER and SUP access, as well as DATA and PRG. Why throw access privileges and address spaces together like that? The VME spec also specifies 4 User definable address spaces...
I'm still not convinced by all these structures - you've defined tonnes of them, I don't feel that it aids readability and maintainability at all.
Quoted 4 lines
Also, could you please send me (off-list) documentation of the Universe bridge? It'd be useful for the work on the generic layer.
It's all over the web :-) We've had it for years and I'm not sure under what terms we originally got it, so I'm afraid I've got to assume we're still bound under some NDA, sorry. Searching google for "Universe II Manual" much get you what you want though...
Martyn
Quoted 2 lines
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 7 lines · Martyn Welch
Quoted 5 lines · Emilio G. Cota
Martyn Welch wrote:
The point here is that the driver should know *nothing* about windows, etc. What it should just know is:
- I want a mapping of a certain size to VME address X
Quoted 13 lines
I'm not convinced. Given that each bridge provides a limited number of windows (some more than others), we are limited to how large a window we can produce (we need to map them somewhere) and the potential combinations are so great (independant 16, 32, 40, 64 and CR/CSR address spaces, not to mention access modes) it is important to assign the windows to a driver, so that it may move them as it sees fit. For example, supporting 10 devices (as you have mentioned earier) with a single driver could potentially require a single window that it knows it has exclusive use of to position over each devices register as required without either having to provide a large window (unfeasibly large on most/all platforms if they are scattered across the 64-bit address space) or needing more windows than are available on any of the bridges I have seen (8 being the maximum).
No, it corresponds to the bridge to manage *its* resources. Drivers would have a very hard time, because they'd need to know their own resources but _also_ the use other drivers are making of the bridge. And that's madness at its best.
Quoted 5 lines
Quoted 2 lines
The struct provides exactly this.
Ah -
vme_dmadoes to some degree. I was talking aboutvme_mappingfor configuring vme windows, from yourvmebus.h:
[ was it just me or some lines + links got copied? I've removed them anyway ]
Quoted 7 lines
* This data structure is used for describing both a hardware window
* and a logical mapping on top of a hardware window. Therefore some of
* the fields are only relevant to one of those two entities. */struct vme_mapping { int window_num;
[ snip ]
Quoted 2 lines
enum vme_read_prefetch_size read_prefetch_size;Tsi-148 specific.
If others don't implement it, it's ok; however you need to cover all the cases, so it's needed here.
[ snip ]
Quoted 11 lines
int bcast_select; unsigned int pci_addru; unsigned int pci_addrl;Why are these split, why not a single unsigned long long?
unsigned int sizeu; unsigned int sizel; Ditto. unsigned int vme_addru; unsigned int vme_addrl;Ditto.
- Most accesses are 32-bit accesses. Treating all of them as 64-bit accesses would decrease performance for most of them--which happen to be 32-bit.
Quoted 5 lines
In addition your enum
vme_address_modifierwould need extending when specifying slave windows, the tsi-148 can support windows with USER and SUP access, as well as DATA and PRG. Why throw access privileges and address spaces together like that? The VME spec also specifies 4 User definable address spaces...
Well Sebastien worked on this for less than two months, and to be honest with you the result is much better than the original code we had from Motorola--which is the one you based your work on, I think.
So yes, the whole spec is not there, but
- If no one uses something, there's no point in implementing it-- irrespective of being written on a spec or not.
- Sebastien did a bloody good job, but obviously he focused on our urgent matters, i.e. with the Motorola driver we couldn't go on production with our machines.
Quoted 3 lines
I'm still not convinced by all these structures - you've defined tonnes of them, I don't feel that it aids readability and maintainability at all.
Tons of them? Seriously?
t61$ /data/src/linux-next/include/linux ->cat pci*.h | egrep '^struct' | wc -l 41
t61$ /data/src/linux-next/include/linux ->cat vme*.h | egrep '^struct' | wc -l 6
I would call that pretty sane as a starting point.
[ NB. this is my current dev tree, vme.h is just 'our' vmebus.h
Lynx's compatility crap removed ]
Quoted 4 lines
It's all over the web :-) We've had it for years and I'm not sure under what terms we originally got it, so I'm afraid I've got to assume we're still bound under some NDA, sorry. Searching google for "Universe II Manual" much get you what you want though...
o rite, no probs.
Thanks,
E.
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 10 lines · Emilio G. Cota
Quoted 8 lines · Martyn Welch
Quoted 6 lines · Emilio G. Cota
Martyn Welch wrote:
The point here is that the driver should know *nothing* about windows, etc. What it should just know is:
- I want a mapping of a certain size to VME address X
Quoted 8 lines
Quoted 2 lines
I'm not convinced. Given that each bridge provides a limited number of windows (some more than others), we are limited to how large a window we can produce (we need to map them somewhere) and the potential combinations are so great (independant 16, 32, 40, 64 and CR/CSR address spaces, not to mention access modes) it is important to assign the windows to a driver, so that it may move them as it sees fit. For example, supporting 10 devices (as you have mentioned earier) with a single driver could potentially require a single window that it knows it has exclusive use of to position over each devices register as required without either having to provide a large window (unfeasibly large on most/all platforms if they are scattered across the 64-bit address space) or needing more windows than are available on any of the bridges I have seen (8 being the maximum).
No, it corresponds to the bridge to manage *its* resources. Drivers would have a very hard time, because they'd need to know their own resources but _also_ the use other drivers are making of the bridge. And that's madness at its best.
I think you've missed how the current API works - drivers request a resource, not a specific window, specifying the attributes they wish the window to support, such as "A32 address space", "2eSST transfer mode". The VME core looks at the resources it has available and returns a resource structure that identifies the underlying resource. The driver has no need to know which one it is. If a suitable resource is not available, no resource is returned, the underlying hardware is therefore incapable of providing the required attributes or all the resources are in use.
In terms of knowing "the use other drivers are making of the bridge", if to drivers try to create a slave window using the same address space and location there is no way, beyond basic checks that windows don't overlap and rejecting the second attempt, that anything can be done. The bus simply can't support this.
The API I have defined provides basic resource management - may I suggest if you want something more complex that allows the user to just pick a location/size and not worry about windows at all that you provide a patch for this that layers above the basic resource management? This would then be of use to anyone that wanted to use it for any underlying VME bridge.
Quoted 45 lines
Quoted 7 lines
Quoted 4 lines
The struct provides exactly this.
Ah -
vme_dmadoes to some degree. I was talking aboutvme_mappingfor configuring vme windows, from yourvmebus.h:[ was it just me or some lines + links got copied? I've removed them anyway ]
Quoted 8 lines
* This data structure is used for describing both a hardware window
* and a logical mapping on top of a hardware window. Therefore some of
* the fields are only relevant to one of those two entities. */struct vme_mapping { int window_num;[ snip ]
Quoted 3 lines
enum vme_read_prefetch_size read_prefetch_size;Tsi-148 specific.
If others don't implement it, it's ok; however you need to cover all the cases, so it's needed here.
[ snip ]
Quoted 4 lines
int bcast_select; unsigned int pci_addru; unsigned int pci_addrl;Quoted 8 lines
Why are these split, why not a single unsigned long long?
unsigned int sizeu; unsigned int sizel; Ditto. unsigned int vme_addru; unsigned int vme_addrl;Ditto.
Quoted 5 lines
- Most accesses are 32-bit accesses. Treating all of them as 64-bit accesses would decrease performance for most of them--which happen to be 32-bit.
I'm not - I'm storing them as 64-bit values, which they are, in the structures used in *software*. These are then split *when* a write to the hardware registers is required. Similarly, when the registers are occasionally read they are combined and stored as a 64-bit value. This simplifies all *software* checking and manipulation. By storing these as 2 32-bit values every driver that uses the VME core will need to convert pci addresses, vme addresses and counts to 2 32-bit values. That is madness.
Quoted 8 lines
Quoted 2 lines
In addition your enum
vme_address_modifierwould need extending when specifying slave windows, the tsi-148 can support windows with USER and SUP access, as well as DATA and PRG. Why throw access privileges and address spaces together like that? The VME spec also specifies 4 User definable address spaces...Well Sebastien worked on this for less than two months, and to be honest with you the result is much better than the original code we had from Motorola--which is the one you based your work on, I think.
So that's where the 2 32-bit values come from. That was an artifact of the Motorola driver mapping the structure over the registers. I'm surprised you kept that, it was practically the first thing I corrected.
Quoted 8 lines
So yes, the whole spec is not there, but
- If no one uses something, there's no point in implementing it-- irrespective of being written on a spec or not.
- Sebastien did a bloody good job, but obviously he focused on our urgent matters, i.e. with the Motorola driver we couldn't go on production with our machines.
Right - but I've taken a higher level view of what a VME API should provide, with the aim of providing an API that will support the features described in the VME specifications for more than one chip set, rather than targeting just those features that I need right now. I have provided a VME core to enable common checking routines and functionality to be provided to all VME bridge drivers without duplication. I too feel that I have done a good job and, whilst I am not going to make claims about it's readiness for production machines at this stage, feel that the tsi-148 driver that I have adapted/written is quite robust.
Quoted 23 lines
Quoted 2 lines
I'm still not convinced by all these structures - you've defined tonnes of them, I don't feel that it aids readability and maintainability at all.
Tons of them? Seriously?
t61$ /data/src/linux-next/include/linux ->cat pci*.h | egrep '^struct' | wc -l 41
t61$ /data/src/linux-next/include/linux ->cat vme*.h | egrep '^struct' | wc -l 6I would call that pretty sane as a starting point.
[ NB. this is my current dev tree,
vme.his just 'our'vmebus.h
Lynx's compatility crap removed ]Quoted 2 lines
It's all over the web :-) We've had it for years and I'm not sure under what terms we originally got it, so I'm afraid I've got to assume we're still bound under some NDA, sorry. Searching google for "Universe II Manual" much get you what you want though...
o rite, no probs.
Thanks,
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 2 lines · Martyn Welch
suggest if you want something more complex that allows the user to just pick a location/size and not worry about windows at all
That's exactly the whole point. I think each bridge should manage its resources; putting this on the upper layer would mean the layer should have a mechanism of 'discovering' what the bridge can/can't do. Anyway this could be revisited later.
I'm preparing a patch for this.
Quoted 11 lines
Quoted 3 lines
- Most accesses are 32-bit accesses. Treating all of them as 64-bit accesses would decrease performance for most of them--which happen to be 32-bit.
I'm not - I'm storing them as 64-bit values, which they are, in the structures used in *software*. These are then split *when* a write to the hardware registers is required. Similarly, when the registers are occasionally read they are combined and stored as a 64-bit value. This simplifies all *software* checking and manipulation. By storing these as 2 32-bit values every driver that uses the VME core will need to convert pci addresses, vme addresses and counts to 2 32-bit values. That is madness.
I agree with you on that's painful for doing 64-bit accesses. However I'm still not convinced on the performance side (I mean software), since most of the time the upper 32bits will be empty. Will have a look though.
Cheers,
E.
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 11 lines · Emilio G. Cota
Quoted 2 lines · Martyn Welch
suggest if you want something more complex that allows the user to just pick a location/size and not worry about windows at all
That's exactly the whole point. I think each bridge should manage its resources; putting this on the upper layer would mean the layer should have a mechanism of 'discovering' what the bridge can/can't do. Anyway this could be revisited later.
I'm preparing a patch for this.
I disagree. The bridge drivers should register their resources with the core. The core, or a layer above it, can control how those resources are used. This moves the complexity you want for managing the windows to a level that will work on all underlying drivers rather than having to be written explicitly for each one. The mechanism I have provided does this discovery.
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
- Most accesses are 32-bit accesses. Treating all of them as 64-bit accesses would decrease performance for most of them--which happen to be 32-bit.
Quoted 9 lines
Quoted 2 lines
I'm not - I'm storing them as 64-bit values, which they are, in the structures used in *software*. These are then split *when* a write to the hardware registers is required. Similarly, when the registers are occasionally read they are combined and stored as a 64-bit value. This simplifies all *software* checking and manipulation. By storing these as 2 32-bit values every driver that uses the VME core will need to convert pci addresses, vme addresses and counts to 2 32-bit values. That is madness.
I agree with you on that's painful for doing 64-bit accesses. However I'm still not convinced on the performance side (I mean software), since most of the time the upper 32bits will be empty. Will have a look though.
Regardless of the contents of the upper 32 bits, they will need to be checked. For example when ensuring a window offset and size fit within a given address space. To do this the bound needs to be calculated.
To do this with 2 32-bit values. add the lower window offset and size, which could lead to an over-flow of the lower 32-bit value, which needs to be added to the upper 32-bit value, which is the sum of the upper window offset and size. Then potentially check both the upper and lower values (thinking of 16-bit address range here) to ensure they are below the maximum address for the address range.
Compared to an addition of the window offset and size and a comparison to the maximum address for the address range when using one 64-bit value.
Clearly neither of the above basic outlines check that the combination has wrapped over 64 bits, but this would affect both equally.
So, which is more complex?
Martyn
Quoted 3 lines
Cheers,
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 6 lines · Martyn Welch
I disagree. The bridge drivers should register their resources with the core. The core, or a layer above it, can control how those resources are used. This moves the complexity you want for managing the windows to a level that will work on all underlying drivers rather than having to be written explicitly for each one. The mechanism I have provided does this discovery.
nah, it would be foolish to think we can write an upper layer that covers every corner case for every bridge we're gonna encounter. For instance, imagine a bridge that has 10 windows, with the annoying feature that window#10 *only* accepts CS/CSR mappings. How stupid is that? Very stupid. But what would be more stupid is to write allegedly 'generic' interfaces that break every time a bridge comes up with a stupid feature.
So that doesn't belong to a generic interface. Now, to avoid code duplication between two (or more) _very_ similar bridges, we just share the 'resource management' code among those, privately. And that's pretty much it.
Regards,
E.
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 8 lines · Emilio G. Cota
Quoted 2 lines · Martyn Welch
I disagree. The bridge drivers should register their resources with the core. The core, or a layer above it, can control how those resources are used. This moves the complexity you want for managing the windows to a level that will work on all underlying drivers rather than having to be written explicitly for each one. The mechanism I have provided does this discovery.
nah, it would be foolish to think we can write an upper layer that covers every corner case for every bridge we're gonna encounter.
So it's foolish to have a generic USB layer, or a generic PCI layer or generic "name you bus here" layer?
Unless you provide a consistent API, such as one supporting the features documented in the VME specifications, how are you planning to write drivers that could potentially work on more than one specific bridge?
Quoted 6 lines
For instance, imagine a bridge that has 10 windows, with the annoying feature that window#10 *only* accepts CS/CSR mappings. How stupid is that? Very stupid. But what would be more stupid is to write allegedly 'generic' interfaces that break every time a bridge comes up with a stupid feature.
Actually, the VME core as it exists would support such a situation. The 10th window would only register as being capable of supporting that address space. What about a driver only needed to access CR/CSR space (to see what was available on other hosts for example), then utilized a DMA controller to transfer data over the VME bus (thereby not requiring a master or slave window). It could request a window that fitted it's needs ("vme_address_t aspace = VMECRCST;") and the core would be able to hand it that resource. Should the underlying bridge not have such a limited window, it can had it one with a larger feature set, ensuring that it meets the requirements as requested by the driver.
Quoted 5 lines
So that doesn't belong to a generic interface. Now, to avoid code duplication between two (or more) _very_ similar bridges, we just share the 'resource management' code among those, privately. And that's pretty much it.
Or it could be layered on top, utilising the the resource management that I have proposed and the two can sit together happily side-by-side. If you are right and that method of access works best, then drivers will use that rather than requesting resources. If not then the two can continue to sit side-by-side. Why make the bridge drivers more complex than they need to be?
Also, it seems that your API doesn't currently support Location Monitors. These are specified in the VME spec, I'd be interested in how you plan to support this feature in a consistent manner with your current API. These can only be used in a single location at a time and cover a fixed number of addresses from the initial offset. The location monitors are treated as a resource in my VME core which is consistent with the windows being treated as resources.
Come to think of it, I can't see any code managing slave windows either - how is your API going to consistently manage these?
Martyn
Quoted 3 lines
Regards,
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 2 lines · Martyn Welch
So it's foolish to have a generic USB layer, or a generic PCI layer or generic "name you bus here" layer?
[ snip ]
Quoted 8 lines
Or it could be layered on top, utilising the the resource management that I have proposed and the two can sit together happily side-by-side.
If you are right and that method of access works best, then drivers will
use that rather than requesting resources. If not then the two can continue to sit side-by-side. Why make the bridge drivers more complex than they need to be?
aagh, please stop this nonsense.
http://lwn.net/Articles/336262/
"Here we see the first reason to dislike midlayers - they encourage special cases. When writing a midlayer it is impossible to foresee every possible need that a bottom level driver might have, so it is impossible to allow for them all in the midlayer. The midlayer could conceivably be redesigned every time a new requirement came along, but that is unlikely to be an effective use of time. Instead, special cases tend to grow."
I won't discuss this anymore.
Quoted 3 lines
Unless you provide a consistent API, such as one supporting the features documented in the VME specifications, how are you planning to write drivers that could potentially work on more than one specific bridge?
[ snip ]
Quoted 5 lines
Also, it seems that your API doesn't currently support Location Monitors. These are specified in the VME spec, I'd be interested in how
you plan to support this feature in a consistent manner with your current API
What I intend to provide is a sane environment to work with VME devices. The first step is to cover the needs of most use cases:
- 1 master Single Board Computer (SBC) running Linux
- All the other slots have slave devices in them. These devices are controlled by kernel drivers on the SBC.
- The SBC does DMA, direct I/O access (mapping) to/from the slaves, and handles interrupts from the devices.
Once that's settled we could discuss about further parts of the spec. But really today getting the above right is _much_ more important than location monitors support.
E.
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 18 lines · Emilio G. Cota
Quoted 6 lines · Martyn Welch
Also, it seems that your API doesn't currently support Location Monitors. These are specified in the VME spec, I'd be interested in how
you plan to support this feature in a consistent manner with your current APIWhat I intend to provide is a sane environment to work with VME devices. The first step is to cover the needs of most use cases:
- 1 master Single Board Computer (SBC) running Linux
- All the other slots have slave devices in them. These devices are controlled by kernel drivers on the SBC.
- The SBC does DMA, direct I/O access (mapping) to/from the slaves, and handles interrupts from the devices.
Quoted 4 lines
Once that's settled we could discuss about further parts of the spec. But really today getting the above right is _much_ more important than location monitors support.
Right - this is a specific case that you see as important. Depending on the number of slots in the rack and the diversity of the cards used, the VME core and driver I have suggested already covers this.
However, I also need to consider other cases, such as communications between multiple SBCs and potentially a few slave devices. I have a need for this *now*, not in the future. To me, slave support and to a lesser degree location monitor support is important now, hence why it's supported in my driver and core. Hence why I am asking, how do you propose to support these features?
Martyn
Quoted 2 lines
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 6 lines · Martyn Welch
However, I also need to consider other cases, such as communications between multiple SBCs and potentially a few slave devices. I have a need for this *now*, not in the future. To me, slave support and to a lesser degree location monitor support is important now, hence why it's supported in my driver and core. Hence why I am asking, how do you propose to support these features?
Point taken.
Could you tell me a bit more about your needs on this respect? So, you have multiple SBCs (some of them as masters, some others as slaves) running Linux and want to communicate between them? Have you got working code for all that?
E.
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 8 lines · Emilio G. Cota
Quoted 1 line · Martyn Welch
However, I also need to consider other cases, such as communications between multiple SBCs and potentially a few slave devices. I have a need for this *now*, not in the future. To me, slave support and to a lesser degree location monitor support is important now, hence why it's supported in my driver and core. Hence why I am asking, how do you propose to support these features?
Point taken.
Could you tell me a bit more about your needs on this respect? So, you have multiple SBCs (some of them as masters, some others as slaves) running Linux and want to communicate between them?
Yes - though potentially each card will be both a master and a slave.
Quoted 1 line
Have you got working code for all that?
In so far as the VME core and tsi-148 driver supports the required functionality, I believe so. We have systems supporting this, though currently with different OSes. We also have a bit of legacy Linux VME code, using the old style of interface. At the moment my priority is less on individual applications and more focused towards ensuring we have a robust API which will support all the features we are likely to require for the chipsets that we have in our products.
We have briefly talked about a "Ethernet over VME" like the "Ethernet over RapidIO" driver, however, this currently isn't top priority.
Martyn
Quoted 2 lines
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)127322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 2/5] Staging: vme: add VME userspace driver
In reply to Emilio G. CotaQuoted 9 lines · Emilio G. Cota
Quoted 2 lines · Martyn Welch
Quoted 2 lines
Quoted 2 lines
Instead of using that we implemented a heretic IOCTL-based interface for user-space; at least with it you could create a
[ snip ]
Quoted 4 lines
Quoted 2 lines
Quoted 2 lines
#define
VME_IOCTL_START_DMA_IOWR('V', 10, struct vme_dma)I am moving the interface in that direction, I remain unconvinced about the contents of your
vme_mappingstructure, it's too tsi-148 specific.
Quoted 6 lines
Could you please point out why is too tsi148-specific?
The point here is that the driver should know *nothing* about windows, etc. What it should just know is:
- I want a mapping of a certain size to VME address X The struct provides exactly this.
So does the existing UIO interface, right? Please don't reinvent the wheel here :)
thanks,
greg k-h
Re: [patch 2/5] Staging: vme: add VME userspace driver
In reply to Emilio G. CotaQuoted 12 lines · Emilio G. Cota
(oops, just correcting Sebastien's e-mail address)
Quoted 9 lines · Emilio G. Cota
I remember we had a look at the vmelinux interface, but it really was so limited that we decided to get rid of it altogether. Do you really think it's worth bringing it back to life? Since it's never been merged into the kernel, why don't we just get it right this time and forget about it?
Instead of using that we implemented a heretic IOCTL-based interface for user-space; at least with it you could create a driver (with no interrupt support) for a device in user-space.
Why not just use the UIO interface instead of creating yet-another-kernel/user-api?
Will that interface not work for what you want to use here?
thanks,
greg k-h
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 9 lines · Greg KH
Quoted 3 lines · Emilio G. Cota
Quoted 3 lines
Instead of using that we implemented a heretic IOCTL-based interface for user-space; at least with it you could create a driver (with no interrupt support) for a device in user-space.
Why not just use the UIO interface instead of creating yet-another-kernel/user-api?
Will that interface not work for what you want to use here?
Certainly. I didn't mean we'd like to see that merged. Currently we make extensive use of IOCTLs because we are maintaining kernel and user-space code for two platforms/OSes (ppc-lynx and x86-linux), and not having access to lynx' source code doesn't help. So IOCTLs are pretty much everywhere, much to my regret.
E.
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 9 lines · Emilio G. Cota
Quoted 8 lines · Greg KH
Quoted 3 lines · Emilio G. Cota
Quoted 3 lines
Instead of using that we implemented a heretic IOCTL-based interface for user-space; at least with it you could create a driver (with no interrupt support) for a device in user-space.
Why not just use the UIO interface instead of creating yet-another-kernel/user-api?
Quoted 7 lines
Quoted 1 line
Will that interface not work for what you want to use here?
Certainly. I didn't mean we'd like to see that merged. Currently we make extensive use of IOCTLs because we are maintaining kernel and user-space code for two platforms/OSes (ppc-lynx and x86-linux), and not having access to lynx' source code doesn't help. So IOCTLs are pretty much everywhere, much to my regret.
That does not mean we will accept adding new ioctls for the Linux drivers, so I fail to see the point of this :)
thanks,
greg k-h
Re: [patch 2/5] Staging: vme: add VME userspace driver
Quoted 12 lines · Greg KH
Quoted 11 lines · Emilio G. Cota
Quoted 9 lines · Greg KH
Quoted 3 lines · Emilio G. Cota
Quoted 3 lines
Instead of using that we implemented a heretic IOCTL-based interface for user-space; at least with it you could create a driver (with no interrupt support) for a device in user-space.
Why not just use the UIO interface instead of creating yet-another-kernel/user-api?
Will that interface not work for what you want to use here?
Quoted 8 lines
Quoted 5 lines
Certainly. I didn't mean we'd like to see that merged. Currently we make extensive use of IOCTLs because we are maintaining kernel and user-space code for two platforms/OSes (ppc-lynx and x86-linux), and not having access to lynx' source code doesn't help. So IOCTLs are pretty much everywhere, much to my regret.
That does not mean we will accept adding new ioctls for the Linux drivers, so I fail to see the point of this :)
I showed it just to point out that the vmelinux.org interface was not very useful--I never said I'd like to see these IOCTLs merged.
I do *not* want to see those IOCTLs anywhere outside of our code, and never did. Sorry for the confusion; hope it's clear now.
E.
PATCH 3/5 Staging: vme: add Universe I/II bridge driver
In reply to Greg K-HFrom: Martyn Welch <[email protected]>
Currently this code doesn't compile, so it is disabled.
That should be fixed up...
--- drivers/staging/vme/Kconfig | 2 drivers/staging/vme/Makefile | 2 drivers/staging/vme/bridges/Kconfig | 8 drivers/staging/vme/bridges/Makefile | 1 drivers/staging/vme/bridges/vme_ca91cx42.c | 1811 +++++++++++++++++++++++++++++ drivers/staging/vme/bridges/vme_ca91cx42.h | 403 ++++++ 6 files changed, 2225 insertions(+), 2 deletions(-)
drivers/staging/vme/bridges/Kconfig new file +8 -0
@@ -0,0 +1,8 @@ +comment "VME Bridge Drivers" + +config VME_CA91CX42 + tristate "Universe I/II" + depends on BROKEN + help + If you say Y here you get support for the Tundra CA91C042 (Universe I) + and CA91C142 (Universe II) VME bridge chips.
drivers/staging/vme/bridges/Makefile new file +1 -0
@@ -0,0 +1 @@ +obj-$(CONFIG_VME_CA91CX42) += vme_ca91cx42.o
drivers/staging/vme/bridges/vme_ca91cx42.c new file +1811 -0
1,811 lines not on this page · raw message
drivers/staging/vme/bridges/vme_ca91cx42.h new file +403 -0
403 lines not on this page · raw message
drivers/staging/vme/Kconfig +1 -1
@@ -10,7 +10,7 @@ menuconfig VME if VME -#source "drivers/staging/vme/bridges/Kconfig" +source "drivers/staging/vme/bridges/Kconfig" source "drivers/staging/vme/devices/Kconfig"
drivers/staging/vme/Makefile +1 -1
@@ -3,5 +3,5 @@ # obj-$(CONFIG_VME) += vme.o -#obj-y += bridges/ +obj-y += bridges/ obj-y += devices/
Re: [patch 3/5] Staging: vme: add Universe I/II bridge driver
Quoted 2 lines · Greg K-H
drivers/staging/vme/bridges/vme_ca91cx42.c new file +2
-0+int uni_init (void ) +{
...
drivers/staging/vme/bridges/vme_ca91cx42.c line 1754
Quoted 2 lines
drivers/staging/vme/bridges/vme_ca91cx42.c new file +2
-0+ result = + request_irq(vmechip_irq, uni_irqhandler, IRQF_SHARED | IRQF_DISABLED,
DISABLED on SHARED is not guaranteed. You must specify one or the other.
PATCH 4/5 Staging: vme: add Tundra TSI148 VME-PCI Bridge driver
In reply to Greg K-HFrom: Martyn Welch <[email protected]>
--- drivers/staging/vme/bridges/Kconfig | 6 drivers/staging/vme/bridges/Makefile | 1 drivers/staging/vme/bridges/vme_tsi148.c | 2912 +++++++++++++++++++++++++++++++ drivers/staging/vme/bridges/vme_tsi148.h | 1387 ++++++++++++++ 4 files changed, 4306 insertions(+)
drivers/staging/vme/bridges/Kconfig +6 -0
@@ -6,3 +6,9 @@ config VME_CA91CX42 help If you say Y here you get support for the Tundra CA91C042 (Universe I) and CA91C142 (Universe II) VME bridge chips. + +config VME_TSI148 + tristate "Tempe" + help + If you say Y here you get support for the Tundra TSI148 VME bridge + chip.
drivers/staging/vme/bridges/Makefile +1 -0
@@ -1 +1,2 @@ obj-$(CONFIG_VME_CA91CX42) += vme_ca91cx42.o +obj-$(CONFIG_VME_TSI148) += vme_tsi148.o
drivers/staging/vme/bridges/vme_tsi148.c new file +2912 -0
2,912 lines not on this page · raw message
drivers/staging/vme/bridges/vme_tsi148.h new file +1387 -0
77 lines not on this page · raw message
The rest of this diff is not shown. View the raw message.
Part of the diff is not shown here. View the raw message.
Re: [patch 4/5] Staging: vme: add Tundra TSI148 VME-PCI Bridge driver
drivers/staging/vme/bridges/vme_tsi148.c line 484
Quoted 5 lines · Greg K-H
drivers/staging/vme/bridges/vme_tsi148.c new file +5
-0+void tsi148_free_irq (int level,int statid) +{ +u32 tmp; + +/* Get semaphore */
Hmm, useful comment. Anyway I think you plan to get rid of all those while being in staging ;)?
drivers/staging/vme/bridges/vme_tsi148.c line 487
Quoted 3 lines
drivers/staging/vme/bridges/vme_tsi148.c new file +3
-0+down (&(vme_irq)); + + tsi148_bridge->irq[level -1 ].callback[statid].func =NULL ;
What if the interrupt comes now? I think you want to switch this block with "Disable IRQ level" one. And take care of PCI posting (if it is not enough that INTEO reaches the device by a consequent INTEN read).
Quoted 17 lines
drivers/staging/vme/bridges/vme_tsi148.c new file +17
-0+ tsi148_bridge->irq[level -1 ].callback[statid].priv_data =NULL ; + tsi148_bridge->irq[level -1 ].count--; + +/* Disable IRQ level */ +if (tsi148_bridge->irq[level -1 ].count ==0 ) { + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEN ); + tmp &= ~TSI148_LCSR_INTEN_IRQEN [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEN ); + } + +/* Release semaphore */ +up (&(vme_irq)); +}
Re: [patch 4/5] Staging: vme: add Tundra TSI148 VME-PCI Bridge driver
Quoted 6 lines · Jiri Slaby
drivers/staging/vme/bridges/vme_tsi148.c line 487
Quoted 3 lines · Greg K-H
drivers/staging/vme/bridges/vme_tsi148.c new file +3
-0+down (&(vme_irq)); + + tsi148_bridge->irq[level -1 ].callback[statid].func =NULL ;What if the interrupt comes now?
On the second though, actually nothing worse than "Spurilous VME interrupt" (if some arch doesn't reorder func and priv_data assignments). It's not that bad, but still worth fixing.
Quoted 22 lines
I think you want to switch this block
with "Disable IRQ level" one. And take care of PCI posting (if it is not enough that INTEO reaches the device by a consequent INTEN read).Quoted 17 lines
drivers/staging/vme/bridges/vme_tsi148.c new file +17
-0+ tsi148_bridge->irq[level -1 ].callback[statid].priv_data =NULL ; + tsi148_bridge->irq[level -1 ].count--; + +/* Disable IRQ level */ +if (tsi148_bridge->irq[level -1 ].count ==0 ) { + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEN ); + tmp &= ~TSI148_LCSR_INTEN_IRQEN [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEN ); + } + +/* Release semaphore */ +up (&(vme_irq)); +}
PATCH 1/1 Staging: Correct tsi-148 VME interrupt free routine
In reply to Jiri SlabyAs identified by Jiri, the VME interrupt free routine removes the service routine before disabling the interrupt. Re-order operations to be performed in the reverse of the request routine.
---
Jiri: Thanks for the feedback.
Martyn
drivers/staging/vme/bridges/vme_tsi148.c | 15 ++++++++------- 1 files changed, 8 insertions(+), 7 deletions(-)
drivers/staging/vme/bridges/vme_tsi148.c +8 -7
@@ -484,21 +484,22 @@ void tsi148_free_irq(int level, int statid)/* Get semaphore */ down (&(vme_irq)); - tsi148_bridge->irq[level -1 ].callback[statid].func =NULL ; - tsi148_bridge->irq[level -1 ].callback[statid].priv_data =NULL ; tsi148_bridge->irq[level -1 ].count--; -/* Disable IRQ level */ +/* Disable IRQ level if no more interrupts attached at this level*/ if (tsi148_bridge->irq[level -1 ].count ==0 ) { - tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); - tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; -iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); - tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEN ); tmp &= ~TSI148_LCSR_INTEN_IRQEN [level -1 ];iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEN ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); } + tsi148_bridge->irq[level -1 ].callback[statid].func =NULL ; + tsi148_bridge->irq[level -1 ].callback[statid].priv_data =NULL ; +/* Release semaphore */ up (&(vme_irq)); }
PATCH v2 1/1 Staging: Correct tsi-148 VME interrupt free routine
As identified by Jiri, the VME interrupt free routine removes the service routine before disabling the interrupt. Re-order operations to be performed in the reverse of the request routine.
---
v2: Remember "Signed-off-by"...
drivers/staging/vme/bridges/vme_tsi148.c | 15 ++++++++------- 1 files changed, 8 insertions(+), 7 deletions(-)
drivers/staging/vme/bridges/vme_tsi148.c +8 -7
@@ -484,21 +484,22 @@ void tsi148_free_irq(int level, int statid)/* Get semaphore */ down (&(vme_irq)); - tsi148_bridge->irq[level -1 ].callback[statid].func =NULL ; - tsi148_bridge->irq[level -1 ].callback[statid].priv_data =NULL ; tsi148_bridge->irq[level -1 ].count--; -/* Disable IRQ level */ +/* Disable IRQ level if no more interrupts attached at this level*/ if (tsi148_bridge->irq[level -1 ].count ==0 ) { - tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); - tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; -iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); - tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEN ); tmp &= ~TSI148_LCSR_INTEN_IRQEN [level -1 ];iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEN ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); } + tsi148_bridge->irq[level -1 ].callback[statid].func =NULL ; + tsi148_bridge->irq[level -1 ].callback[statid].priv_data =NULL ; +/* Release semaphore */ up (&(vme_irq)); }
Re: [PATCH v2] Staging: Correct tsi-148 VME interrupt free routine
drivers/staging/vme/bridges/vme_tsi148.c void tsi148_free_irq(int level, int statid) · line 497
Quoted 12 lines · Martyn Welch
drivers/staging/vme/bridges/vme_tsi148.c +4
-4if (tsi148_bridge->irq[level -1 ].count ==0 ) { - tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); - tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; -iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); - tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEN ); tmp &= ~TSI148_LCSR_INTEN_IRQEN [level -1 ];iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEN ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO );
I have no idea what the registers do and I suppose it's behind some PCI bridge anywhere. If it is not true, ignore the further.
Is it OK that the second write to INTEO doesn't reach the device before you set func to NULL? I mean, is it enough to prevent the interrupt raising only by twiddling INTEN? Otherwise you need to put some read right here to push non-completed writes on bridges (flush posted writes). (I mentioned this in the former mail too.)
Otherwise looks good.
Quoted 8 lines
drivers/staging/vme/bridges/vme_tsi148.c +3
-0} + tsi148_bridge->irq[level -1 ].callback[statid].func =NULL ; + tsi148_bridge->irq[level -1 ].callback[statid].priv_data =NULL ; +/* Release semaphore */ up (&(vme_irq)); }
Re: [PATCH v2] Staging: Correct tsi-148 VME interrupt free routine
Quoted 19 lines · Jiri Slaby
drivers/staging/vme/bridges/vme_tsi148.c void tsi148_free_irq(int level, int statid) · line 497
Quoted 13 lines · Martyn Welch
drivers/staging/vme/bridges/vme_tsi148.c +4
-4if (tsi148_bridge->irq[level -1 ].count ==0 ) { - tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); - tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; -iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); - tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEN ); tmp &= ~TSI148_LCSR_INTEN_IRQEN [level -1 ];iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEN ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO );I have no idea what the registers do and I suppose it's behind some PCI bridge anywhere. If it is not true, ignore the further.
Quoted 6 lines
Is it OK that the second write to INTEO doesn't reach the device before you set func to NULL? I mean, is it enough to prevent the interrupt raising only by twiddling INTEN? Otherwise you need to put some read right here to push non-completed writes on bridges (flush posted writes). (I mentioned this in the former mail too.)
Yes, this is fine:
TSI148_LCSR_INTEN = Interrupt EnableTSI148_LCSR_INTEO = Interrupt Output Enable
If an interrupt source is completely disabled, it doesn't matter whether or not it is stopped from generating a PCI interrupt.
Martyn
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Re: [patch 4/5] Staging: vme: add Tundra TSI148 VME-PCI Bridge driver
In reply to Greg K-HGreg, Martyn,
please have a look at our tsi148 driver, available at:
http://repo.or.cz/w/tsi148vmebridge.git
Whenever you encounter a reference to 'CES', ignore it, it's a CERN thing for easing our lives porting drivers from LynxOS.
I could post the driver to LKML for review if you want; I would remove that CES crap before posting. I put this off because I knew it would never get merged before there was some generic vme glue supporting it. However now we're getting there so it's probably a good time to get cracking with this task.
Martyn, a few comments about your driver, haven't had much time to dig very deep.
- again, use mutexes instead of semaphores
- The DMA code looks rather messy; I mean stuff like this:
Quoted 11 lines
drivers/staging/vme/bridges/vme_tsi148.c new file +11
-0+#if 0 +/* XXX Still todo */ +for (x =0 ; x <8 ; x++) {/* vme block size */ +if ((32 << x) >= vmeDma->maxVmeBlockSize) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<12 ); +
drivers/staging/vme/bridges/vme_tsi148.c line 1900
Quoted 10 lines
drivers/staging/vme/bridges/vme_tsi148.c new file +10
-0+for (x =0 ; x <8 ; x++) {/* pci block size */ +if ((32 << x) >= vmeDma->maxPciBlockSize) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<4 ); + +if (vmeDma->vmeBackOffTimer) {
and the ifdefs 0..
- correct me if I'm wrong, but does the slave need to know the window number he wants for his device? See:
drivers/staging/vme/bridges/vme_tsi148.c line 795
Quoted 12 lines
drivers/staging/vme/bridges/vme_tsi148.c new file +12
-0+int tsi148_slave_get (struct vme_slave_resource *image,int *enabled, +unsigned long long *vme_base,unsigned long long *size, +dma_addr_t *pci_base,vme_address_t *aspace,vme_cycle_t *cycle) +{ +unsigned int i, granularity =0 , ctl =0 ; +unsigned int vme_base_low, vme_base_high; +unsigned int vme_bound_low, vme_bound_high; +unsigned int pci_offset_low, pci_offset_high; +unsigned long long vme_bound, pci_offset; + + + i = image->number;
If that's the case, this is totally broken; in fact, even if you kept a list somewhere up on the generic vme layer keeping track of the already used windows (images?) to avoid any clashes, in practice you'd very soon run out of windows: 8 modules, 8 mappings, 8 windows. And you're dead.
In a crate we have up to 20 modules, each of them with at least one address space to be mapped. To make this work we do the following:
* The tsi148 has 1GB of address space available for mapping, which is further divided into 8 windows, with a unique address modifier per window.
* Say you have 15 modules that need to map 1MB each, all of them with the same address modifier (AM).
Then what the slave driver requests is *not* a window, is what we call a 'vme mapping', which means "do whatever you want but give me a kernel address that points at this VME address, mapping with this size and AM".
Our tsi148 driver notices then that there's already a window for that AM, so it just appends the mapping to the existing window. So at the end of the day only one window is used for all those mappings, which are nicely piled together.
Please clarify me how this is handled in your driver. The bottom line is to ensure that
a) Many different modules can peacefully co-exist on a crate
b) Drivers should know nothing about what else is on your crate
E.
(I keep this here for the added Cc)
Quoted 4,336 lines · Greg K-H
From: Martyn Welch <[email protected]>
Signed-off-by Martyn Welch <[email protected]>Signed-off-by Greg Kroah-Hartman <[email protected]>--- drivers/staging/vme/bridges/Kconfig | 6 drivers/staging/vme/bridges/Makefile | 1 drivers/staging/vme/bridges/vme_tsi148.c | 2912 +++++++++++++++++++++++++++++++ drivers/staging/vme/bridges/vme_tsi148.h | 1387 ++++++++++++++ 4 files changed, 4306 insertions(+)drivers/staging/vme/bridges/Kconfig +6
-0@@ -6,3 +6,9 @@ config VME_CA91CX42 help If you say Y here you get support for the Tundra CA91C042 (Universe I) and CA91C142 (Universe II) VME bridge chips. + +config VME_TSI148 + tristate "Tempe" + help + If you say Y here you get support for the Tundra TSI148 VME bridge + chip.drivers/staging/vme/bridges/Makefile +1
-0@@ -1 +1,2 @@ obj-$(CONFIG_VME_CA91CX42) += vme_ca91cx42.o +obj-$(CONFIG_VME_TSI148) += vme_tsi148.odrivers/staging/vme/bridges/vme_tsi148.c new file +2912
-0@@ -0,0 +1,2912 @@ +/* +* Support for the Tundra TSI148 VME-PCI Bridge Chip +* +* Author: Martyn Welch <[email protected]> +* Copyright 2008 GE Fanuc Intelligent Platforms Embedded Systems, Inc. +* +* Based on work by Tom Armistead and Ajit Prem +* Copyright 2004 Motorola Inc. +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mm.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/proc_fs.h> +#include <linux/pci.h> +#include <linux/poll.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <asm/time.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#include "../vme.h" +#include "../vme_bridge.h" +#include "vme_tsi148.h" + +static int __init tsi148_init (void ); +static int tsi148_probe (struct pci_dev *,const struct pci_device_id *); +static void tsi148_remove (struct pci_dev *); +static void __exit tsi148_exit(void); + + +int tsi148_slave_set (struct vme_slave_resource *,int ,unsigned long long , +unsigned long long ,dma_addr_t ,vme_address_t ,vme_cycle_t ); +int tsi148_slave_get (struct vme_slave_resource *,int *,unsigned long long *, +unsigned long long *,dma_addr_t *,vme_address_t *,vme_cycle_t *); + +int tsi148_master_get (struct vme_master_resource *,int *,unsigned long long *, +unsigned long long *,vme_address_t *,vme_cycle_t *,vme_width_t *); +int tsi148_master_set (struct vme_master_resource *,int ,unsigned long long , +unsigned long long ,vme_address_t ,vme_cycle_t ,vme_width_t ); +ssize_t tsi148_master_read (struct vme_master_resource *,void *,size_t , +loff_t ); +ssize_t tsi148_master_write (struct vme_master_resource *,void *,size_t , +loff_t ); +unsigned int tsi148_master_rmw (struct vme_master_resource *,unsigned int , +unsigned int ,unsigned int ,loff_t ); +int tsi148_dma_list_add (struct vme_dma_list *,struct vme_dma_attr *, +struct vme_dma_attr *,size_t ); +int tsi148_dma_list_exec (struct vme_dma_list *); +int tsi148_dma_list_empty (struct vme_dma_list *); +int tsi148_generate_irq (int ,int ); +int tsi148_lm_set (unsigned long long ,vme_address_t ,vme_cycle_t ); +int tsi148_lm_get (unsigned long long *,vme_address_t *,vme_cycle_t *); +int tsi148_lm_attach (int ,void (*callback)(int )); +int tsi148_lm_detach (int ); +int tsi148_slot_get (void ); + +/* Modue parameter */ +int err_chk =0 ; + +/* XXX These should all be in a per device structure */ +struct vme_bridge *tsi148_bridge; +wait_queue_head_t dma_queue[2 ]; +wait_queue_head_t iack_queue; +void (*lm_callback[4 ])(int );/* Called in interrupt handler, be careful! */ +void *crcsr_kernel; +dma_addr_t crcsr_bus; +struct vme_master_resource *flush_image; +struct semaphore vme_rmw;/* Only one RMW cycle at a time */ +struct semaphore vme_int;/* +* Only one VME interrupt can be +* generated at a time, provide locking +*/ +struct semaphore vme_irq;/* Locking for VME irq callback configuration */ +struct semaphore vme_lm;/* Locking for location monitor operations */ + + +static char driver_name[] ="vme_tsi148" ; + +static struct pci_device_id tsi148_ids[] = { + {PCI_DEVICE (PCI_VENDOR_ID_TUNDRA ,PCI_DEVICE_ID_TUNDRA_TSI148 ) }, + { }, +}; + +static struct pci_driver tsi148_driver = { + .name = driver_name, + .id_table = tsi148_ids, + .probe = tsi148_probe, + .remove = tsi148_remove, +}; + +static void reg_join (unsigned int high,unsigned int low, +unsigned long long *variable) +{ + *variable = (unsigned long long )high <<32 ; + *variable |= (unsigned long long )low; +} + +static void reg_split (unsigned long long variable,unsigned int *high, +unsigned int *low) +{ + *low = (unsigned int )variable &0xFFFFFFFF ; + *high = (unsigned int )(variable >>32 ); +} + +/* +* Wakes up DMA queue. +*/ +static u32 tsi148_DMA_irqhandler (int channel_mask) +{ +u32 serviced =0 ; + +if (channel_mask &TSI148_LCSR_INTS_DMA0S ) { +wake_up (&dma_queue[0 ]); + serviced |=TSI148_LCSR_INTC_DMA0C ; + } +if (channel_mask &TSI148_LCSR_INTS_DMA1S ) { +wake_up (&dma_queue[1 ]); + serviced |=TSI148_LCSR_INTC_DMA1C ; + } + +return serviced; +} + +/* +* Wake up location monitor queue +*/ +static u32 tsi148_LM_irqhandler (u32 stat) +{ +int i; +u32 serviced =0 ; + +for (i =0 ; i <4 ; i++) { +if (stat &TSI148_LCSR_INTS_LMS [i]) { +/* We only enable interrupts if the callback is set */ + lm_callback[i](i); + serviced |=TSI148_LCSR_INTC_LMC [i]; + } + } + +return serviced; +} + +/* +* Wake up mail box queue. +* +* XXX This functionality is not exposed up though API. +*/ +static u32 tsi148_MB_irqhandler (u32 stat) +{ +int i; +u32 val; +u32 serviced =0 ; + +for (i =0 ; i <4 ; i++) { +if (stat &TSI148_LCSR_INTS_MBS [i]) { + val =ioread32be (tsi148_bridge->base + +TSI148_GCSR_MBOX [i]); +printk ("VME Mailbox %d received: 0x%x\n" , i, val); + serviced |=TSI148_LCSR_INTC_MBC [i]; + } + } + +return serviced; +} + +/* +* Display error & status message when PERR (PCI) exception interrupt occurs. +*/ +static u32 tsi148_PERR_irqhandler (void ) +{ +printk (KERN_ERR +"PCI Exception at address: 0x%08x:%08x, attributes: %08x\n" , +ioread32be (tsi148_bridge->base +TSI148_LCSR_EDPAU ), +ioread32be (tsi148_bridge->base +TSI148_LCSR_EDPAL ), +ioread32be (tsi148_bridge->base +TSI148_LCSR_EDPAT ) + ); +printk (KERN_ERR +"PCI-X attribute reg: %08x, PCI-X split completion reg: %08x\n" , +ioread32be (tsi148_bridge->base +TSI148_LCSR_EDPXA ), +ioread32be (tsi148_bridge->base +TSI148_LCSR_EDPXS ) + ); + +iowrite32be (TSI148_LCSR_EDPAT_EDPCL , + tsi148_bridge->base +TSI148_LCSR_EDPAT ); + +return TSI148_LCSR_INTC_PERRC ; +} + +/* +* Save address and status when VME error interrupt occurs. +*/ +static u32 tsi148_VERR_irqhandler (void ) +{ +unsigned int error_addr_high, error_addr_low; +unsigned long long error_addr; +u32 error_attrib; +struct vme_bus_error *error; + + error_addr_high =ioread32be (tsi148_bridge->base +TSI148_LCSR_VEAU ); + error_addr_low =ioread32be (tsi148_bridge->base +TSI148_LCSR_VEAL ); + error_attrib =ioread32be (tsi148_bridge->base +TSI148_LCSR_VEAT ); + +reg_join (error_addr_high, error_addr_low, &error_addr); + +/* Check for exception register overflow (we have lost error data) */ +if (error_attrib &TSI148_LCSR_VEAT_VEOF ) { +printk (KERN_ERR "VME Bus Exception Overflow Occurred\n" ); + } + + error = (struct vme_bus_error *)kmalloc (sizeof (struct vme_bus_error ), +GFP_ATOMIC ); +if (error) { + error->address = error_addr; + error->attributes = error_attrib; +list_add_tail (&(error->list), &(tsi148_bridge->vme_errors)); + }else { +printk (KERN_ERR +"Unable to alloc memory for VMEbus Error reporting\n" ); +printk (KERN_ERR +"VME Bus Error at address: 0x%llx, attributes: %08x\n" , + error_addr, error_attrib); + } + +/* Clear Status */ +iowrite32be (TSI148_LCSR_VEAT_VESCL , + tsi148_bridge->base +TSI148_LCSR_VEAT ); + +return TSI148_LCSR_INTC_VERRC ; +} + +/* +* Wake up IACK queue. +*/ +static u32 tsi148_IACK_irqhandler (void ) +{ +printk ("tsi148_IACK_irqhandler\n" ); +wake_up (&iack_queue); + +return TSI148_LCSR_INTC_IACKC ; +} + +/* +* Calling VME bus interrupt callback if provided. +*/ +static u32 tsi148_VIRQ_irqhandler (u32 stat) +{ +int vec, i, serviced =0 ; +void (*call)(int ,int ,void *); +void *priv_data; + +for (i =7 ; i >0 ; i--) { +if (stat & (1 << i)) { +/* +* Note: Even though the registers are defined +* as 32-bits in the spec, we only want to issue +* 8-bit IACK cycles on the bus, read from offset +* 3. +*/ + vec =ioread8 (tsi148_bridge->base + +TSI148_LCSR_VIACK [i] +3 ); + + call = tsi148_bridge->irq[i -1 ].callback[vec].func; + priv_data = + tsi148_bridge->irq[i-1 ].callback[vec].priv_data; + +if (call !=NULL ) +call (i, vec, priv_data); +else +printk ("Spurilous VME interrupt, level:%x, " +"vector:%x\n" , i, vec); + + serviced |= (1 << i); + } + } + +return serviced; +} + +/* +* Top level interrupt handler. Clears appropriate interrupt status bits and +* then calls appropriate sub handler(s). +*/ +static irqreturn_t tsi148_irqhandler (int irq,void *dev_id) +{ +u32 stat, enable, serviced =0 ; + +/* Determine which interrupts are unmasked and set */ + enable =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + stat =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTS ); + +/* Only look at unmasked interrupts */ + stat &= enable; + +if (unlikely (!stat)) { +return IRQ_NONE ; + } + +/* Call subhandlers as appropriate */ +/* DMA irqs */ +if (stat & (TSI148_LCSR_INTS_DMA1S |TSI148_LCSR_INTS_DMA0S )) + serviced |=tsi148_DMA_irqhandler (stat); + +/* Location monitor irqs */ +if (stat & (TSI148_LCSR_INTS_LM3S |TSI148_LCSR_INTS_LM2S | +TSI148_LCSR_INTS_LM1S |TSI148_LCSR_INTS_LM0S )) + serviced |=tsi148_LM_irqhandler (stat); + +/* Mail box irqs */ +if (stat & (TSI148_LCSR_INTS_MB3S |TSI148_LCSR_INTS_MB2S | +TSI148_LCSR_INTS_MB1S |TSI148_LCSR_INTS_MB0S )) + serviced |=tsi148_MB_irqhandler (stat); + +/* PCI bus error */ +if (stat &TSI148_LCSR_INTS_PERRS ) + serviced |=tsi148_PERR_irqhandler (); + +/* VME bus error */ +if (stat &TSI148_LCSR_INTS_VERRS ) + serviced |=tsi148_VERR_irqhandler (); + +/* IACK irq */ +if (stat &TSI148_LCSR_INTS_IACKS ) + serviced |=tsi148_IACK_irqhandler (); + +/* VME bus irqs */ +if (stat & (TSI148_LCSR_INTS_IRQ7S |TSI148_LCSR_INTS_IRQ6S | +TSI148_LCSR_INTS_IRQ5S |TSI148_LCSR_INTS_IRQ4S | +TSI148_LCSR_INTS_IRQ3S |TSI148_LCSR_INTS_IRQ2S | +TSI148_LCSR_INTS_IRQ1S )) + serviced |=tsi148_VIRQ_irqhandler (stat); + +/* Clear serviced interrupts */ +iowrite32be (serviced, tsi148_bridge->base +TSI148_LCSR_INTC ); + +return IRQ_HANDLED ; +} + +static int tsi148_irq_init (struct vme_bridge *bridge) +{ +int result; +unsigned int tmp; +struct pci_dev *pdev; + +/* Need pdev */ + pdev = container_of(bridge->parent, struct pci_dev, dev); + +/* Initialise list for VME bus errors */ +INIT_LIST_HEAD (&(bridge->vme_errors)); + + result =request_irq (pdev->irq, + tsi148_irqhandler, +IRQF_SHARED , + driver_name, pdev); +if (result) { +dev_err (&pdev->dev,"Can't get assigned pci irq vector %02X\n" , + pdev->irq); +return result; + } + +/* Enable and unmask interrupts */ + tmp =TSI148_LCSR_INTEO_DMA1EO |TSI148_LCSR_INTEO_DMA0EO | +TSI148_LCSR_INTEO_MB3EO |TSI148_LCSR_INTEO_MB2EO | +TSI148_LCSR_INTEO_MB1EO |TSI148_LCSR_INTEO_MB0EO | +TSI148_LCSR_INTEO_PERREO |TSI148_LCSR_INTEO_VERREO | +TSI148_LCSR_INTEO_IACKEO ; + +/* XXX This leaves the following interrupts masked. +* TSI148_LCSR_INTEO_VIEEO +* TSI148_LCSR_INTEO_SYSFLEO +* TSI148_LCSR_INTEO_ACFLEO +*/ + +/* Don't enable Location Monitor interrupts here - they will be +* enabled when the location monitors are properly configured and +* a callback has been attached. +* TSI148_LCSR_INTEO_LM0EO +* TSI148_LCSR_INTEO_LM1EO +* TSI148_LCSR_INTEO_LM2EO +* TSI148_LCSR_INTEO_LM3EO +*/ + +/* Don't enable VME interrupts until we add a handler, else the board +* will respond to it and we don't want that unless it knows how to +* properly deal with it. +* TSI148_LCSR_INTEO_IRQ7EO +* TSI148_LCSR_INTEO_IRQ6EO +* TSI148_LCSR_INTEO_IRQ5EO +* TSI148_LCSR_INTEO_IRQ4EO +* TSI148_LCSR_INTEO_IRQ3EO +* TSI148_LCSR_INTEO_IRQ2EO +* TSI148_LCSR_INTEO_IRQ1EO +*/ + +iowrite32be (tmp, bridge->base +TSI148_LCSR_INTEO ); +iowrite32be (tmp, bridge->base +TSI148_LCSR_INTEN ); + +return 0 ; +} + +static void tsi148_irq_exit (struct pci_dev *pdev) +{ +/* Turn off interrupts */ +iowrite32be (0x0 , tsi148_bridge->base +TSI148_LCSR_INTEO ); +iowrite32be (0x0 , tsi148_bridge->base +TSI148_LCSR_INTEN ); + +/* Clear all interrupts */ +iowrite32be (0xFFFFFFFF , tsi148_bridge->base +TSI148_LCSR_INTC ); + +/* Detach interrupt handler */ +free_irq (pdev->irq, pdev); +} + +/* +* Check to see if an IACk has been received, return true (1) or false (0). +*/ +int tsi148_iack_received (void ) +{ +u32 tmp; + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_VICR ); + +if (tmp &TSI148_LCSR_VICR_IRQS ) +return 0 ; +else +return 1 ; +} + +/* +* Set up an VME interrupt +*/ +int tsi148_request_irq (int level,int statid, +void (*callback)(int level,int vector,void *priv_data), +void *priv_data) +{ +u32 tmp; + +/* Get semaphore */ +down (&(vme_irq)); + +if (tsi148_bridge->irq[level -1 ].callback[statid].func) { +up (&(vme_irq)); +printk ("VME Interrupt already taken\n" ); +return -EBUSY ; + } + + + tsi148_bridge->irq[level -1 ].count++; + tsi148_bridge->irq[level -1 ].callback[statid].priv_data = priv_data; + tsi148_bridge->irq[level -1 ].callback[statid].func = callback; + +/* Enable IRQ level */ + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + tmp |=TSI148_LCSR_INTEO_IRQEO [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEN ); + tmp |=TSI148_LCSR_INTEN_IRQEN [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEN ); + +/* Release semaphore */ +up (&(vme_irq)); + +return 0 ; +} + +/* +* Free VME interrupt +*/ +void tsi148_free_irq (int level,int statid) +{ +u32 tmp; + +/* Get semaphore */ +down (&(vme_irq)); + + tsi148_bridge->irq[level -1 ].callback[statid].func =NULL ; + tsi148_bridge->irq[level -1 ].callback[statid].priv_data =NULL ; + tsi148_bridge->irq[level -1 ].count--; + +/* Disable IRQ level */ +if (tsi148_bridge->irq[level -1 ].count ==0 ) { + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + tmp &= ~TSI148_LCSR_INTEO_IRQEO [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEN ); + tmp &= ~TSI148_LCSR_INTEN_IRQEN [level -1 ]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEN ); + } + +/* Release semaphore */ +up (&(vme_irq)); +} + +/* +* Generate a VME bus interrupt at the requested level & vector. Wait for +* interrupt to be acked. +* +* Only one interrupt can be generated at a time - so add a semaphore. +*/ +int tsi148_generate_irq (int level,int statid) +{ +u32 tmp; + +/* Get semaphore */ +down (&(vme_int)); + +/* Read VICR register */ + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_VICR ); + +/* Set Status/ID */ + tmp = (tmp & ~TSI148_LCSR_VICR_STID_M ) | + (statid &TSI148_LCSR_VICR_STID_M ); +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_VICR ); + +/* Assert VMEbus IRQ */ + tmp = tmp |TSI148_LCSR_VICR_IRQL [level]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_VICR ); + +/* XXX Consider implementing a timeout? */ +wait_event_interruptible (iack_queue,tsi148_iack_received ()); + +/* Release semaphore */ +up (&(vme_int)); + +return 0 ; +} + +/* +* Find the first error in this address range +*/ +static struct vme_bus_error *tsi148_find_error (vme_address_t aspace, +unsigned long long address,size_t count) +{ +struct list_head *err_pos; +struct vme_bus_error *vme_err, *valid =NULL ; +unsigned long long bound; + + bound = address + count; + +/* +* XXX We are currently not looking at the address space when parsing +* for errors. This is because parsing the Address Modifier Codes +* is going to be quite resource intensive to do properly. We +* should be OK just looking at the addresses and this is certainly +* much better than what we had before. +*/ + err_pos =NULL ; +/* Iterate through errors */ +list_for_each (err_pos, &(tsi148_bridge->vme_errors)) { + vme_err = list_entry(err_pos, struct vme_bus_error, list); +if ((vme_err->address >= address) && (vme_err->address < bound)){ + valid = vme_err; +break ; + } + } + +return valid; +} + +/* +* Clear errors in the provided address range. +*/ +static void tsi148_clear_errors (vme_address_t aspace, +unsigned long long address,size_t count) +{ +struct list_head *err_pos, *temp; +struct vme_bus_error *vme_err; +unsigned long long bound; + + bound = address + count; + +/* +* XXX We are currently not looking at the address space when parsing +* for errors. This is because parsing the Address Modifier Codes +* is going to be quite resource intensive to do properly. We +* should be OK just looking at the addresses and this is certainly +* much better than what we had before. +*/ + err_pos =NULL ; +/* Iterate through errors */ +list_for_each_safe (err_pos, temp, &(tsi148_bridge->vme_errors)) { + vme_err = list_entry(err_pos, struct vme_bus_error, list); + +if ((vme_err->address >= address) && (vme_err->address < bound)){ +list_del (err_pos); +kfree (vme_err); + } + } +} + +/* +* Initialize a slave window with the requested attributes. +*/ +int tsi148_slave_set (struct vme_slave_resource *image,int enabled, +unsigned long long vme_base,unsigned long long size, +dma_addr_t pci_base,vme_address_t aspace,vme_cycle_t cycle) +{ +unsigned int i, addr =0 , granularity =0 ; +unsigned int temp_ctl =0 ; +unsigned int vme_base_low, vme_base_high; +unsigned int vme_bound_low, vme_bound_high; +unsigned int pci_offset_low, pci_offset_high; +unsigned long long vme_bound, pci_offset; + +#if 0 +printk ("Set slave image %d to:\n" , image->number); +printk ("\tEnabled: %s\n" , (enabled ==1 )?"yes" :"no" ); +printk ("\tVME Base:0x%llx\n" , vme_base); +printk ("\tWindow Size:0x%llx\n" , size); +printk ("\tPCI Base:0x%lx\n" , (unsigned long )pci_base); +printk ("\tAddress Space:0x%x\n" , aspace); +printk ("\tTransfer Cycle Properties:0x%x\n" , cycle); +#endif + + i = image->number; + +switch (aspace) { +case VME_A16 : + granularity =0x10 ; + addr |=TSI148_LCSR_ITAT_AS_A16 ; +break ; +case VME_A24 : + granularity =0x1000 ; + addr |=TSI148_LCSR_ITAT_AS_A24 ; +break ; +case VME_A32 : + granularity =0x10000 ; + addr |=TSI148_LCSR_ITAT_AS_A32 ; +break ; +case VME_A64 : + granularity =0x10000 ; + addr |=TSI148_LCSR_ITAT_AS_A64 ; +break ; +case VME_CRCSR : +case VME_USER1 : +case VME_USER2 : +case VME_USER3 : +case VME_USER4 : +default : +printk ("Invalid address space\n" ); +return -EINVAL ; +break ; + } + +/* Convert 64-bit variables to 2x 32-bit variables */ +reg_split (vme_base, &vme_base_high, &vme_base_low); + +/* +* Bound address is a valid address for the window, adjust +* accordingly +*/ + vme_bound = vme_base + size - granularity; +reg_split (vme_bound, &vme_bound_high, &vme_bound_low); + pci_offset = (unsigned long long )pci_base - vme_base; +reg_split (pci_offset, &pci_offset_high, &pci_offset_low); + +if (vme_base_low & (granularity -1 )) { +printk ("Invalid VME base alignment\n" ); +return -EINVAL ; + } +if (vme_bound_low & (granularity -1 )) { +printk ("Invalid VME bound alignment\n" ); +return -EINVAL ; + } +if (pci_offset_low & (granularity -1 )) { +printk ("Invalid PCI Offset alignment\n" ); +return -EINVAL ; + } + +#if 0 +printk ("\tVME Bound:0x%llx\n" , vme_bound); +printk ("\tPCI Offset:0x%llx\n" , pci_offset); +#endif + +/* Disable while we are mucking around */ + temp_ctl =ioread32be (tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITAT ); + temp_ctl &= ~TSI148_LCSR_ITAT_EN ; +iowrite32be (temp_ctl, tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITAT ); + +/* Setup mapping */ +iowrite32be (vme_base_high, tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITSAU ); +iowrite32be (vme_base_low, tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITSAL ); +iowrite32be (vme_bound_high, tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITEAU ); +iowrite32be (vme_bound_low, tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITEAL ); +iowrite32be (pci_offset_high, tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITOFU ); +iowrite32be (pci_offset_low, tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITOFL ); + +/* XXX Prefetch stuff currently unsupported */ +#if 0 + +for (x =0 ; x <4 ; x++) { +if ((64 << x) >= vmeIn->prefetchSize) { +break ; + } + } +if (x ==4 ) + x--; + temp_ctl |= (x <<16 ); + +if (vmeIn->prefetchThreshold) +if (vmeIn->prefetchThreshold) + temp_ctl |=0x40000 ; +#endif + +/* Setup 2eSST speeds */ + temp_ctl &= ~TSI148_LCSR_ITAT_2eSSTM_M; +switch (cycle & (VME_2eSST160 | VME_2eSST267 | VME_2eSST320)) { +case VME_2eSST160: + temp_ctl |= TSI148_LCSR_ITAT_2eSSTM_160; +break ; +case VME_2eSST267: + temp_ctl |= TSI148_LCSR_ITAT_2eSSTM_267; +break ; +case VME_2eSST320: + temp_ctl |= TSI148_LCSR_ITAT_2eSSTM_320; +break ; + } + +/* Setup cycle types */ + temp_ctl &= ~(0x1F <<7 ); +if (cycle &VME_BLT ) + temp_ctl |=TSI148_LCSR_ITAT_BLT ; +if (cycle &VME_MBLT ) + temp_ctl |=TSI148_LCSR_ITAT_MBLT ; +if (cycle & VME_2eVME) + temp_ctl |= TSI148_LCSR_ITAT_2eVME; +if (cycle & VME_2eSST) + temp_ctl |= TSI148_LCSR_ITAT_2eSST; +if (cycle & VME_2eSSTB) + temp_ctl |= TSI148_LCSR_ITAT_2eSSTB; + +/* Setup address space */ + temp_ctl &= ~TSI148_LCSR_ITAT_AS_M ; + temp_ctl |= addr; + + temp_ctl &= ~0xF ; +if (cycle &VME_SUPER ) + temp_ctl |=TSI148_LCSR_ITAT_SUPR ; +if (cycle &VME_USER ) + temp_ctl |=TSI148_LCSR_ITAT_NPRIV ; +if (cycle &VME_PROG ) + temp_ctl |=TSI148_LCSR_ITAT_PGM ; +if (cycle &VME_DATA ) + temp_ctl |=TSI148_LCSR_ITAT_DATA ; + +/* Write ctl reg without enable */ +iowrite32be (temp_ctl, tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITAT ); + +if (enabled) + temp_ctl |=TSI148_LCSR_ITAT_EN ; + +iowrite32be (temp_ctl, tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITAT ); + +return 0 ; +} + +/* +* Get slave window configuration. +* +* XXX Prefetch currently unsupported. +*/ +int tsi148_slave_get (struct vme_slave_resource *image,int *enabled, +unsigned long long *vme_base,unsigned long long *size, +dma_addr_t *pci_base,vme_address_t *aspace,vme_cycle_t *cycle) +{ +unsigned int i, granularity =0 , ctl =0 ; +unsigned int vme_base_low, vme_base_high; +unsigned int vme_bound_low, vme_bound_high; +unsigned int pci_offset_low, pci_offset_high; +unsigned long long vme_bound, pci_offset; + + + i = image->number; + +/* Read registers */ + ctl =ioread32be (tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITAT ); + + vme_base_high =ioread32be (tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITSAU ); + vme_base_low =ioread32be (tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITSAL ); + vme_bound_high =ioread32be (tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITEAU ); + vme_bound_low =ioread32be (tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITEAL ); + pci_offset_high =ioread32be (tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITOFU ); + pci_offset_low =ioread32be (tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITOFL ); + +/* Convert 64-bit variables to 2x 32-bit variables */ +reg_join (vme_base_high, vme_base_low, vme_base); +reg_join (vme_bound_high, vme_bound_low, &vme_bound); +reg_join (pci_offset_high, pci_offset_low, &pci_offset); + + *pci_base = (dma_addr_t )vme_base + pci_offset; + + *enabled =0 ; + *aspace =0 ; + *cycle =0 ; + +if (ctl &TSI148_LCSR_ITAT_EN ) + *enabled =1 ; + +if ((ctl &TSI148_LCSR_ITAT_AS_M ) ==TSI148_LCSR_ITAT_AS_A16 ) { + granularity =0x10 ; + *aspace |=VME_A16 ; + } +if ((ctl &TSI148_LCSR_ITAT_AS_M ) ==TSI148_LCSR_ITAT_AS_A24 ) { + granularity =0x1000 ; + *aspace |=VME_A24 ; + } +if ((ctl &TSI148_LCSR_ITAT_AS_M ) ==TSI148_LCSR_ITAT_AS_A32 ) { + granularity =0x10000 ; + *aspace |=VME_A32 ; + } +if ((ctl &TSI148_LCSR_ITAT_AS_M ) ==TSI148_LCSR_ITAT_AS_A64 ) { + granularity =0x10000 ; + *aspace |=VME_A64 ; + } + +/* Need granularity before we set the size */ + *size = (unsigned long long )((vme_bound - *vme_base) + granularity); + + +if ((ctl & TSI148_LCSR_ITAT_2eSSTM_M) == TSI148_LCSR_ITAT_2eSSTM_160) + *cycle |= VME_2eSST160; +if ((ctl & TSI148_LCSR_ITAT_2eSSTM_M) == TSI148_LCSR_ITAT_2eSSTM_267) + *cycle |= VME_2eSST267; +if ((ctl & TSI148_LCSR_ITAT_2eSSTM_M) == TSI148_LCSR_ITAT_2eSSTM_320) + *cycle |= VME_2eSST320; + +if (ctl &TSI148_LCSR_ITAT_BLT ) + *cycle |=VME_BLT ; +if (ctl &TSI148_LCSR_ITAT_MBLT ) + *cycle |=VME_MBLT ; +if (ctl & TSI148_LCSR_ITAT_2eVME) + *cycle |= VME_2eVME; +if (ctl & TSI148_LCSR_ITAT_2eSST) + *cycle |= VME_2eSST; +if (ctl & TSI148_LCSR_ITAT_2eSSTB) + *cycle |= VME_2eSSTB; + +if (ctl &TSI148_LCSR_ITAT_SUPR ) + *cycle |=VME_SUPER ; +if (ctl &TSI148_LCSR_ITAT_NPRIV ) + *cycle |=VME_USER ; +if (ctl &TSI148_LCSR_ITAT_PGM ) + *cycle |=VME_PROG ; +if (ctl &TSI148_LCSR_ITAT_DATA ) + *cycle |=VME_DATA ; + +return 0 ; +} + +/* +* Allocate and map PCI Resource +*/ +static int tsi148_alloc_resource (struct vme_master_resource *image, +unsigned long long size) +{ +unsigned long long existing_size; +int retval =0 ; +struct pci_dev *pdev; + +/* Find pci_dev container of dev */ +if (tsi148_bridge->parent ==NULL ) { +printk ("Dev entry NULL\n" ); +return -EINVAL ; + } + pdev = container_of(tsi148_bridge->parent, struct pci_dev, dev); + + existing_size = (unsigned long long )(image->pci_resource.end - + image->pci_resource.start); + +/* If the existing size is OK, return */ +if (existing_size == (size -1 )) +return 0 ; + +if (existing_size !=0 ) { +iounmap (image->kern_base); + image->kern_base =NULL ; +if (image->pci_resource.name !=NULL ) +kfree (image->pci_resource.name); +release_resource (&(image->pci_resource)); +memset (&(image->pci_resource),0 ,sizeof (struct resource )); + } + +if (image->pci_resource.name ==NULL ) { + image->pci_resource.name =kmalloc (VMENAMSIZ +3 ,GFP_KERNEL ); +if (image->pci_resource.name ==NULL ) { +printk (KERN_ERR "Unable to allocate memory for resource" +" name\n" ); + retval = -ENOMEM ; + goto err_name; + } + } + +sprintf ((char *)image->pci_resource.name,"%s.%d" , tsi148_bridge->name, + image->number); + + image->pci_resource.start =0 ; + image->pci_resource.end = (unsigned long )size; + image->pci_resource.flags =IORESOURCE_MEM ; + + retval =pci_bus_alloc_resource (pdev->bus, + &(image->pci_resource), size, size,PCIBIOS_MIN_MEM , +0 ,NULL ,NULL ); +if (retval) { +printk (KERN_ERR "Failed to allocate mem resource for " +"window %d size 0x%lx start 0x%lx\n" , + image->number, (unsigned long )size, + (unsigned long )image->pci_resource.start); + goto err_resource; + } + + image->kern_base =ioremap_nocache ( + image->pci_resource.start, size); +if (image->kern_base ==NULL ) { +printk (KERN_ERR "Failed to remap resource\n" ); + retval = -ENOMEM ; + goto err_remap; + } + +return 0 ; + +iounmap (image->kern_base); + image->kern_base =NULL ; +err_remap: +release_resource (&(image->pci_resource)); +err_resource: +kfree (image->pci_resource.name); +memset (&(image->pci_resource),0 ,sizeof (struct resource )); +err_name: +return retval; +} + +/* +* Free and unmap PCI Resource +*/ +static void tsi148_free_resource (struct vme_master_resource *image) +{ +iounmap (image->kern_base); + image->kern_base =NULL ; +release_resource (&(image->pci_resource)); +kfree (image->pci_resource.name); +memset (&(image->pci_resource),0 ,sizeof (struct resource )); +} + +/* +* Set the attributes of an outbound window. +*/ +int tsi148_master_set (struct vme_master_resource *image,int enabled, +unsigned long long vme_base,unsigned long long size, +vme_address_t aspace,vme_cycle_t cycle,vme_width_t dwidth) +{ +int retval =0 ; +unsigned int i; +unsigned int temp_ctl =0 ; +unsigned int pci_base_low, pci_base_high; +unsigned int pci_bound_low, pci_bound_high; +unsigned int vme_offset_low, vme_offset_high; +unsigned long long pci_bound, vme_offset, pci_base; + +/* Verify input data */ +if (vme_base &0xFFFF ) { +printk ("Invalid VME Window alignment\n" ); + retval = -EINVAL ; + goto err_window; + } +if (size <0x10000 ) { +printk ("Invalid VME Window size\n" ); + retval = -EINVAL ; + goto err_window; + } + +spin_lock (&(image->lock)); + +/* Let's allocate the resource here rather than further up the stack as +* it avoids pushing loads of bus dependant stuff up the stack +*/ + retval =tsi148_alloc_resource (image, size); +if (retval) { +spin_unlock (&(image->lock)); +printk (KERN_ERR "Unable to allocate memory for resource " +"name\n" ); + retval = -ENOMEM ; + goto err_res; + } + + pci_base = (unsigned long long )image->pci_resource.start; + + +/* +* Bound address is a valid address for the window, adjust +* according to window granularity. +*/ + pci_bound = pci_base + (size -0x10000 ); + vme_offset = vme_base - pci_base; + +/* Convert 64-bit variables to 2x 32-bit variables */ +reg_split (pci_base, &pci_base_high, &pci_base_low); +reg_split (pci_bound, &pci_bound_high, &pci_bound_low); +reg_split (vme_offset, &vme_offset_high, &vme_offset_low); + +if (pci_base_low &0xFFFF ) { +spin_unlock (&(image->lock)); +printk ("Invalid PCI base alignment\n" ); + retval = -EINVAL ; + goto err_gran; + } +if (pci_bound_low &0xFFFF ) { +spin_unlock (&(image->lock)); +printk ("Invalid PCI bound alignment\n" ); + retval = -EINVAL ; + goto err_gran; + } +if (vme_offset_low &0xFFFF ) { +spin_unlock (&(image->lock)); +printk ("Invalid VME Offset alignment\n" ); + retval = -EINVAL ; + goto err_gran; + } + + i = image->number; + +/* Disable while we are mucking around */ + temp_ctl =ioread32be (tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTAT ); + temp_ctl &= ~TSI148_LCSR_OTAT_EN ; +iowrite32be (temp_ctl, tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTAT ); + +/* XXX Prefetch stuff currently unsupported */ +#if 0 +if (vmeOut->prefetchEnable) { + temp_ctl |=0x40000 ; +for (x =0 ; x <4 ; x++) { +if ((2 << x) >= vmeOut->prefetchSize) +break ; + } +if (x ==4 ) + x =3 ; + temp_ctl |= (x <<16 ); + } +#endif + +/* Setup 2eSST speeds */ + temp_ctl &= ~TSI148_LCSR_OTAT_2eSSTM_M; +switch (cycle & (VME_2eSST160 | VME_2eSST267 | VME_2eSST320)) { +case VME_2eSST160: + temp_ctl |= TSI148_LCSR_OTAT_2eSSTM_160; +break ; +case VME_2eSST267: + temp_ctl |= TSI148_LCSR_OTAT_2eSSTM_267; +break ; +case VME_2eSST320: + temp_ctl |= TSI148_LCSR_OTAT_2eSSTM_320; +break ; + } + +/* Setup cycle types */ +if (cycle &VME_BLT ) { + temp_ctl &= ~TSI148_LCSR_OTAT_TM_M ; + temp_ctl |=TSI148_LCSR_OTAT_TM_BLT ; + } +if (cycle &VME_MBLT ) { + temp_ctl &= ~TSI148_LCSR_OTAT_TM_M ; + temp_ctl |=TSI148_LCSR_OTAT_TM_MBLT ; + } +if (cycle & VME_2eVME) { + temp_ctl &= ~TSI148_LCSR_OTAT_TM_M ; + temp_ctl |= TSI148_LCSR_OTAT_TM_2eVME; + } +if (cycle & VME_2eSST) { + temp_ctl &= ~TSI148_LCSR_OTAT_TM_M ; + temp_ctl |= TSI148_LCSR_OTAT_TM_2eSST; + } +if (cycle & VME_2eSSTB) { +printk ("Currently not setting Broadcast Select Registers\n" ); + temp_ctl &= ~TSI148_LCSR_OTAT_TM_M ; + temp_ctl |= TSI148_LCSR_OTAT_TM_2eSSTB; + } + +/* Setup data width */ + temp_ctl &= ~TSI148_LCSR_OTAT_DBW_M ; +switch (dwidth) { +case VME_D16 : + temp_ctl |=TSI148_LCSR_OTAT_DBW_16 ; +break ; +case VME_D32 : + temp_ctl |=TSI148_LCSR_OTAT_DBW_32 ; +break ; +default : +spin_unlock (&(image->lock)); +printk ("Invalid data width\n" ); + retval = -EINVAL ; + goto err_dwidth; + } + +/* Setup address space */ + temp_ctl &= ~TSI148_LCSR_OTAT_AMODE_M ; +switch (aspace) { +case VME_A16 : + temp_ctl |=TSI148_LCSR_OTAT_AMODE_A16 ; +break ; +case VME_A24 : + temp_ctl |=TSI148_LCSR_OTAT_AMODE_A24 ; +break ; +case VME_A32 : + temp_ctl |=TSI148_LCSR_OTAT_AMODE_A32 ; +break ; +case VME_A64 : + temp_ctl |=TSI148_LCSR_OTAT_AMODE_A64 ; +break ; +case VME_CRCSR : + temp_ctl |=TSI148_LCSR_OTAT_AMODE_CRCSR ; +break ; +case VME_USER1 : + temp_ctl |=TSI148_LCSR_OTAT_AMODE_USER1 ; +break ; +case VME_USER2 : + temp_ctl |=TSI148_LCSR_OTAT_AMODE_USER2 ; +break ; +case VME_USER3 : + temp_ctl |=TSI148_LCSR_OTAT_AMODE_USER3 ; +break ; +case VME_USER4 : + temp_ctl |=TSI148_LCSR_OTAT_AMODE_USER4 ; +break ; +default : +spin_unlock (&(image->lock)); +printk ("Invalid address space\n" ); + retval = -EINVAL ; + goto err_aspace; +break ; + } + + temp_ctl &= ~(3 <<4 ); +if (cycle &VME_SUPER ) + temp_ctl |=TSI148_LCSR_OTAT_SUP ; +if (cycle &VME_PROG ) + temp_ctl |=TSI148_LCSR_OTAT_PGM ; + +/* Setup mapping */ +iowrite32be (pci_base_high, tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTSAU ); +iowrite32be (pci_base_low, tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTSAL ); +iowrite32be (pci_bound_high, tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTEAU ); +iowrite32be (pci_bound_low, tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTEAL ); +iowrite32be (vme_offset_high, tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTOFU ); +iowrite32be (vme_offset_low, tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTOFL ); + +/* XXX We need to deal with OTBS */ +#if 0 +iowrite32be (vmeOut->bcastSelect2esst, tsi148_bridge->base + +TSI148_LCSR_OT [i] +TSI148_LCSR_OFFSET_OTBS ); +#endif + +/* Write ctl reg without enable */ +iowrite32be (temp_ctl, tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTAT ); + +if (enabled) + temp_ctl |=TSI148_LCSR_OTAT_EN ; + +iowrite32be (temp_ctl, tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTAT ); + +spin_unlock (&(image->lock)); +return 0 ; + +err_aspace: +err_dwidth: +err_gran: +tsi148_free_resource (image); +err_res: +err_window: +return retval; + +} + +/* +* Set the attributes of an outbound window. +* +* XXX Not parsing prefetch information. +*/ +int __tsi148_master_get (struct vme_master_resource *image,int *enabled, +unsigned long long *vme_base,unsigned long long *size, +vme_address_t *aspace,vme_cycle_t *cycle,vme_width_t *dwidth) +{ +unsigned int i, ctl; +unsigned int pci_base_low, pci_base_high; +unsigned int pci_bound_low, pci_bound_high; +unsigned int vme_offset_low, vme_offset_high; + +unsigned long long pci_base, pci_bound, vme_offset; + + i = image->number; + + ctl =ioread32be (tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTAT ); + + pci_base_high =ioread32be (tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTSAU ); + pci_base_low =ioread32be (tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTSAL ); + pci_bound_high =ioread32be (tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTEAU ); + pci_bound_low =ioread32be (tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTEAL ); + vme_offset_high =ioread32be (tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTOFU ); + vme_offset_low =ioread32be (tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTOFL ); + +/* Convert 64-bit variables to 2x 32-bit variables */ +reg_join (pci_base_high, pci_base_low, &pci_base); +reg_join (pci_bound_high, pci_bound_low, &pci_bound); +reg_join (vme_offset_high, vme_offset_low, &vme_offset); + + *vme_base = pci_base + vme_offset; + *size = (unsigned long long )(pci_bound - pci_base) +0x10000 ; + + *enabled =0 ; + *aspace =0 ; + *cycle =0 ; + *dwidth =0 ; + +if (ctl &TSI148_LCSR_OTAT_EN ) + *enabled =1 ; + +/* Setup address space */ +if ((ctl &TSI148_LCSR_OTAT_AMODE_M ) ==TSI148_LCSR_OTAT_AMODE_A16 ) + *aspace |=VME_A16 ; +if ((ctl &TSI148_LCSR_OTAT_AMODE_M ) ==TSI148_LCSR_OTAT_AMODE_A24 ) + *aspace |=VME_A24 ; +if ((ctl &TSI148_LCSR_OTAT_AMODE_M ) ==TSI148_LCSR_OTAT_AMODE_A32 ) + *aspace |=VME_A32 ; +if ((ctl &TSI148_LCSR_OTAT_AMODE_M ) ==TSI148_LCSR_OTAT_AMODE_A64 ) + *aspace |=VME_A64 ; +if ((ctl &TSI148_LCSR_OTAT_AMODE_M ) ==TSI148_LCSR_OTAT_AMODE_CRCSR ) + *aspace |=VME_CRCSR ; +if ((ctl &TSI148_LCSR_OTAT_AMODE_M ) ==TSI148_LCSR_OTAT_AMODE_USER1 ) + *aspace |=VME_USER1 ; +if ((ctl &TSI148_LCSR_OTAT_AMODE_M ) ==TSI148_LCSR_OTAT_AMODE_USER2 ) + *aspace |=VME_USER2 ; +if ((ctl &TSI148_LCSR_OTAT_AMODE_M ) ==TSI148_LCSR_OTAT_AMODE_USER3 ) + *aspace |=VME_USER3 ; +if ((ctl &TSI148_LCSR_OTAT_AMODE_M ) ==TSI148_LCSR_OTAT_AMODE_USER4 ) + *aspace |=VME_USER4 ; + +/* Setup 2eSST speeds */ +if ((ctl & TSI148_LCSR_OTAT_2eSSTM_M) == TSI148_LCSR_OTAT_2eSSTM_160) + *cycle |= VME_2eSST160; +if ((ctl & TSI148_LCSR_OTAT_2eSSTM_M) == TSI148_LCSR_OTAT_2eSSTM_267) + *cycle |= VME_2eSST267; +if ((ctl & TSI148_LCSR_OTAT_2eSSTM_M) == TSI148_LCSR_OTAT_2eSSTM_320) + *cycle |= VME_2eSST320; + +/* Setup cycle types */ +if ((ctl &TSI148_LCSR_OTAT_TM_M ) ==TSI148_LCSR_OTAT_TM_SCT ) + *cycle |=VME_SCT ; +if ((ctl &TSI148_LCSR_OTAT_TM_M ) ==TSI148_LCSR_OTAT_TM_BLT ) + *cycle |=VME_BLT ; +if ((ctl &TSI148_LCSR_OTAT_TM_M ) ==TSI148_LCSR_OTAT_TM_MBLT ) + *cycle |=VME_MBLT ; +if ((ctl &TSI148_LCSR_OTAT_TM_M ) == TSI148_LCSR_OTAT_TM_2eVME) + *cycle |= VME_2eVME; +if ((ctl &TSI148_LCSR_OTAT_TM_M ) == TSI148_LCSR_OTAT_TM_2eSST) + *cycle |= VME_2eSST; +if ((ctl &TSI148_LCSR_OTAT_TM_M ) == TSI148_LCSR_OTAT_TM_2eSSTB) + *cycle |= VME_2eSSTB; + +if (ctl &TSI148_LCSR_OTAT_SUP ) + *cycle |=VME_SUPER ; +else + *cycle |=VME_USER ; + +if (ctl &TSI148_LCSR_OTAT_PGM ) + *cycle |=VME_PROG ; +else + *cycle |=VME_DATA ; + +/* Setup data width */ +if ((ctl &TSI148_LCSR_OTAT_DBW_M ) ==TSI148_LCSR_OTAT_DBW_16 ) + *dwidth =VME_D16 ; +if ((ctl &TSI148_LCSR_OTAT_DBW_M ) ==TSI148_LCSR_OTAT_DBW_32 ) + *dwidth =VME_D32 ; + +return 0 ; +} + + +int tsi148_master_get (struct vme_master_resource *image,int *enabled, +unsigned long long *vme_base,unsigned long long *size, +vme_address_t *aspace,vme_cycle_t *cycle,vme_width_t *dwidth) +{ +int retval; + +spin_lock (&(image->lock)); + + retval =__tsi148_master_get (image, enabled, vme_base, size, aspace, + cycle, dwidth); + +spin_unlock (&(image->lock)); + +return retval; +} + +ssize_t tsi148_master_read (struct vme_master_resource *image,void *buf, +size_t count,loff_t offset) +{ +int retval, enabled; +unsigned long long vme_base, size; +vme_address_t aspace; +vme_cycle_t cycle; +vme_width_t dwidth; +struct vme_bus_error *vme_err =NULL ; + +spin_lock (&(image->lock)); + +memcpy_fromio (buf, image->kern_base + offset, (unsigned int )count); + retval = count; + +if (!err_chk) + goto skip_chk; + +__tsi148_master_get (image, &enabled, &vme_base, &size, &aspace, &cycle, + &dwidth); + + vme_err =tsi148_find_error (aspace, vme_base + offset, count); +if (vme_err !=NULL ) { +dev_err (image->parent->parent,"First VME read error detected " +"an at address 0x%llx\n" , vme_err->address); + retval = vme_err->address - (vme_base + offset); +/* Clear down save errors in this address range */ +tsi148_clear_errors (aspace, vme_base + offset, count); + } + +skip_chk: +spin_unlock (&(image->lock)); + +return retval; +} + + +/* XXX We need to change vme_master_resource->sem to a spinlock so that read +* and write functions can be used in an interrupt context +*/ +ssize_t tsi148_master_write (struct vme_master_resource *image,void *buf, +size_t count,loff_t offset) +{ +int retval =0 , enabled; +unsigned long long vme_base, size; +vme_address_t aspace; +vme_cycle_t cycle; +vme_width_t dwidth; + +struct vme_bus_error *vme_err =NULL ; + +spin_lock (&(image->lock)); + +memcpy_toio (image->kern_base + offset, buf, (unsigned int )count); + retval = count; + +/* +* Writes are posted. We need to do a read on the VME bus to flush out +* all of the writes before we check for errors. We can't guarentee +* that reading the data we have just written is safe. It is believed +* that there isn't any read, write re-ordering, so we can read any +* location in VME space, so lets read the Device ID from the tsi148's +* own registers as mapped into CR/CSR space. +* +* We check for saved errors in the written address range/space. +*/ + +if (!err_chk) + goto skip_chk; + +/* +* Get window info first, to maximise the time that the buffers may +* fluch on their own +*/ +__tsi148_master_get (image, &enabled, &vme_base, &size, &aspace, &cycle, + &dwidth); + +ioread16 (flush_image->kern_base +0x7F000 ); + + vme_err =tsi148_find_error (aspace, vme_base + offset, count); +if (vme_err !=NULL ) { +printk ("First VME write error detected an at address 0x%llx\n" , + vme_err->address); + retval = vme_err->address - (vme_base + offset); +/* Clear down save errors in this address range */ +tsi148_clear_errors (aspace, vme_base + offset, count); + } + +skip_chk: +spin_unlock (&(image->lock)); + +return retval; +} + +/* +* Perform an RMW cycle on the VME bus. +* +* Requires a previously configured master window, returns final value. +*/ +unsigned int tsi148_master_rmw (struct vme_master_resource *image, +unsigned int mask,unsigned int compare,unsigned int swap, +loff_t offset) +{ +unsigned long long pci_addr; +unsigned int pci_addr_high, pci_addr_low; +u32 tmp, result; +int i; + + +/* Find the PCI address that maps to the desired VME address */ + i = image->number; + +/* Locking as we can only do one of these at a time */ +down (&(vme_rmw)); + +/* Lock image */ +spin_lock (&(image->lock)); + + pci_addr_high =ioread32be (tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTSAU ); + pci_addr_low =ioread32be (tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTSAL ); + +reg_join (pci_addr_high, pci_addr_low, &pci_addr); +reg_split (pci_addr + offset, &pci_addr_high, &pci_addr_low); + +/* Configure registers */ +iowrite32be (mask, tsi148_bridge->base +TSI148_LCSR_RMWEN ); +iowrite32be (compare, tsi148_bridge->base +TSI148_LCSR_RMWC ); +iowrite32be (swap, tsi148_bridge->base +TSI148_LCSR_RMWS ); +iowrite32be (pci_addr_high, tsi148_bridge->base +TSI148_LCSR_RMWAU ); +iowrite32be (pci_addr_low, tsi148_bridge->base +TSI148_LCSR_RMWAL ); + +/* Enable RMW */ + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_VMCTRL ); + tmp |=TSI148_LCSR_VMCTRL_RMWEN ; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_VMCTRL ); + +/* Kick process off with a read to the required address. */ + result =ioread32be (image->kern_base + offset); + +/* Disable RMW */ + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_VMCTRL ); + tmp &= ~TSI148_LCSR_VMCTRL_RMWEN ; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_VMCTRL ); + +spin_unlock (&(image->lock)); + +up (&(vme_rmw)); + +return result; +} + +static int tsi148_dma_set_vme_src_attributes (u32 *attr,vme_address_t aspace, +vme_cycle_t cycle,vme_width_t dwidth) +{ +/* Setup 2eSST speeds */ +switch (cycle & (VME_2eSST160 | VME_2eSST267 | VME_2eSST320)) { +case VME_2eSST160: + *attr |= TSI148_LCSR_DSAT_2eSSTM_160; +break ; +case VME_2eSST267: + *attr |= TSI148_LCSR_DSAT_2eSSTM_267; +break ; +case VME_2eSST320: + *attr |= TSI148_LCSR_DSAT_2eSSTM_320; +break ; + } + +/* Setup cycle types */ +if (cycle &VME_SCT ) { + *attr |=TSI148_LCSR_DSAT_TM_SCT ; + } +if (cycle &VME_BLT ) { + *attr |=TSI148_LCSR_DSAT_TM_BLT ; + } +if (cycle &VME_MBLT ) { + *attr |=TSI148_LCSR_DSAT_TM_MBLT ; + } +if (cycle & VME_2eVME) { + *attr |= TSI148_LCSR_DSAT_TM_2eVME; + } +if (cycle & VME_2eSST) { + *attr |= TSI148_LCSR_DSAT_TM_2eSST; + } +if (cycle & VME_2eSSTB) { +printk ("Currently not setting Broadcast Select Registers\n" ); + *attr |= TSI148_LCSR_DSAT_TM_2eSSTB; + } + +/* Setup data width */ +switch (dwidth) { +case VME_D16 : + *attr |=TSI148_LCSR_DSAT_DBW_16 ; +break ; +case VME_D32 : + *attr |=TSI148_LCSR_DSAT_DBW_32 ; +break ; +default : +printk ("Invalid data width\n" ); +return -EINVAL ; + } + +/* Setup address space */ +switch (aspace) { +case VME_A16 : + *attr |=TSI148_LCSR_DSAT_AMODE_A16 ; +break ; +case VME_A24 : + *attr |=TSI148_LCSR_DSAT_AMODE_A24 ; +break ; +case VME_A32 : + *attr |=TSI148_LCSR_DSAT_AMODE_A32 ; +break ; +case VME_A64 : + *attr |=TSI148_LCSR_DSAT_AMODE_A64 ; +break ; +case VME_CRCSR : + *attr |=TSI148_LCSR_DSAT_AMODE_CRCSR ; +break ; +case VME_USER1 : + *attr |=TSI148_LCSR_DSAT_AMODE_USER1 ; +break ; +case VME_USER2 : + *attr |=TSI148_LCSR_DSAT_AMODE_USER2 ; +break ; +case VME_USER3 : + *attr |=TSI148_LCSR_DSAT_AMODE_USER3 ; +break ; +case VME_USER4 : + *attr |=TSI148_LCSR_DSAT_AMODE_USER4 ; +break ; +default : +printk ("Invalid address space\n" ); +return -EINVAL ; +break ; + } + +if (cycle &VME_SUPER ) + *attr |=TSI148_LCSR_DSAT_SUP ; +if (cycle &VME_PROG ) + *attr |=TSI148_LCSR_DSAT_PGM ; + +return 0 ; +} + +static int tsi148_dma_set_vme_dest_attributes (u32 *attr,vme_address_t aspace, +vme_cycle_t cycle,vme_width_t dwidth) +{ +/* Setup 2eSST speeds */ +switch (cycle & (VME_2eSST160 | VME_2eSST267 | VME_2eSST320)) { +case VME_2eSST160: + *attr |= TSI148_LCSR_DDAT_2eSSTM_160; +break ; +case VME_2eSST267: + *attr |= TSI148_LCSR_DDAT_2eSSTM_267; +break ; +case VME_2eSST320: + *attr |= TSI148_LCSR_DDAT_2eSSTM_320; +break ; + } + +/* Setup cycle types */ +if (cycle &VME_SCT ) { + *attr |=TSI148_LCSR_DDAT_TM_SCT ; + } +if (cycle &VME_BLT ) { + *attr |=TSI148_LCSR_DDAT_TM_BLT ; + } +if (cycle &VME_MBLT ) { + *attr |=TSI148_LCSR_DDAT_TM_MBLT ; + } +if (cycle & VME_2eVME) { + *attr |= TSI148_LCSR_DDAT_TM_2eVME; + } +if (cycle & VME_2eSST) { + *attr |= TSI148_LCSR_DDAT_TM_2eSST; + } +if (cycle & VME_2eSSTB) { +printk ("Currently not setting Broadcast Select Registers\n" ); + *attr |= TSI148_LCSR_DDAT_TM_2eSSTB; + } + +/* Setup data width */ +switch (dwidth) { +case VME_D16 : + *attr |=TSI148_LCSR_DDAT_DBW_16 ; +break ; +case VME_D32 : + *attr |=TSI148_LCSR_DDAT_DBW_32 ; +break ; +default : +printk ("Invalid data width\n" ); +return -EINVAL ; + } + +/* Setup address space */ +switch (aspace) { +case VME_A16 : + *attr |=TSI148_LCSR_DDAT_AMODE_A16 ; +break ; +case VME_A24 : + *attr |=TSI148_LCSR_DDAT_AMODE_A24 ; +break ; +case VME_A32 : + *attr |=TSI148_LCSR_DDAT_AMODE_A32 ; +break ; +case VME_A64 : + *attr |=TSI148_LCSR_DDAT_AMODE_A64 ; +break ; +case VME_CRCSR : + *attr |=TSI148_LCSR_DDAT_AMODE_CRCSR ; +break ; +case VME_USER1 : + *attr |=TSI148_LCSR_DDAT_AMODE_USER1 ; +break ; +case VME_USER2 : + *attr |=TSI148_LCSR_DDAT_AMODE_USER2 ; +break ; +case VME_USER3 : + *attr |=TSI148_LCSR_DDAT_AMODE_USER3 ; +break ; +case VME_USER4 : + *attr |=TSI148_LCSR_DDAT_AMODE_USER4 ; +break ; +default : +printk ("Invalid address space\n" ); +return -EINVAL ; +break ; + } + +if (cycle &VME_SUPER ) + *attr |=TSI148_LCSR_DDAT_SUP ; +if (cycle &VME_PROG ) + *attr |=TSI148_LCSR_DDAT_PGM ; + +return 0 ; +} + +/* +* Add a link list descriptor to the list +* +* XXX Need to handle 2eSST Broadcast select bits +*/ +int tsi148_dma_list_add (struct vme_dma_list *list,struct vme_dma_attr *src, +struct vme_dma_attr *dest,size_t count) +{ +struct tsi148_dma_entry *entry, *prev; +u32 address_high, address_low; +struct vme_dma_pattern *pattern_attr; +struct vme_dma_pci *pci_attr; +struct vme_dma_vme *vme_attr; +dma_addr_t desc_ptr; +int retval =0 ; + +/* XXX descriptor must be aligned on 64-bit boundaries */ + entry = (struct tsi148_dma_entry *)kmalloc ( +sizeof (struct tsi148_dma_entry ),GFP_KERNEL ); +if (entry ==NULL ) { +printk ("Failed to allocate memory for dma resource " +"structure\n" ); + retval = -ENOMEM ; + goto err_mem; + } + +/* Test descriptor alignment */ +if ((unsigned long )&(entry->descriptor) &0x7 ) { +printk ("Descriptor not aligned to 8 byte boundary as " +"required: %p\n" , &(entry->descriptor)); + retval = -EINVAL ; + goto err_align; + } + +/* Given we are going to fill out the structure, we probably don't +* need to zero it, but better safe than sorry for now. +*/ +memset (&(entry->descriptor),0 ,sizeof (struct tsi148_dma_descriptor )); + +/* Fill out source part */ +switch (src->type) { +case VME_DMA_PATTERN : + pattern_attr = (struct vme_dma_pattern *)src->private; + + entry->descriptor.dsal = pattern_attr->pattern; + entry->descriptor.dsat =TSI148_LCSR_DSAT_TYP_PAT ; +/* Default behaviour is 32 bit pattern */ +if (pattern_attr->type &VME_DMA_PATTERN_BYTE ) { + entry->descriptor.dsat |=TSI148_LCSR_DSAT_PSZ ; + } +/* It seems that the default behaviour is to increment */ +if ((pattern_attr->type &VME_DMA_PATTERN_INCREMENT ) ==0 ) { + entry->descriptor.dsat |=TSI148_LCSR_DSAT_NIN ; + } +break ; +case VME_DMA_PCI : + pci_attr = (struct vme_dma_pci *)src->private; + +reg_split ((unsigned long long )pci_attr->address, &address_high, + &address_low); + entry->descriptor.dsau = address_high; + entry->descriptor.dsal = address_low; + entry->descriptor.dsat =TSI148_LCSR_DSAT_TYP_PCI ; +break ; +case VME_DMA_VME : + vme_attr = (struct vme_dma_vme *)src->private; + +reg_split ((unsigned long long )vme_attr->address, &address_high, + &address_low); + entry->descriptor.dsau = address_high; + entry->descriptor.dsal = address_low; + entry->descriptor.dsat =TSI148_LCSR_DSAT_TYP_VME ; + + retval =tsi148_dma_set_vme_src_attributes ( + &(entry->descriptor.dsat), vme_attr->aspace, + vme_attr->cycle, vme_attr->dwidth); +if (retval <0 ) + goto err_source; +break ; +default : +printk ("Invalid source type\n" ); + retval = -EINVAL ; + goto err_source; +break ; + } + +/* Assume last link - this will be over-written by adding another */ + entry->descriptor.dnlau =0 ; + entry->descriptor.dnlal =TSI148_LCSR_DNLAL_LLA ; + + +/* Fill out destination part */ +switch (dest->type) { +case VME_DMA_PCI : + pci_attr = (struct vme_dma_pci *)dest->private; + +reg_split ((unsigned long long )pci_attr->address, &address_high, + &address_low); + entry->descriptor.ddau = address_high; + entry->descriptor.ddal = address_low; + entry->descriptor.ddat =TSI148_LCSR_DDAT_TYP_PCI ; +break ; +case VME_DMA_VME : + vme_attr = (struct vme_dma_vme *)dest->private; + +reg_split ((unsigned long long )vme_attr->address, &address_high, + &address_low); + entry->descriptor.ddau = address_high; + entry->descriptor.ddal = address_low; + entry->descriptor.ddat =TSI148_LCSR_DDAT_TYP_VME ; + + retval =tsi148_dma_set_vme_dest_attributes ( + &(entry->descriptor.ddat), vme_attr->aspace, + vme_attr->cycle, vme_attr->dwidth); +if (retval <0 ) + goto err_dest; +break ; +default : +printk ("Invalid destination type\n" ); + retval = -EINVAL ; + goto err_dest; +break ; + } + +/* Fill out count */ + entry->descriptor.dcnt = (u32 )count; + +/* Add to list */ +list_add_tail (&(entry->list), &(list->entries)); + +/* Fill out previous descriptors "Next Address" */ +if (entry->list.prev != &(list->entries)){ + prev = list_entry(entry->list.prev, struct tsi148_dma_entry, + list); +/* We need the bus address for the pointer */ + desc_ptr =virt_to_bus (&(entry->descriptor)); +reg_split (desc_ptr, &(prev->descriptor.dnlau), + &(prev->descriptor.dnlal)); + } + +return 0 ; + +err_dest: +err_source: +err_align: +kfree (entry); +err_mem: +return retval; +} + +/* +* Check to see if the provided DMA channel is busy. +*/ +static int tsi148_dma_busy (int channel) +{ +u32 tmp; + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_DMA [channel] + +TSI148_LCSR_OFFSET_DSTA ); + +if (tmp &TSI148_LCSR_DSTA_BSY ) +return 0 ; +else +return 1 ; + +} + +/* +* Execute a previously generated link list +* +* XXX Need to provide control register configuration. +*/ +int tsi148_dma_list_exec (struct vme_dma_list *list) +{ +struct vme_dma_resource *ctrlr; +int channel, retval =0 ; +struct tsi148_dma_entry *entry; +dma_addr_t bus_addr; +u32 bus_addr_high, bus_addr_low; +u32 val, dctlreg =0 ; +#if 0 +int x; +#endif + + ctrlr = list->parent; + +down (&(ctrlr->sem)); + + channel = ctrlr->number; + +if (!list_empty (&(ctrlr->running))) { +/* +* XXX We have an active DMA transfer and currently haven't +* sorted out the mechanism for "pending" DMA transfers. +* Return busy. +*/ +/* Need to add to pending here */ +up (&(ctrlr->sem)); +return -EBUSY ; + }else { +list_add (&(list->list), &(ctrlr->running)); + } +#if 0 +/* XXX Still todo */ +for (x =0 ; x <8 ; x++) {/* vme block size */ +if ((32 << x) >= vmeDma->maxVmeBlockSize) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<12 ); + +for (x =0 ; x <8 ; x++) {/* pci block size */ +if ((32 << x) >= vmeDma->maxPciBlockSize) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<4 ); + +if (vmeDma->vmeBackOffTimer) { +for (x =1 ; x <8 ; x++) {/* vme timer */ +if ((1 << (x -1 )) >= vmeDma->vmeBackOffTimer) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<8 ); + } + +if (vmeDma->pciBackOffTimer) { +for (x =1 ; x <8 ; x++) {/* pci timer */ +if ((1 << (x -1 )) >= vmeDma->pciBackOffTimer) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<0 ); + } +#endif + +/* Get first bus address and write into registers */ + entry = list_first_entry(&(list->entries), struct tsi148_dma_entry, + list); + + bus_addr =virt_to_bus (&(entry->descriptor)); + +up (&(ctrlr->sem)); + +reg_split (bus_addr, &bus_addr_high, &bus_addr_low); + +iowrite32be (bus_addr_high, tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DNLAU ); +iowrite32be (bus_addr_low, tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DNLAL ); + +/* Start the operation */ +iowrite32be (dctlreg |TSI148_LCSR_DCTL_DGO , tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DCTL ); + +wait_event_interruptible (dma_queue[channel],tsi148_dma_busy (channel)); +/* +* Read status register, this register is valid until we kick off a +* new transfer. +*/ + val =ioread32be (tsi148_bridge->base +TSI148_LCSR_DMA [channel] + +TSI148_LCSR_OFFSET_DSTA ); + +if (val &TSI148_LCSR_DSTA_VBE ) { +printk (KERN_ERR "tsi148: DMA Error. DSTA=%08X\n" , val); + retval = -EIO ; + } + +/* Remove list from running list */ +down (&(ctrlr->sem)); +list_del (&(list->list)); +up (&(ctrlr->sem)); + +return retval; +} + +/* +* Clean up a previously generated link list +* +* We have a separate function, don't assume that the chain can't be reused. +*/ +int tsi148_dma_list_empty (struct vme_dma_list *list) +{ +struct list_head *pos, *temp; +struct tsi148_dma_entry *entry; + +/* detach and free each entry */ +list_for_each_safe (pos, temp, &(list->entries)) { +list_del (pos); + entry = list_entry(pos, struct tsi148_dma_entry, list); +kfree (entry); + } + +return (0 ); +} + +/* +* All 4 location monitors reside at the same base - this is therefore a +* system wide configuration. +* +* This does not enable the LM monitor - that should be done when the first +* callback is attached and disabled when the last callback is removed. +*/ +int tsi148_lm_set (unsigned long long lm_base,vme_address_t aspace, +vme_cycle_t cycle) +{ +u32 lm_base_high, lm_base_low, lm_ctl =0 ; +int i; + +/* Get semaphore */ +down (&(vme_lm)); + +/* If we already have a callback attached, we can't move it! */ +for (i =0 ; i <4 ; i++) { +if (lm_callback[i] !=NULL ) { +up (&(vme_lm)); +printk ("Location monitor callback attached, can't " +"reset\n" ); +return -EBUSY ; + } + } + +switch (aspace) { +case VME_A16 : + lm_ctl |=TSI148_LCSR_LMAT_AS_A16 ; +break ; +case VME_A24 : + lm_ctl |=TSI148_LCSR_LMAT_AS_A24 ; +break ; +case VME_A32 : + lm_ctl |=TSI148_LCSR_LMAT_AS_A32 ; +break ; +case VME_A64 : + lm_ctl |=TSI148_LCSR_LMAT_AS_A64 ; +break ; +default : +up (&(vme_lm)); +printk ("Invalid address space\n" ); +return -EINVAL ; +break ; + } + +if (cycle &VME_SUPER ) + lm_ctl |=TSI148_LCSR_LMAT_SUPR ; +if (cycle &VME_USER ) + lm_ctl |=TSI148_LCSR_LMAT_NPRIV ; +if (cycle &VME_PROG ) + lm_ctl |=TSI148_LCSR_LMAT_PGM ; +if (cycle &VME_DATA ) + lm_ctl |=TSI148_LCSR_LMAT_DATA ; + +reg_split (lm_base, &lm_base_high, &lm_base_low); + +iowrite32be (lm_base_high, tsi148_bridge->base +TSI148_LCSR_LMBAU ); +iowrite32be (lm_base_low, tsi148_bridge->base +TSI148_LCSR_LMBAL ); +iowrite32be (lm_ctl, tsi148_bridge->base +TSI148_LCSR_LMAT ); + +up (&(vme_lm)); + +return 0 ; +} + +/* Get configuration of the callback monitor and return whether it is enabled +* or disabled. +*/ +int tsi148_lm_get (unsigned long long *lm_base,vme_address_t *aspace, +vme_cycle_t *cycle) +{ +u32 lm_base_high, lm_base_low, lm_ctl, enabled =0 ; + +/* Get semaphore */ +down (&(vme_lm)); + + lm_base_high =ioread32be (tsi148_bridge->base +TSI148_LCSR_LMBAU ); + lm_base_low =ioread32be (tsi148_bridge->base +TSI148_LCSR_LMBAL ); + lm_ctl =ioread32be (tsi148_bridge->base +TSI148_LCSR_LMAT ); + +reg_join (lm_base_high, lm_base_low, lm_base); + +if (lm_ctl &TSI148_LCSR_LMAT_EN ) + enabled =1 ; + +if ((lm_ctl &TSI148_LCSR_LMAT_AS_M ) ==TSI148_LCSR_LMAT_AS_A16 ) { + *aspace |=VME_A16 ; + } +if ((lm_ctl &TSI148_LCSR_LMAT_AS_M ) ==TSI148_LCSR_LMAT_AS_A24 ) { + *aspace |=VME_A24 ; + } +if ((lm_ctl &TSI148_LCSR_LMAT_AS_M ) ==TSI148_LCSR_LMAT_AS_A32 ) { + *aspace |=VME_A32 ; + } +if ((lm_ctl &TSI148_LCSR_LMAT_AS_M ) ==TSI148_LCSR_LMAT_AS_A64 ) { + *aspace |=VME_A64 ; + } + +if (lm_ctl &TSI148_LCSR_LMAT_SUPR ) + *cycle |=VME_SUPER ; +if (lm_ctl &TSI148_LCSR_LMAT_NPRIV ) + *cycle |=VME_USER ; +if (lm_ctl &TSI148_LCSR_LMAT_PGM ) + *cycle |=VME_PROG ; +if (lm_ctl &TSI148_LCSR_LMAT_DATA ) + *cycle |=VME_DATA ; + +up (&(vme_lm)); + +return enabled; +} + +/* +* Attach a callback to a specific location monitor. +* +* Callback will be passed the monitor triggered. +*/ +int tsi148_lm_attach (int monitor,void (*callback)(int )) +{ +u32 lm_ctl, tmp; + +/* Get semaphore */ +down (&(vme_lm)); + +/* Ensure that the location monitor is configured - need PGM or DATA */ + lm_ctl =ioread32be (tsi148_bridge->base +TSI148_LCSR_LMAT ); +if ((lm_ctl & (TSI148_LCSR_LMAT_PGM |TSI148_LCSR_LMAT_DATA )) ==0 ) { +up (&(vme_lm)); +printk ("Location monitor not properly configured\n" ); +return -EINVAL ; + } + +/* Check that a callback isn't already attached */ +if (lm_callback[monitor] !=NULL ) { +up (&(vme_lm)); +printk ("Existing callback attached\n" ); +return -EBUSY ; + } + +/* Attach callback */ + lm_callback[monitor] = callback; + +/* Enable Location Monitor interrupt */ + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEN ); + tmp |=TSI148_LCSR_INTEN_LMEN [monitor]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEN ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + tmp |=TSI148_LCSR_INTEO_LMEO [monitor]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); + +/* Ensure that global Location Monitor Enable set */ +if ((lm_ctl &TSI148_LCSR_LMAT_EN ) ==0 ) { + lm_ctl |=TSI148_LCSR_LMAT_EN ; +iowrite32be (lm_ctl, tsi148_bridge->base +TSI148_LCSR_LMAT ); + } + +up (&(vme_lm)); + +return 0 ; +} + +/* +* Detach a callback function forn a specific location monitor. +*/ +int tsi148_lm_detach (int monitor) +{ +u32 lm_en, tmp; + +/* Get semaphore */ +down (&(vme_lm)); + +/* Disable Location Monitor and ensure previous interrupts are clear */ + lm_en =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEN ); + lm_en &= ~TSI148_LCSR_INTEN_LMEN [monitor]; +iowrite32be (lm_en, tsi148_bridge->base +TSI148_LCSR_INTEN ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_INTEO ); + tmp &= ~TSI148_LCSR_INTEO_LMEO [monitor]; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_INTEO ); + +iowrite32be (TSI148_LCSR_INTC_LMC [monitor], + tsi148_bridge->base +TSI148_LCSR_INTEO ); + +/* Detach callback */ + lm_callback[monitor] =NULL ; + +/* If all location monitors disabled, disable global Location Monitor */ +if ((lm_en & (TSI148_LCSR_INTS_LM0S |TSI148_LCSR_INTS_LM1S | +TSI148_LCSR_INTS_LM2S |TSI148_LCSR_INTS_LM3S )) ==0 ) { + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_LMAT ); + tmp &= ~TSI148_LCSR_LMAT_EN ; +iowrite32be (tmp, tsi148_bridge->base +TSI148_LCSR_LMAT ); + } + +up (&(vme_lm)); + +return 0 ; +} + +/* +* Determine Geographical Addressing +*/ +int tsi148_slot_get (void ) +{ +u32 slot =0 ; + + slot =ioread32be (tsi148_bridge->base +TSI148_LCSR_VSTAT ); + slot = slot &TSI148_LCSR_VSTAT_GA_M ; +return (int )slot; +} + +static int __init tsi148_init (void ) +{ +return pci_register_driver (&tsi148_driver); +} + +/* +* Configure CR/CSR space +* +* Access to the CR/CSR can be configured at power-up. The location of the +* CR/CSR registers in the CR/CSR address space is determined by the boards +* Auto-ID or Geographic address. This function ensures that the window is +* enabled at an offset consistent with the boards geopgraphic address. +* +* Each board has a 512kB window, with the highest 4kB being used for the +* boards registers, this means there is a fix length 508kB window which must +* be mapped onto PCI memory. +*/ +static int tsi148_crcsr_init (struct pci_dev *pdev) +{ +u32 cbar, crat, vstat; +u32 crcsr_bus_high, crcsr_bus_low; +int retval; + +/* Allocate mem for CR/CSR image */ + crcsr_kernel =pci_alloc_consistent (pdev,VME_CRCSR_BUF_SIZE , + &crcsr_bus); +if (crcsr_kernel ==NULL ) { +dev_err (&pdev->dev,"Failed to allocate memory for CR/CSR " +"image\n" ); +return -ENOMEM ; + } + +memset (crcsr_kernel,0 ,VME_CRCSR_BUF_SIZE ); + +reg_split (crcsr_bus, &crcsr_bus_high, &crcsr_bus_low); + +iowrite32be (crcsr_bus_high, tsi148_bridge->base +TSI148_LCSR_CROU ); +iowrite32be (crcsr_bus_low, tsi148_bridge->base +TSI148_LCSR_CROL ); + +/* Ensure that the CR/CSR is configured at the correct offset */ + cbar =ioread32be (tsi148_bridge->base +TSI148_CBAR ); + cbar = (cbar &TSI148_CRCSR_CBAR_M )>>3 ; + + vstat =tsi148_slot_get (); + +if (cbar != vstat) { +dev_info (&pdev->dev,"Setting CR/CSR offset\n" ); +iowrite32be (cbar<<3 , tsi148_bridge->base +TSI148_CBAR ); + } +dev_info (&pdev->dev,"CR/CSR Offset: %d\n" , cbar); + + crat =ioread32be (tsi148_bridge->base +TSI148_LCSR_CRAT ); +if (crat &TSI148_LCSR_CRAT_EN ) { +dev_info (&pdev->dev,"Enabling CR/CSR space\n" ); +iowrite32be (crat |TSI148_LCSR_CRAT_EN , + tsi148_bridge->base +TSI148_LCSR_CRAT ); + }else +dev_info (&pdev->dev,"CR/CSR already enabled\n" ); + +/* If we want flushed, error-checked writes, set up a window +* over the CR/CSR registers. We read from here to safely flush +* through VME writes. +*/ +if (err_chk) { + retval =tsi148_master_set (flush_image,1 , (vstat *0x80000 ), +0x80000 ,VME_CRCSR ,VME_SCT ,VME_D16 ); +if (retval) +dev_err (&pdev->dev,"Configuring flush image failed\n" ); + } + +return 0 ; + +} + +static void tsi148_crcsr_exit (struct pci_dev *pdev) +{ +u32 crat; + +/* Turn off CR/CSR space */ + crat =ioread32be (tsi148_bridge->base +TSI148_LCSR_CRAT ); +iowrite32be (crat & ~TSI148_LCSR_CRAT_EN , + tsi148_bridge->base +TSI148_LCSR_CRAT ); + +/* Free image */ +iowrite32be (0 , tsi148_bridge->base +TSI148_LCSR_CROU ); +iowrite32be (0 , tsi148_bridge->base +TSI148_LCSR_CROL ); + +pci_free_consistent (pdev,VME_CRCSR_BUF_SIZE , crcsr_kernel, crcsr_bus); +} + +static int tsi148_probe (struct pci_dev *pdev,const struct pci_device_id *id) +{ +int retval, i, master_num; +u32 data; +struct list_head *pos =NULL ; +struct vme_master_resource *master_image; +struct vme_slave_resource *slave_image; +struct vme_dma_resource *dma_ctrlr; + +/* If we want to support more than one of each bridge, we need to +* dynamically generate this so we get one per device +*/ + tsi148_bridge = (struct vme_bridge *)kmalloc (sizeof (struct vme_bridge ), +GFP_KERNEL ); +if (tsi148_bridge ==NULL ) { +dev_err (&pdev->dev,"Failed to allocate memory for device " +"structure\n" ); + retval = -ENOMEM ; + goto err_struct; + } + +memset (tsi148_bridge,0 ,sizeof (struct vme_bridge )); + +/* Enable the device */ + retval =pci_enable_device (pdev); +if (retval) { +dev_err (&pdev->dev,"Unable to enable device\n" ); + goto err_enable; + } + +/* Map Registers */ + retval =pci_request_regions (pdev, driver_name); +if (retval) { +dev_err (&pdev->dev,"Unable to reserve resources\n" ); + goto err_resource; + } + +/* map registers in BAR 0 */ + tsi148_bridge->base =ioremap_nocache (pci_resource_start (pdev,0 ),4096 ); +if (!tsi148_bridge->base) { +dev_err (&pdev->dev,"Unable to remap CRG region\n" ); + retval = -EIO ; + goto err_remap; + } + +/* Check to see if the mapping worked out */ + data =ioread32 (tsi148_bridge->base +TSI148_PCFS_ID ) &0x0000FFFF ; +if (data !=PCI_VENDOR_ID_TUNDRA ) { +dev_err (&pdev->dev,"CRG region check failed\n" ); + retval = -EIO ; + goto err_test; + } + +/* Initialize wait queues & mutual exclusion flags */ +/* XXX These need to be moved to the vme_bridge structure */ +init_waitqueue_head (&dma_queue[0 ]); +init_waitqueue_head (&dma_queue[1 ]); +init_waitqueue_head (&iack_queue); +init_MUTEX (&(vme_int)); +init_MUTEX (&(vme_irq)); +init_MUTEX (&(vme_rmw)); +init_MUTEX (&(vme_lm)); + + tsi148_bridge->parent = &(pdev->dev); +strcpy (tsi148_bridge->name, driver_name); + +/* Setup IRQ */ + retval =tsi148_irq_init (tsi148_bridge); +if (retval !=0 ) { +dev_err (&pdev->dev,"Chip Initialization failed.\n" ); + goto err_irq; + } + +/* If we are going to flush writes, we need to read from the VME bus. +* We need to do this safely, thus we read the devices own CR/CSR +* register. To do this we must set up a window in CR/CSR space and +* hence have one less master window resource available. +*/ + master_num =TSI148_MAX_MASTER ; +if (err_chk){ + master_num--; +/* XXX */ + flush_image = (struct vme_master_resource *)kmalloc ( +sizeof (struct vme_master_resource ),GFP_KERNEL ); +if (flush_image ==NULL ) { +dev_err (&pdev->dev,"Failed to allocate memory for " +"flush resource structure\n" ); + retval = -ENOMEM ; + goto err_master; + } + flush_image->parent = tsi148_bridge; +spin_lock_init (&(flush_image->lock)); + flush_image->locked =1 ; + flush_image->number = master_num; + flush_image->address_attr =VME_A16 |VME_A24 |VME_A32 | +VME_A64 ; + flush_image->cycle_attr =VME_SCT |VME_BLT |VME_MBLT | + VME_2eVME | VME_2eSST | VME_2eSSTB | VME_2eSST160 | + VME_2eSST267 | VME_2eSST320 |VME_SUPER |VME_USER | +VME_PROG |VME_DATA ; + flush_image->width_attr =VME_D16 |VME_D32 ; +memset (&(flush_image->pci_resource),0 , +sizeof (struct resource )); + flush_image->kern_base =NULL ; + } + +/* Add master windows to list */ +INIT_LIST_HEAD (&(tsi148_bridge->master_resources)); +for (i =0 ; i < master_num; i++) { + master_image = (struct vme_master_resource *)kmalloc ( +sizeof (struct vme_master_resource ),GFP_KERNEL ); +if (master_image ==NULL ) { +dev_err (&pdev->dev,"Failed to allocate memory for " +"master resource structure\n" ); + retval = -ENOMEM ; + goto err_master; + } + master_image->parent = tsi148_bridge; +spin_lock_init (&(master_image->lock)); + master_image->locked =0 ; + master_image->number = i; + master_image->address_attr =VME_A16 |VME_A24 |VME_A32 | +VME_A64 ; + master_image->cycle_attr =VME_SCT |VME_BLT |VME_MBLT | + VME_2eVME | VME_2eSST | VME_2eSSTB | VME_2eSST160 | + VME_2eSST267 | VME_2eSST320 |VME_SUPER |VME_USER | +VME_PROG |VME_DATA ; + master_image->width_attr =VME_D16 |VME_D32 ; +memset (&(master_image->pci_resource),0 , +sizeof (struct resource )); + master_image->kern_base =NULL ; +list_add_tail (&(master_image->list), + &(tsi148_bridge->master_resources)); + } + +/* Add slave windows to list */ +INIT_LIST_HEAD (&(tsi148_bridge->slave_resources)); +for (i =0 ; i <TSI148_MAX_SLAVE ; i++) { + slave_image = (struct vme_slave_resource *)kmalloc ( +sizeof (struct vme_slave_resource ),GFP_KERNEL ); +if (slave_image ==NULL ) { +dev_err (&pdev->dev,"Failed to allocate memory for " +"slave resource structure\n" ); + retval = -ENOMEM ; + goto err_slave; + } + slave_image->parent = tsi148_bridge; +init_MUTEX (&(slave_image->sem)); + slave_image->locked =0 ; + slave_image->number = i; + slave_image->address_attr =VME_A16 |VME_A24 |VME_A32 | +VME_A64 |VME_CRCSR |VME_USER1 |VME_USER2 | +VME_USER3 |VME_USER4 ; + slave_image->cycle_attr =VME_SCT |VME_BLT |VME_MBLT | + VME_2eVME | VME_2eSST | VME_2eSSTB | VME_2eSST160 | + VME_2eSST267 | VME_2eSST320 |VME_SUPER |VME_USER | +VME_PROG |VME_DATA ; +list_add_tail (&(slave_image->list), + &(tsi148_bridge->slave_resources)); + } + +/* Add dma engines to list */ +INIT_LIST_HEAD (&(tsi148_bridge->dma_resources)); +for (i =0 ; i <TSI148_MAX_DMA ; i++) { + dma_ctrlr = (struct vme_dma_resource *)kmalloc ( +sizeof (struct vme_dma_resource ),GFP_KERNEL ); +if (dma_ctrlr ==NULL ) { +dev_err (&pdev->dev,"Failed to allocate memory for " +"dma resource structure\n" ); + retval = -ENOMEM ; + goto err_dma; + } + dma_ctrlr->parent = tsi148_bridge; +init_MUTEX (&(dma_ctrlr->sem)); + dma_ctrlr->locked =0 ; + dma_ctrlr->number = i; +INIT_LIST_HEAD (&(dma_ctrlr->pending)); +INIT_LIST_HEAD (&(dma_ctrlr->running)); +list_add_tail (&(dma_ctrlr->list), + &(tsi148_bridge->dma_resources)); + } + + tsi148_bridge->slave_get = tsi148_slave_get; + tsi148_bridge->slave_set = tsi148_slave_set; + tsi148_bridge->master_get = tsi148_master_get; + tsi148_bridge->master_set = tsi148_master_set; + tsi148_bridge->master_read = tsi148_master_read; + tsi148_bridge->master_write = tsi148_master_write; + tsi148_bridge->master_rmw = tsi148_master_rmw; + tsi148_bridge->dma_list_add = tsi148_dma_list_add; + tsi148_bridge->dma_list_exec = tsi148_dma_list_exec; + tsi148_bridge->dma_list_empty = tsi148_dma_list_empty; + tsi148_bridge->request_irq = tsi148_request_irq; + tsi148_bridge->free_irq = tsi148_free_irq; + tsi148_bridge->generate_irq = tsi148_generate_irq; + tsi148_bridge->lm_set = tsi148_lm_set; + tsi148_bridge->lm_get = tsi148_lm_get; + tsi148_bridge->lm_attach = tsi148_lm_attach; + tsi148_bridge->lm_detach = tsi148_lm_detach; + tsi148_bridge->slot_get = tsi148_slot_get; + + data =ioread32be (tsi148_bridge->base +TSI148_LCSR_VSTAT ); +dev_info (&pdev->dev,"Board is%s the VME system controller\n" , + (data &TSI148_LCSR_VSTAT_SCONS )?"" :" not" ); +dev_info (&pdev->dev,"VME geographical address is %d\n" , + data &TSI148_LCSR_VSTAT_GA_M ); +dev_info (&pdev->dev,"VME Write and flush and error check is %s\n" , + err_chk ?"enabled" :"disabled" ); + +if (tsi148_crcsr_init (pdev)) { +dev_err (&pdev->dev,"CR/CSR configuration failed.\n" ); + goto err_crcsr; + + } + +/* Need to save tsi148_bridge pointer locally in link list for use in +* tsi148_remove() +*/ + retval =vme_register_bridge (tsi148_bridge); +if (retval !=0 ) { +dev_err (&pdev->dev,"Chip Registration failed.\n" ); + goto err_reg; + } + +/* Clear VME bus "board fail", and "power-up reset" lines */ + data =ioread32be (tsi148_bridge->base +TSI148_LCSR_VSTAT ); + data &= ~TSI148_LCSR_VSTAT_BRDFL ; + data |=TSI148_LCSR_VSTAT_CPURST ; +iowrite32be (data, tsi148_bridge->base +TSI148_LCSR_VSTAT ); + +return 0 ; + +vme_unregister_bridge (tsi148_bridge); +err_reg: +tsi148_crcsr_exit (pdev); +err_crcsr: +err_dma: +/* resources are stored in link list */ +list_for_each (pos, &(tsi148_bridge->dma_resources)) { + dma_ctrlr = list_entry(pos, struct vme_dma_resource, list); +list_del (pos); +kfree (dma_ctrlr); + } +err_slave: +/* resources are stored in link list */ +list_for_each (pos, &(tsi148_bridge->slave_resources)) { + slave_image = list_entry(pos, struct vme_slave_resource, list); +list_del (pos); +kfree (slave_image); + } +err_master: +/* resources are stored in link list */ +list_for_each (pos, &(tsi148_bridge->master_resources)) { + master_image = list_entry(pos, struct vme_master_resource, list); +list_del (pos); +kfree (master_image); + } + +tsi148_irq_exit (pdev); +err_irq: +err_test: +iounmap (tsi148_bridge->base); +err_remap: +pci_release_regions (pdev); +err_resource: +pci_disable_device (pdev); +err_enable: +kfree (tsi148_bridge); +err_struct: +return retval; + +} + +static void tsi148_remove (struct pci_dev *pdev) +{ +struct list_head *pos =NULL ; +struct vme_master_resource *master_image; +struct vme_slave_resource *slave_image; +struct vme_dma_resource *dma_ctrlr; +int i; + +dev_dbg (&pdev->dev,"Driver is being unloaded.\n" ); + +/* XXX We need to find the pdev->dev in the list of vme_bridge->dev's */ + +/* +* Shutdown all inbound and outbound windows. +*/ +for (i =0 ; i <8 ; i++) { +iowrite32be (0 , tsi148_bridge->base +TSI148_LCSR_IT [i] + +TSI148_LCSR_OFFSET_ITAT ); +iowrite32be (0 , tsi148_bridge->base +TSI148_LCSR_OT [i] + +TSI148_LCSR_OFFSET_OTAT ); + } + +/* +* Shutdown Location monitor. +*/ +iowrite32be (0 , tsi148_bridge->base +TSI148_LCSR_LMAT ); + +/* +* Shutdown CRG map. +*/ +iowrite32be (0 , tsi148_bridge->base +TSI148_LCSR_CSRAT ); + +/* +* Clear error status. +*/ +iowrite32be (0xFFFFFFFF , tsi148_bridge->base +TSI148_LCSR_EDPAT ); +iowrite32be (0xFFFFFFFF , tsi148_bridge->base +TSI148_LCSR_VEAT ); +iowrite32be (0x07000700 , tsi148_bridge->base +TSI148_LCSR_PSTAT ); + +/* +* Remove VIRQ interrupt (if any) +*/ +if (ioread32be (tsi148_bridge->base +TSI148_LCSR_VICR ) &0x800 ) { +iowrite32be (0x8000 , tsi148_bridge->base +TSI148_LCSR_VICR ); + } + +/* +* Disable and clear all interrupts. +*/ +iowrite32be (0x0 , tsi148_bridge->base +TSI148_LCSR_INTEO ); +iowrite32be (0xFFFFFFFF , tsi148_bridge->base +TSI148_LCSR_INTC ); +iowrite32be (0xFFFFFFFF , tsi148_bridge->base +TSI148_LCSR_INTEN ); + +/* +* Map all Interrupts to PCI INTA +*/ +iowrite32be (0x0 , tsi148_bridge->base +TSI148_LCSR_INTM1 ); +iowrite32be (0x0 , tsi148_bridge->base +TSI148_LCSR_INTM2 ); + +tsi148_irq_exit (pdev); + +vme_unregister_bridge (tsi148_bridge); + +tsi148_crcsr_exit (pdev); + +/* resources are stored in link list */ +list_for_each (pos, &(tsi148_bridge->dma_resources)) { + dma_ctrlr = list_entry(pos, struct vme_dma_resource, list); +list_del (pos); +kfree (dma_ctrlr); + } + +/* resources are stored in link list */ +list_for_each (pos, &(tsi148_bridge->slave_resources)) { + slave_image = list_entry(pos, struct vme_slave_resource, list); +list_del (pos); +kfree (slave_image); + } + +/* resources are stored in link list */ +list_for_each (pos, &(tsi148_bridge->master_resources)) { + master_image = list_entry(pos, struct vme_master_resource, list); +list_del (pos); +kfree (master_image); + } + +tsi148_irq_exit (pdev); + +iounmap (tsi148_bridge->base); + +pci_release_regions (pdev); + +pci_disable_device (pdev); + +kfree (tsi148_bridge); +} + +static void __exit tsi148_exit(void) +{ +pci_unregister_driver (&tsi148_driver); + +printk (KERN_DEBUG "Driver removed.\n" ); +} + +MODULE_PARM_DESC (err_chk,"Check for VME errors on reads and writes" ); +module_param (err_chk, bool,0 ); + +MODULE_DESCRIPTION ("VME driver for the Tundra Tempe VME bridge" ); +MODULE_LICENSE ("GPL" ); + +module_init (tsi148_init); +module_exit (tsi148_exit); + +/*---------------------------------------------------------------------------- +* STAGING +*--------------------------------------------------------------------------*/ + +#if 0 +/* +* Direct Mode DMA transfer +* +* XXX Not looking at direct mode for now, we can always use link list mode +* with a single entry. +*/ +int tsi148_dma_run (struct vme_dma_resource *resource,struct vme_dma_attr src, +struct vme_dma_attr dest,size_t count) +{ +u32 dctlreg =0 ; +unsigned int tmp; +int val; +int channel, x; +struct vmeDmaPacket *cur_dma; +struct tsi148_dma_descriptor *dmaLL; + +/* direct mode */ + dctlreg =0x800000 ; + +for (x =0 ; x <8 ; x++) {/* vme block size */ +if ((32 << x) >= vmeDma->maxVmeBlockSize) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<12 ); + +for (x =0 ; x <8 ; x++) {/* pci block size */ +if ((32 << x) >= vmeDma->maxPciBlockSize) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<4 ); + +if (vmeDma->vmeBackOffTimer) { +for (x =1 ; x <8 ; x++) {/* vme timer */ +if ((1 << (x -1 )) >= vmeDma->vmeBackOffTimer) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<8 ); + } + +if (vmeDma->pciBackOffTimer) { +for (x =1 ; x <8 ; x++) {/* pci timer */ +if ((1 << (x -1 )) >= vmeDma->pciBackOffTimer) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<0 ); + } + +/* Program registers for DMA transfer */ +iowrite32be (dmaLL->dsau, tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DSAU ); +iowrite32be (dmaLL->dsal, tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DSAL ); +iowrite32be (dmaLL->ddau, tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DDAU ); +iowrite32be (dmaLL->ddal, tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DDAL ); +iowrite32be (dmaLL->dsat, tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DSAT ); +iowrite32be (dmaLL->ddat, tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DDAT ); +iowrite32be (dmaLL->dcnt, tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DCNT ); +iowrite32be (dmaLL->ddbs, tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DDBS ); + +/* Start the operation */ +iowrite32be (dctlreg |0x2000000 , tsi148_bridge->base + +TSI148_LCSR_DMA [channel] +TSI148_LCSR_OFFSET_DCTL ); + + tmp =ioread32be (tsi148_bridge->base +TSI148_LCSR_DMA [channel] + +TSI148_LCSR_OFFSET_DSTA ); +wait_event_interruptible (dma_queue[channel], (tmp &0x1000000 ) ==0 ); + +/* +* Read status register, we should probably do this in some error +* handler rather than here so that we can be sure we haven't kicked off +* another DMA transfer. +*/ + val =ioread32be (tsi148_bridge->base +TSI148_LCSR_DMA [channel] + +TSI148_LCSR_OFFSET_DSTA ); + + vmeDma->vmeDmaStatus =0 ; +if (val &0x10000000 ) { +printk (KERN_ERR +"DMA Error in DMA_tempe_irqhandler DSTA=%08X\n" , + val); + vmeDma->vmeDmaStatus = val; + + } +return (0 ); +} +#endif + +#if 0 + +/* Global VME controller information */ +struct pci_dev *vme_pci_dev; + +/* +* Set the VME bus arbiter with the requested attributes +*/ +int tempe_set_arbiter (vmeArbiterCfg_t * vmeArb) +{ +int temp_ctl =0 ; +int gto =0 ; + + temp_ctl =ioread32be (tsi148_bridge->base +TSI148_LCSR_VCTRL ); + temp_ctl &=0xFFEFFF00 ; + +if (vmeArb->globalTimeoutTimer ==0xFFFFFFFF ) { + gto =8 ; + }else if (vmeArb->globalTimeoutTimer >2048 ) { +return (-EINVAL ); + }else if (vmeArb->globalTimeoutTimer ==0 ) { + gto =0 ; + }else { + gto =1 ; +while ((16 * (1 << (gto -1 ))) < vmeArb->globalTimeoutTimer) { + gto +=1 ; + } + } + temp_ctl |= gto; + +if (vmeArb->arbiterMode !=VME_PRIORITY_MODE ) { + temp_ctl |=1 <<6 ; + } + +if (vmeArb->arbiterTimeoutFlag) { + temp_ctl |=1 <<7 ; + } + +if (vmeArb->noEarlyReleaseFlag) { + temp_ctl |=1 <<20 ; + } +iowrite32be (temp_ctl, tsi148_bridge->base +TSI148_LCSR_VCTRL ); + +return (0 ); +} + +/* +* Return the attributes of the VME bus arbiter. +*/ +int tempe_get_arbiter (vmeArbiterCfg_t * vmeArb) +{ +int temp_ctl =0 ; +int gto =0 ; + + + temp_ctl =ioread32be (tsi148_bridge->base +TSI148_LCSR_VCTRL ); + + gto = temp_ctl &0xF ; +if (gto !=0 ) { + vmeArb->globalTimeoutTimer = (16 * (1 << (gto -1 ))); + } + +if (temp_ctl & (1 <<6 )) { + vmeArb->arbiterMode =VME_R_ROBIN_MODE ; + }else { + vmeArb->arbiterMode =VME_PRIORITY_MODE ; + } + +if (temp_ctl & (1 <<7 )) { + vmeArb->arbiterTimeoutFlag =1 ; + } + +if (temp_ctl & (1 <<20 )) { + vmeArb->noEarlyReleaseFlag =1 ; + } + +return (0 ); +} + +/* +* Set the VME bus requestor with the requested attributes +*/ +int tempe_set_requestor (vmeRequesterCfg_t * vmeReq) +{ +int temp_ctl =0 ; + + temp_ctl =ioread32be (tsi148_bridge->base +TSI148_LCSR_VMCTRL ); + temp_ctl &=0xFFFF0000 ; + +if (vmeReq->releaseMode ==1 ) { + temp_ctl |= (1 <<3 ); + } + +if (vmeReq->fairMode ==1 ) { + temp_ctl |= (1 <<2 ); + } + + temp_ctl |= (vmeReq->timeonTimeoutTimer &7 ) <<8 ; + temp_ctl |= (vmeReq->timeoffTimeoutTimer &7 ) <<12 ; + temp_ctl |= vmeReq->requestLevel; + +iowrite32be (temp_ctl, tsi148_bridge->base +TSI148_LCSR_VMCTRL ); +return (0 ); +} + +/* +* Return the attributes of the VME bus requestor +*/ +int tempe_get_requestor (vmeRequesterCfg_t * vmeReq) +{ +int temp_ctl =0 ; + + temp_ctl =ioread32be (tsi148_bridge->base +TSI148_LCSR_VMCTRL ); + +if (temp_ctl &0x18 ) { + vmeReq->releaseMode =1 ; + } + +if (temp_ctl & (1 <<2 )) { + vmeReq->fairMode =1 ; + } + + vmeReq->requestLevel = temp_ctl &3 ; + vmeReq->timeonTimeoutTimer = (temp_ctl >>8 ) &7 ; + vmeReq->timeoffTimeoutTimer = (temp_ctl >>12 ) &7 ; + +return (0 ); +} + + +#endif drivers/staging/vme/bridges/vme_tsi148.h new file +1387
-0@@ -0,0 +1,1387 @@ +/* +* tsi148.h +* +* Support for the Tundra TSI148 VME Bridge chip +* +* Author: Tom Armistead +* Updated and maintained by Ajit Prem +* Copyright 2004 Motorola Inc. +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +#ifndef TSI148_H +#define TSI148_H + +#ifndef PCI_VENDOR_ID_TUNDRA +#define PCI_VENDOR_ID_TUNDRA 0x10e3 +#endif + +#ifndef PCI_DEVICE_ID_TUNDRA_TSI148 +#define PCI_DEVICE_ID_TUNDRA_TSI148 0x148 +#endif + +/* + * Define the number of each that the Tsi148 supports. + */ +#define TSI148_MAX_MASTER 8 /* Max Master Windows */ +#define TSI148_MAX_SLAVE 8 /* Max Slave Windows */ +#define TSI148_MAX_DMA 2 /* Max DMA Controllers */ +#define TSI148_MAX_MAILBOX 4 /* Max Mail Box registers */ +#define TSI148_MAX_SEMAPHORE 8 /* Max Semaphores */ + +/* + * Layout of a DMAC Linked-List Descriptor + * + * Note: This structure is accessed via the chip and therefore must be + * correctly laid out - It must also be aligned on 64-bit boundaries. + */ +struct tsi148_dma_descriptor { + u32 dsau; /* Source Address */ + u32 dsal;Lines of this file are not shown. View the raw message.
The rest of this diff is not shown. View the raw message.
Part of the diff is not shown here. View the raw message.
Re: [patch 4/5] Staging: vme: add Tundra TSI148 VME-PCI Bridgedriver
Hi Emilio,
Sorry for not replying to this mail sooner - got caught in my junk folder for some reason.
Quoted 9 lines · Emilio G. Cota
Greg, Martyn,
please have a look at our tsi148 driver, available at:
http://repo.or.cz/w/tsi148vmebridge.git
Whenever you encounter a reference to 'CES', ignore it, it's a CERN thing for easing our lives porting drivers from LynxOS.
I've had a brief look - it seem's to provide roughtly what I am attempting to provide with the tsi-148 driver as part of the VME stuff in staging.
Quoted 8 lines
I could post the driver to LKML for review if you want; I would remove that CES crap before posting. I put this off because I knew it would never get merged before there was some generic vme glue supporting it. However now we're getting there so it's probably a good time to get cracking with this task.
Quoted 5 lines
Martyn, a few comments about your driver, haven't had much time to dig very deep.
- again, use mutexes instead of semaphores
Yes - I have a patch on the way.
Quoted 2 lines
- The DMA code looks rather messy; I mean stuff like this:
That's because it's incomplete - hence why it's in the staging tree.
Quoted 26 lines
Quoted 12 lines
drivers/staging/vme/bridges/vme_tsi148.c new file +11
-0+#if 0 +/* XXX Still todo */ +for (x =0 ; x <8 ; x++) {/* vme block size */ +if ((32 << x) >= vmeDma->maxVmeBlockSize) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<12 ); +drivers/staging/vme/bridges/vme_tsi148.c line 1900
Quoted 10 lines
drivers/staging/vme/bridges/vme_tsi148.c new file +10
-0+for (x =0 ; x <8 ; x++) {/* pci block size */ +if ((32 << x) >= vmeDma->maxPciBlockSize) { +break ; + } + } +if (x ==8 ) + x =7 ; + dctlreg |= (x <<4 ); + +if (vmeDma->vmeBackOffTimer) {and the ifdefs 0..
- correct me if I'm wrong, but does the slave need to know the window number he wants for his device? See:
Quoted 17 lines
drivers/staging/vme/bridges/vme_tsi148.c line 795
Quoted 12 lines
drivers/staging/vme/bridges/vme_tsi148.c new file +12
-0+int tsi148_slave_get (struct vme_slave_resource *image,int *enabled, +unsigned long long *vme_base,unsigned long long *size, +dma_addr_t *pci_base,vme_address_t *aspace,vme_cycle_t *cycle) +{ +unsigned int i, granularity =0 , ctl =0 ; +unsigned int vme_base_low, vme_base_high; +unsigned int vme_bound_low, vme_bound_high; +unsigned int pci_offset_low, pci_offset_high; +unsigned long long vme_bound, pci_offset; + + + i = image->number;If that's the case, this is totally broken; in fact, even if you kept a list somewhere up on the generic vme layer keeping track of the already used windows (images?) to avoid any clashes,
Yes - the core layer keeps track of what is in use.
Quoted 3 lines
in practice you'd very soon run out of windows: 8 modules, 8 mappings, 8 windows. And you're dead.
Depends how the drivers use them.
Quoted 5 lines
In a crate we have up to 20 modules, each of them with at least one address space to be mapped. To make this work we do the following:
Quoted 10 lines
* The tsi148 has 1GB of address space available for mapping, which is further divided into 8 windows, with a unique address modifier per window.
* Say you have 15 modules that need to map 1MB each, all of them with the same address modifier (AM).
Then what the slave driver requests is *not* a window, is what we call a 'vme mapping', which means "do whatever you want but give me a kernel address that points at this VME address, mapping with this size and AM".
With the API I have presented you could still map all 15 modules with one window in your driver if that makes sense.
Quoted 6 lines
Our tsi148 driver notices then that there's already a window for that AM, so it just appends the mapping to the existing window. So at the end of the day only one window is used for all those mappings, which are nicely piled together.
If you want this kind of control, I think it would work well as a layer above the basic resource management built into the API I have. The two could live fairly happily side-by-side.
Quoted 5 lines
Please clarify me how this is handled in your driver. The bottom line is to ensure that
a) Many different modules can peacefully co-exist on a crate
Drivers request resources, if the resources are available, they get them. Resources are requested with respect to the attributes required.
Quoted 2 lines
b) Drivers should know nothing about what else is on your crate
I agree.
Martyn
Quoted 4 lines
E.
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
PATCH 5/5 Staging: vme: add TODO file
In reply to Greg K-HFrom: Martyn Welch <[email protected]>
This describes the current vme api, along with a list of things that needs to be fixed up.
--- drivers/staging/vme/TODO | 388 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+)
drivers/staging/vme/TODO new file +388 -0
@@ -0,0 +1,388 @@ + VME Device Driver API + ===================== + +Driver registration +=================== + +As with other subsystems within the Linux kernel, VME device drivers register +with the VME subsystem, typically called from the devices init routine. This is +achieved via a call to the follwoing function: + + int vme_register_driver (struct vme_driver *driver); + +If driver registration is successful this function returns zero, if an error +occurred a negative error code will be returned. + +A pointer to a structure of type ???vme_driver??? must be provided to the +registration function. The structure is as follows: + + struct vme_driver { + struct list_head node; + char *name; + const struct vme_device_id *bind_table; + int (*probe) (struct device *, int, int); + int (*remove) (struct device *, int, int); + void (*shutdown) (void); + struct device_driver driver; + }; + +At the minimum, the ???.name???, ???.probe??? and ???.bind_table??? elements of this +structure should be correctly set. The ???.name??? element is a pointer to a string +holding the device driver???s name. The ???.probe??? element should contain a pointer +to the probe routine. + +The arguments of the probe routine are as follows: + + probe(struct device *dev, int bus, int slot); + +The ???.bind_table??? is a pointer to an array of type ???vme_device_id???: + + struct vme_device_id { + int bus; + int slot; + }; + +Each structure in this array should provide a bus and slot number where the core +should probe, using the driver???s probe routine, for a device on the specified +VME bus. + +The VME subsystem supports a single VME driver per ???slot???. There are considered +to be 32 slots per bus, one for each slot-ID as defined in the ANSI/VITA 1-1994 +specification and are analogious to the physical slots on the VME backplane. + +A function is also provided to unregister the driver from the VME core and is +usually called from the device driver???s exit routine: + + void vme_unregister_driver (struct vme_driver *driver); + + +Resource management +=================== + +Once a driver has registered with the VME core the provided probe routine will +be called for each of the bus/slot combination that becomes valid as VME buses +are themselves registered. The probe routine is passed a pointer to the devices +device structure. This pointer should be saved, it will be required for +requesting VME resources. + +The driver can request ownership of one or more master windows, slave windows +and/or dma channels. Rather than allowing the device driver to request a +specific window or DMA channel (which may be used by a different driver) this +driver allows a resource to be assigned based on the required attributes of the +driver in question: + + struct vme_resource * vme_master_request(struct device *dev, + vme_address_t aspace, vme_cycle_t cycle, vme_width_t width); + + struct vme_resource * vme_slave_request(struct device *dev, + vme_address_t aspace, vme_cycle_t cycle); + +TODO: DMA Resource Allocation incomplete. No attribute based selection. + + struct vme_resource *vme_request_dma(struct device *dev); + +For slave windows these attributes are split into those of type ???vme_address_t??? +and ???vme_cycle_t???. Master windows add a further set of attributes ???vme_cycle_t???. +These attributes are defined as bitmasks and as such any combination of the +attributes can be requested for a single window, the core will assign a window +that meets the requirements, returning a pointer of type vme_resource that +should be used to identify the allocated resource when it is used. If an +unallocated window fitting the requirements can not be found a NULL pointer will +be returned. + +Functions are also provided to free window allocations once they are no longer +required. These functions should be passed the pointer to the resource provided +during resource allocation: + + void vme_master_free(struct vme_resource *res); + + void vme_slave_free(struct vme_resource *res); + + void vme_dma_free(struct vme_resource *res); + + +Master windows +============== + +Master windows provide access from the local processor[s] out onto the VME bus. +The number of windows available and the available access modes is dependant on +the underlying chipset. A window must be configured before it can be used. + + +Master window configuration +--------------------------- + +Once a master window has been assigned the following functions can be used to +configure it and retrieve the current settings: + + int vme_master_set (struct vme_resource *res, int enabled, + unsigned long long base, unsigned long long size, + vme_address_t aspace, vme_cycle_t cycle, vme_width_t width); + + int vme_master_get (struct vme_resource *res, int *enabled, + unsigned long long *base, unsigned long long *size, + vme_address_t *aspace, vme_cycle_t *cycle, vme_width_t *width); + +The address spaces, transfer widths and cycle types are the same as described +under resource management, however some of the options are mutually exclusive. +For example, only one address space may be specified. + +These functions return 0 on success or an error code should the call fail. + + +Master window broadcast select mask +----------------------------------- + +TODO: Add functions to set and get Broadcast Select mask: + + int vme_master_bmsk_set (struct vme_resource *res, int mask); + int vme_master_bmsk_get (struct vme_resource *res, int *mask); + + +Master window access +-------------------- + +The following functions can be used to read from and write to configured master +windows. These functions return the number of bytes copied: + + ssize_t vme_master_read(struct vme_resource *res, void *buf, + size_t count, loff_t offset); + + ssize_t vme_master_write(struct vme_resource *res, void *buf, + size_t count, loff_t offset); + +In addition to simple reads and writes, a function is provided to do a +read-modify-write transaction. This function returns the original value of the +VME bus location : + + unsigned int vme_master_rmw (struct vme_resource *res, + unsigned int mask, unsigned int compare, unsigned int swap, + loff_t offset); + +This functions by reading the offset, applying the mask. If the bits selected in +the mask match with the values of the corresponding bits in the compare field, +the value of swap is written the specified offset. + + +Slave windows +============= + +Slave windows provide devices on the VME bus access into mapped portions of the +local memory. The number of windows available and the access modes that can be +used is dependant on the underlying chipset. A window must be configured before +it can be used. + + +Slave window configuration +-------------------------- + +Once a slave window has been assigned the following functions can be used to +configure it and retrieve the current settings: + + int vme_slave_set (struct vme_resource *res, int enabled, + unsigned long long base, unsigned long long size, + dma_addr_t mem, vme_address_t aspace, vme_cycle_t cycle); + + int vme_slave_get (struct vme_resource *res, int *enabled, + unsigned long long *base, unsigned long long *size, + dma_addr_t *mem, vme_address_t *aspace, vme_cycle_t *cycle); + +The address spaces, transfer widths and cycle types are the same as described +under resource management, however some of the options are mutually exclusive. +For example, only one address space may be specified. + +These functions return 0 on success or an error code should the call fail. + + +Slave window buffer allocation +------------------------------ + +Functions are provided to allow the user to allocate and free a contiguous +buffers which will be accessible by the VME bridge. These functions do not have +to be used, other methods can be used to allocate a buffer, though care must be +taken to ensure that they are contiguous and accessible by the VME bridge: + + void * vme_alloc_consistent(struct vme_resource *res, size_t size, + dma_addr_t *mem); + + void vme_free_consistent(struct vme_resource *res, size_t size, + void *virt, dma_addr_t mem); + + +Slave window access +------------------- + +Slave windows map local memory onto the VME bus, the standard methods for +accessing memory should be used. + + +DMA channels +============ + +The VME DMA transfer provides the ability to run link-list DMA transfers. The +API introduces the concept of DMA lists. Each DMA list is a link-list which can +be passed to a DMA controller. Multiple lists can be created, extended, +executed, reused and destroyed. + + +List Management +--------------- + +The following functions are provided to create and destroy DMA lists. Execution +of a list will not automatically destroy the list, thus enabling a list to be +reused for repetitive tasks: + + struct vme_dma_list *vme_new_dma_list(struct vme_resource *res); + + int vme_dma_list_free(struct vme_dma_list *list); + + +List Population +--------------- + +An item can be added to a list using the following function ( the source and +destination attributes need to be created before calling this function, this is +covered under "Transfer Attributes"): + + int vme_dma_list_add(struct vme_dma_list *list, + struct vme_dma_attr *src, struct vme_dma_attr *dest, + size_t count); + + +Transfer Attributes +------------------- + +The attributes for the source and destination are handled separately from adding +an item to a list. This is due to the diverse attributes required for each type +of source and destination. There are functions to create attributes for PCI, VME +and pattern sources and destinations (where appropriate): + +Pattern source: + + struct vme_dma_attr *vme_dma_pattern_attribute(u32 pattern, + vme_pattern_t type); + +PCI source or destination: + + struct vme_dma_attr *vme_dma_pci_attribute(dma_addr_t mem); + +VME source or destination: + + struct vme_dma_attr *vme_dma_vme_attribute(unsigned long long base, + vme_address_t aspace, vme_cycle_t cycle, vme_width_t width); + +The following function should be used to free an attribute: + + void vme_dma_free_attribute(struct vme_dma_attr *attr); + + +List Execution +-------------- + +The following function queues a list for execution. The function will return +once the list has been executed: + + int vme_dma_list_exec(struct vme_dma_list *list); + + +Interrupts +========== + +The VME API provides functions to attach and detach callbacks to specific VME +level and status ID combinations and for the generation of VME interrupts with +specific VME level and status IDs. + + +Attaching Interrupt Handlers +---------------------------- + +The following functions can be used to attach and free a specific VME level and +status ID combination. Any given combination can only be assigned a single +callback function. A void pointer parameter is provided, the value of which is +passed to the callback function, the use of this pointer is user undefined: + + int vme_request_irq(struct device *dev, int level, int statid, + void (*callback)(int, int, void *), void *priv); + + void vme_free_irq(struct device *dev, int level, int statid); + +The callback parameters are as follows. Care must be taken in writing a callback +function, callback functions run in interrupt context: + + void callback(int level, int statid, void *priv); + + +Interrupt Generation +-------------------- + +The following function can be used to generate a VME interrupt at a given VME +level and VME status ID: + + int vme_generate_irq(struct device *dev, int level, int statid); + + +Location monitors +================= + +The VME API provides the following functionality to configure the location +monitor. + + +Location Monitor Management +--------------------------- + +TODO: Provide a mechanism to request use of the location monitor. The location + monitors can be moved and we only want one driver to be able to do that + at a time! We also need to be able to free the location monitor for + others to use. + + struct vme_resource * vme_request_lm(struct device *dev); + + void vme_free_lm(struct vme_resource * res); + + +Location Monitor Configuration +------------------------------ + +TODO: Change to struct "vme_resource *res" rather than "struct device *dev". + +The following functions are provided to configure the location and mode of the +location monitor: + + int vme_lm_set(struct device *dev, unsigned long long base, + vme_address_t aspace, vme_cycle_t cycle); + + int vme_lm_get(struct device *dev, unsigned long long *base, + vme_address_t *aspace, vme_cycle_t *cycle); + + +Location Monitor Use +-------------------- + +TODO: Change to struct "vme_resource *res" rather than "struct device *dev". + +The following functions allow a callback to be attached and detached from each +location monitor location. The API currently supports 4 location monitors, +monitoring 4 adjacent locations: + + int vme_lm_attach(struct device *dev, int num, + void (*callback)(int)); + + int vme_lm_detach(struct device *dev, int num); + +The callback function is declared as follows. + + void callback(int num); + + +CR/CSR +====== + +TODO: The VME API needs functions to access the CR/CSR buffer. + +Slot Detection +============== + +This function returns the slot ID of the provided bridge. + + int vme_slot_get(struct device *dev);
Quoted 10 lines · Greg K-H
Hi all,
Here's 5 patches from Martyn Welch that adds VME Bus support to the kernel.
Martyn took the time and merged the three different existing implementations for Linux together into something that works, and got the proper legal agreements from all of the affected parties to release the code properly.
Just for the record - I realized that two were a forks of the vmelinux.org codebase. I picked the most developed fork, the original having not been ported to the 2.6.x tree. I have also used the other fork in the past, I don't believe there is much if anything that is worth taking from that.
Martyn
Signature · 4 lines
Martyn Welch MEng MPhil MIET (Principal Software Engineer) T:+44(0)1327322748 GE Fanuc Intelligent Platforms Ltd, |Registered in England and Wales Tove Valley Business Park, Towcester, |(3828642) at 100 Barbirolli Square, Northants, NN12 6PF, UK T:+44(0)1327359444 |Manchester,M2 3AB VAT:GB 927559189
Quoted 13 lines · Greg K-H
Hi all,
Here's 5 patches from Martyn Welch that adds VME Bus support to the kernel.
Martyn took the time and merged the three different existing implementations for Linux together into something that works, and got the proper legal agreements from all of the affected parties to release the code properly.
I'd like to thank him for taking the time to do this work, it is much appreciated.
Quoted 7 lines
These patches all add the code under the
drivers/staging/directory, as there is still a number of things that needs to be done to clean up the code to get it into "proper" mergable state, but it's a great first cut.Thanks again Martyn!
greg k-h
whoops just spotted this.. I had allocated next month for working on this, i.e. getting VME support into the kernel.
I work at CERN where we have hundreds of VME crates; we're currently moving from LynxOS to real-time linux. The VME chipset we're using is the tsi148, for which Sebastien Dugue wrote a driver at the beginning of the year.
I'll reply to each of the relevant patches to comment on them. Regards,
E.