From 7808edcd306f22aeb23775d34e70b7fa2f58b852 Mon Sep 17 00:00:00 2001 From: Nicos Gollan Date: Thu, 5 May 2011 21:00:37 +0200 Subject: Basic support for Moschip 9900 family I/O chips Add I/O based support for serial and parallel ports of the following chips: Vendor: Moschip (0x9710) Parts (device IDs) * 9900 (0x9900) * 9904 (0x9904 * 9901 (0x9912, also sold as 9912) * 9922 (0x9922) On all chips but the 9900, a single port is provided per PCI subdevice (subvendor-ID 0xA000, subdevice-IDs 0x1000 for serial, 0x2000 for parallel with proper class codes). In cascading configurations, the 9900 provides two devices per subdevice, with subvendor-ID 0xA000 and subdevice-IDs 0x30ps where p is the number of parallel ports and s the number of serial ports. Basic testing was only done on the serial part of a 9912 to the point where it can be used for a serial kernel console, and advanced features are completely untested. It is possible to reduce functionality of the chips by adding a configuration EEPROM, and the datasheet [1] is inconsistent w.r.t subdevices in the 4s+2s1p and 2s1p+4s configurations. The subdevice-ID 0x3012 should likely read 0x3011 with a serial port in function 3, which would be consistent with the BAR layouts. For now, the drivers ignore subdevices with ID 0x1000 and no class code. The parallel ports are integrated in parport_serial even for purely parallel parts to reduce the footprint of the patch. [1] http://www.moschip.com/data/products/MCS9900/MCS9900_Datasheet.pdf Signed-off-by: Nicos Gollan Signed-off-by: Greg Kroah-Hartman --- include/linux/pci_ids.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'include') diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index a311008af5e1..034ebb4451c2 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2821,7 +2821,11 @@ #define PCI_DEVICE_ID_NETMOS_9845 0x9845 #define PCI_DEVICE_ID_NETMOS_9855 0x9855 #define PCI_DEVICE_ID_NETMOS_9865 0x9865 +#define PCI_DEVICE_ID_NETMOS_9900 0x9900 #define PCI_DEVICE_ID_NETMOS_9901 0x9901 +#define PCI_DEVICE_ID_NETMOS_9904 0x9904 +#define PCI_DEVICE_ID_NETMOS_9912 0x9912 +#define PCI_DEVICE_ID_NETMOS_9922 0x9922 #define PCI_VENDOR_ID_3COM_2 0xa727 -- cgit v1.2.3 From ae92c1f5e7b6708371365d262625ac19e67c1e79 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 24 May 2011 10:43:03 +0200 Subject: TTY: export NR_LDISC and N_* line discipline numbers to user-space Since commit (4564f9e5: consolidate line discipline number definitions) the patch moved all line discipline number from a per-architecture termios.h to a shared one: tty.h. However, prior to this consolidation work, the line discipline numbers were outside of an ifdef __KERNEL__/endif block so these numbers used to be exported to user-space. Since such numbers are kernel ABI anyway, and tty.h is already included for user- space header processing, just move these relevant defines outside of the ifdef __KERNEL__/endif block in include/linux/tty.h. CC: Maxime Bizon Signed-off-by: Florian Fainelli Acked-by: Tilman Schmidt Signed-off-by: Greg Kroah-Hartman --- include/linux/tty.h | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/include/linux/tty.h b/include/linux/tty.h index d6f05292e456..44bc0c5617e1 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -5,24 +5,6 @@ * 'tty.h' defines some structures used by tty_io.c and some defines. */ -#ifdef __KERNEL__ -#include -#include -#include -#include -#include -#include -#include - -#include - - -/* - * (Note: the *_driver.minor_start values 1, 64, 128, 192 are - * hardcoded at present.) - */ -#define NR_UNIX98_PTY_DEFAULT 4096 /* Default maximum for Unix98 ptys */ -#define NR_UNIX98_PTY_MAX (1 << MINORBITS) /* Absolute limit */ #define NR_LDISCS 30 /* line disciplines */ @@ -53,6 +35,25 @@ #define N_TRACESINK 23 /* Trace data routing for MIPI P1149.7 */ #define N_TRACEROUTER 24 /* Trace data routing for MIPI P1149.7 */ +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +#include + +#include + + +/* + * (Note: the *_driver.minor_start values 1, 64, 128, 192 are + * hardcoded at present.) + */ +#define NR_UNIX98_PTY_DEFAULT 4096 /* Default maximum for Unix98 ptys */ +#define NR_UNIX98_PTY_MAX (1 << MINORBITS) /* Absolute limit */ + /* * This character is the same as _POSIX_VDISABLE: it cannot be used as * a c_cc[] character, but indicates that a particular special character -- cgit v1.2.3 From bcd5abe28f40cc6a935d3339cde27976f6be3f1a Mon Sep 17 00:00:00 2001 From: Russ Gorby Date: Thu, 16 Jun 2011 14:20:12 -0700 Subject: tty: n_gsm: Add raw-ip support This patch adds the ability to open a network data connection over a mux virtual tty channel. This is for modems that support data connections with raw IP frames instead of PPP. On high speed data connections this eliminates a significant amount of PPP overhead. To use this interface, the application must first tell the modem to open a network connection on a virtual tty. Once that has been accomplished, the app will issue an IOCTL on that virtual tty to create the network interface. The IOCTL will return the index of the interface created. The two IOCTL commands are: ioctl( fd, GSMIOC_ENABLE_NET ); ioctl( fd, GSMIOC_DISABLE_NET ); Signed-off-by: Russ Gorby Acked-by: Alan Cox Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_gsm.c | 279 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/gsmmux.h | 11 ++ 2 files changed, 281 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index b288ff6cb812..9a13e510daea 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -58,6 +58,10 @@ #include #include #include +#include +#include +#include +#include #include static int debug; @@ -77,8 +81,24 @@ module_param(debug, int, 0600); * Semi-arbitrary buffer size limits. 0710 is normally run with 32-64 byte * limits so this is plenty */ -#define MAX_MRU 512 -#define MAX_MTU 512 +#define MAX_MRU 1500 +#define MAX_MTU 1500 +#define GSM_NET_TX_TIMEOUT (HZ*10) + +/** + * struct gsm_mux_net - network interface + * @struct gsm_dlci* dlci + * @struct net_device_stats stats; + * + * Created when net interface is initialized. + **/ +struct gsm_mux_net { + struct kref ref; + struct gsm_dlci *dlci; + struct net_device_stats stats; +}; + +#define STATS(net) (((struct gsm_mux_net *)netdev_priv(net))->stats) /* * Each block of data we have queued to go out is in the form of @@ -113,6 +133,7 @@ struct gsm_dlci { #define DLCI_OPENING 1 /* Sending SABM not seen UA */ #define DLCI_OPEN 2 /* SABM/UA complete */ #define DLCI_CLOSING 3 /* Sending DISC not seen UA/DM */ + struct mutex mutex; /* Link layer */ spinlock_t lock; /* Protects the internal state */ @@ -123,6 +144,7 @@ struct gsm_dlci { struct kfifo *fifo; /* Queue fifo for the DLCI */ struct kfifo _fifo; /* For new fifo API porting only */ int adaption; /* Adaption layer in use */ + int prev_adaption; u32 modem_rx; /* Our incoming virtual modem lines */ u32 modem_tx; /* Our outgoing modem lines */ int dead; /* Refuse re-open */ @@ -134,6 +156,8 @@ struct gsm_dlci { struct sk_buff_head skb_list; /* Queued frames */ /* Data handling callback */ void (*data)(struct gsm_dlci *dlci, u8 *data, int len); + void (*prev_data)(struct gsm_dlci *dlci, u8 *data, int len); + struct net_device *net; /* network interface, if created */ }; /* DLCI 0, 62/63 are special or reseved see gsmtty_open */ @@ -880,8 +904,10 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm, } memcpy(dp, skb_pull(dlci->skb, len), len); __gsm_data_queue(dlci, msg); - if (last) + if (last) { + kfree_skb(dlci->skb); dlci->skb = NULL; + } return size; } @@ -914,7 +940,7 @@ static void gsm_dlci_data_sweep(struct gsm_mux *gsm) i++; continue; } - if (dlci->adaption < 3) + if (dlci->adaption < 3 && !dlci->net) len = gsm_dlci_data_output(gsm, dlci); else len = gsm_dlci_data_output_framed(gsm, dlci); @@ -941,9 +967,12 @@ static void gsm_dlci_data_kick(struct gsm_dlci *dlci) spin_lock_irqsave(&dlci->gsm->tx_lock, flags); /* If we have nothing running then we need to fire up */ - if (dlci->gsm->tx_bytes == 0) - gsm_dlci_data_output(dlci->gsm, dlci); - else if (dlci->gsm->tx_bytes < TX_THRESH_LO) + if (dlci->gsm->tx_bytes == 0) { + if (dlci->net) + gsm_dlci_data_output_framed(dlci->gsm, dlci); + else + gsm_dlci_data_output(dlci->gsm, dlci); + } else if (dlci->gsm->tx_bytes < TX_THRESH_LO) gsm_dlci_data_sweep(dlci->gsm); spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags); } @@ -1577,6 +1606,7 @@ static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr) if (dlci == NULL) return NULL; spin_lock_init(&dlci->lock); + mutex_init(&dlci->mutex); dlci->fifo = &dlci->_fifo; if (kfifo_alloc(&dlci->_fifo, 4096, GFP_KERNEL) < 0) { kfree(dlci); @@ -2059,7 +2089,6 @@ struct gsm_mux *gsm_alloc_mux(void) gsm->t2 = T2; gsm->n2 = N2; gsm->ftype = UIH; - gsm->initiator = 0; gsm->adaption = 1; gsm->encoding = 1; gsm->mru = 64; /* Default to encoding 1 so these should be 64 */ @@ -2478,6 +2507,210 @@ static int gsmld_ioctl(struct tty_struct *tty, struct file *file, } } +/* + * Network interface + * + */ + +static int gsm_mux_net_open(struct net_device *net) +{ + pr_debug("%s called\n", __func__); + netif_start_queue(net); + return 0; +} + +static int gsm_mux_net_close(struct net_device *net) +{ + netif_stop_queue(net); + return 0; +} + +static struct net_device_stats *gsm_mux_net_get_stats(struct net_device *net) +{ + return &((struct gsm_mux_net *)netdev_priv(net))->stats; +} +static void dlci_net_free(struct gsm_dlci *dlci) +{ + if (!dlci->net) { + WARN_ON(1); + return; + } + dlci->adaption = dlci->prev_adaption; + dlci->data = dlci->prev_data; + free_netdev(dlci->net); + dlci->net = NULL; +} +static void net_free(struct kref *ref) +{ + struct gsm_mux_net *mux_net; + struct gsm_dlci *dlci; + + mux_net = container_of(ref, struct gsm_mux_net, ref); + dlci = mux_net->dlci; + + if (dlci->net) { + unregister_netdev(dlci->net); + dlci_net_free(dlci); + } +} + +static int gsm_mux_net_start_xmit(struct sk_buff *skb, + struct net_device *net) +{ + struct gsm_mux_net *mux_net = (struct gsm_mux_net *)netdev_priv(net); + struct gsm_dlci *dlci = mux_net->dlci; + kref_get(&mux_net->ref); + + skb_queue_head(&dlci->skb_list, skb); + STATS(net).tx_packets++; + STATS(net).tx_bytes += skb->len; + gsm_dlci_data_kick(dlci); + /* And tell the kernel when the last transmit started. */ + net->trans_start = jiffies; + kref_put(&mux_net->ref, net_free); + return NETDEV_TX_OK; +} + +/* called when a packet did not ack after watchdogtimeout */ +static void gsm_mux_net_tx_timeout(struct net_device *net) +{ + /* Tell syslog we are hosed. */ + dev_dbg(&net->dev, "Tx timed out.\n"); + + /* Update statistics */ + STATS(net).tx_errors++; +} + +static void gsm_mux_rx_netchar(struct gsm_dlci *dlci, + unsigned char *in_buf, int size) +{ + struct net_device *net = dlci->net; + struct sk_buff *skb; + struct gsm_mux_net *mux_net = (struct gsm_mux_net *)netdev_priv(net); + kref_get(&mux_net->ref); + + /* Allocate an sk_buff */ + skb = dev_alloc_skb(size + NET_IP_ALIGN); + if (!skb) { + /* We got no receive buffer. */ + STATS(net).rx_dropped++; + kref_put(&mux_net->ref, net_free); + return; + } + skb_reserve(skb, NET_IP_ALIGN); + memcpy(skb_put(skb, size), in_buf, size); + + skb->dev = net; + skb->protocol = __constant_htons(ETH_P_IP); + + /* Ship it off to the kernel */ + netif_rx(skb); + + /* update out statistics */ + STATS(net).rx_packets++; + STATS(net).rx_bytes += size; + kref_put(&mux_net->ref, net_free); + return; +} + +int gsm_change_mtu(struct net_device *net, int new_mtu) +{ + struct gsm_mux_net *mux_net = (struct gsm_mux_net *)netdev_priv(net); + if ((new_mtu < 8) || (new_mtu > mux_net->dlci->gsm->mtu)) + return -EINVAL; + net->mtu = new_mtu; + return 0; +} + +static void gsm_mux_net_init(struct net_device *net) +{ + static const struct net_device_ops gsm_netdev_ops = { + .ndo_open = gsm_mux_net_open, + .ndo_stop = gsm_mux_net_close, + .ndo_start_xmit = gsm_mux_net_start_xmit, + .ndo_tx_timeout = gsm_mux_net_tx_timeout, + .ndo_get_stats = gsm_mux_net_get_stats, + .ndo_change_mtu = gsm_change_mtu, + }; + + net->netdev_ops = &gsm_netdev_ops; + + /* fill in the other fields */ + net->watchdog_timeo = GSM_NET_TX_TIMEOUT; + net->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + net->type = ARPHRD_NONE; + net->tx_queue_len = 10; +} + + +/* caller holds the dlci mutex */ +static void gsm_destroy_network(struct gsm_dlci *dlci) +{ + struct gsm_mux_net *mux_net; + + pr_debug("destroy network interface"); + if (!dlci->net) + return; + mux_net = (struct gsm_mux_net *)netdev_priv(dlci->net); + kref_put(&mux_net->ref, net_free); +} + + +/* caller holds the dlci mutex */ +static int gsm_create_network(struct gsm_dlci *dlci, struct gsm_netconfig *nc) +{ + char *netname; + int retval = 0; + struct net_device *net; + struct gsm_mux_net *mux_net; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + /* Already in a non tty mode */ + if (dlci->adaption > 2) + return -EBUSY; + + if (nc->protocol != htons(ETH_P_IP)) + return -EPROTONOSUPPORT; + + if (nc->adaption != 3 && nc->adaption != 4) + return -EPROTONOSUPPORT; + + pr_debug("create network interface"); + + netname = "gsm%d"; + if (nc->if_name[0] != '\0') + netname = nc->if_name; + net = alloc_netdev(sizeof(struct gsm_mux_net), + netname, + gsm_mux_net_init); + if (!net) { + pr_err("alloc_netdev failed"); + return -ENOMEM; + } + net->mtu = dlci->gsm->mtu; + mux_net = (struct gsm_mux_net *)netdev_priv(net); + mux_net->dlci = dlci; + kref_init(&mux_net->ref); + strncpy(nc->if_name, net->name, IFNAMSIZ); /* return net name */ + + /* reconfigure dlci for network */ + dlci->prev_adaption = dlci->adaption; + dlci->prev_data = dlci->data; + dlci->adaption = nc->adaption; + dlci->data = gsm_mux_rx_netchar; + dlci->net = net; + + pr_debug("register netdev"); + retval = register_netdev(net); + if (retval) { + pr_err("network register fail %d\n", retval); + dlci_net_free(dlci); + return retval; + } + return net->ifindex; /* return network index */ +} /* Line discipline for real tty */ struct tty_ldisc_ops tty_ldisc_packet = { @@ -2598,6 +2831,9 @@ static void gsmtty_close(struct tty_struct *tty, struct file *filp) struct gsm_dlci *dlci = tty->driver_data; if (dlci == NULL) return; + mutex_lock(&dlci->mutex); + gsm_destroy_network(dlci); + mutex_unlock(&dlci->mutex); if (tty_port_close_start(&dlci->port, tty, filp) == 0) return; gsm_dlci_begin_close(dlci); @@ -2679,7 +2915,32 @@ static int gsmtty_tiocmset(struct tty_struct *tty, static int gsmtty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { - return -ENOIOCTLCMD; + struct gsm_dlci *dlci = tty->driver_data; + struct gsm_netconfig nc; + int index; + + switch (cmd) { + case GSMIOC_ENABLE_NET: + if (copy_from_user(&nc, (void __user *)arg, sizeof(nc))) + return -EFAULT; + nc.if_name[IFNAMSIZ-1] = '\0'; + /* return net interface index or error code */ + mutex_lock(&dlci->mutex); + index = gsm_create_network(dlci, &nc); + mutex_unlock(&dlci->mutex); + if (copy_to_user((void __user *)arg, &nc, sizeof(nc))) + return -EFAULT; + return index; + case GSMIOC_DISABLE_NET: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + mutex_lock(&dlci->mutex); + gsm_destroy_network(dlci); + mutex_unlock(&dlci->mutex); + return 0; + default: + return -ENOIOCTLCMD; + } } static void gsmtty_set_termios(struct tty_struct *tty, struct ktermios *old) diff --git a/include/linux/gsmmux.h b/include/linux/gsmmux.h index 378de4195caf..c25e9477f7c3 100644 --- a/include/linux/gsmmux.h +++ b/include/linux/gsmmux.h @@ -21,5 +21,16 @@ struct gsm_config #define GSMIOC_GETCONF _IOR('G', 0, struct gsm_config) #define GSMIOC_SETCONF _IOW('G', 1, struct gsm_config) +struct gsm_netconfig { + unsigned int adaption; /* Adaption to use in network mode */ + unsigned short protocol;/* Protocol to use - only ETH_P_IP supported */ + unsigned short unused2; + char if_name[IFNAMSIZ]; /* interface name format string */ + __u8 unused[28]; /* For future use */ +}; + +#define GSMIOC_ENABLE_NET _IOW('G', 2, struct gsm_netconfig) +#define GSMIOC_DISABLE_NET _IO('G', 3) + #endif -- cgit v1.2.3 From 8168e9c2de7383c497aff9cd906dd475f9b42b2c Mon Sep 17 00:00:00 2001 From: J Freyensee Date: Fri, 17 Jun 2011 15:09:53 -0700 Subject: PTI feature to allow user to name and mark masterchannel request. This feature addition provides a new parameter in pti_request_masterchannel() to allow the user to provide their own name to mark the request when the trace is viewed in a PTI SW trace viewer (like MPTA). If a name is not provided and NULL is provided, the 'current' process name is used. API function header documentation documents this. Signed-off-by: Jeremy Rocher Signed-off-by: J Freyensee Signed-off-by: Greg Kroah-Hartman --- drivers/misc/pti.c | 99 ++++++++++++++++++++++++++++++++--------------------- include/linux/pti.h | 3 +- 2 files changed, 62 insertions(+), 40 deletions(-) (limited to 'include') diff --git a/drivers/misc/pti.c b/drivers/misc/pti.c index bb6f9255c17c..96a25e3b4847 100644 --- a/drivers/misc/pti.c +++ b/drivers/misc/pti.c @@ -146,45 +146,54 @@ static void pti_write_to_aperture(struct pti_masterchannel *mc, /** * pti_control_frame_built_and_sent()- control frame build and send function. * - * @mc: The master / channel structure on which the function - * built a control frame. + * @mc: The master / channel structure on which the function + * built a control frame. + * @thread_name: The thread name associated with the master / channel or + * 'NULL' if using the 'current' global variable. * * To be able to post process the PTI contents on host side, a control frame * is added before sending any PTI content. So the host side knows on * each PTI frame the name of the thread using a dedicated master / channel. - * The thread name is retrieved from the 'current' global variable. + * The thread name is retrieved from 'current' global variable if 'thread_name' + * is 'NULL', else it is retrieved from 'thread_name' parameter. * This function builds this frame and sends it to a master ID CONTROL_ID. * The overhead is only 32 bytes since the driver only writes to HW * in 32 byte chunks. */ - -static void pti_control_frame_built_and_sent(struct pti_masterchannel *mc) +static void pti_control_frame_built_and_sent(struct pti_masterchannel *mc, + const char *thread_name) { struct pti_masterchannel mccontrol = {.master = CONTROL_ID, .channel = 0}; + const char *thread_name_p; const char *control_format = "%3d %3d %s"; u8 control_frame[CONTROL_FRAME_LEN]; - /* - * Since we access the comm member in current's task_struct, - * we only need to be as large as what 'comm' in that - * structure is. - */ - char comm[TASK_COMM_LEN]; + if (!thread_name) { + /* + * Since we access the comm member in current's task_struct, + * we only need to be as large as what 'comm' in that + * structure is. + */ + char comm[TASK_COMM_LEN]; - if (!in_interrupt()) - get_task_comm(comm, current); - else - strncpy(comm, "Interrupt", TASK_COMM_LEN); + if (!in_interrupt()) + get_task_comm(comm, current); + else + strncpy(comm, "Interrupt", TASK_COMM_LEN); - /* Absolutely ensure our buffer is zero terminated. */ - comm[TASK_COMM_LEN-1] = 0; + /* Absolutely ensure our buffer is zero terminated. */ + comm[TASK_COMM_LEN-1] = 0; + thread_name_p = comm; + } else { + thread_name_p = thread_name; + } mccontrol.channel = pti_control_channel; pti_control_channel = (pti_control_channel + 1) & 0x7f; snprintf(control_frame, CONTROL_FRAME_LEN, control_format, mc->master, - mc->channel, comm); + mc->channel, thread_name_p); pti_write_to_aperture(&mccontrol, control_frame, strlen(control_frame)); } @@ -206,18 +215,20 @@ static void pti_write_full_frame_to_aperture(struct pti_masterchannel *mc, const unsigned char *buf, int len) { - pti_control_frame_built_and_sent(mc); + pti_control_frame_built_and_sent(mc, NULL); pti_write_to_aperture(mc, (u8 *)buf, len); } /** * get_id()- Allocate a master and channel ID. * - * @id_array: an array of bits representing what channel - * id's are allocated for writing. - * @max_ids: The max amount of available write IDs to use. - * @base_id: The starting SW channel ID, based on the Intel - * PTI arch. + * @id_array: an array of bits representing what channel + * id's are allocated for writing. + * @max_ids: The max amount of available write IDs to use. + * @base_id: The starting SW channel ID, based on the Intel + * PTI arch. + * @thread_name: The thread name associated with the master / channel or + * 'NULL' if using the 'current' global variable. * * Returns: * pti_masterchannel struct with master, channel ID address @@ -227,7 +238,10 @@ static void pti_write_full_frame_to_aperture(struct pti_masterchannel *mc, * channel id. The bit is one if the id is taken and 0 if free. For * every master there are 128 channel id's. */ -static struct pti_masterchannel *get_id(u8 *id_array, int max_ids, int base_id) +static struct pti_masterchannel *get_id(u8 *id_array, + int max_ids, + int base_id, + const char *thread_name) { struct pti_masterchannel *mc; int i, j, mask; @@ -257,7 +271,7 @@ static struct pti_masterchannel *get_id(u8 *id_array, int max_ids, int base_id) mc->master = base_id; mc->channel = ((i & 0xf)<<3) + j; /* write new master Id / channel Id allocation to channel control */ - pti_control_frame_built_and_sent(mc); + pti_control_frame_built_and_sent(mc, thread_name); return mc; } @@ -273,18 +287,22 @@ static struct pti_masterchannel *get_id(u8 *id_array, int max_ids, int base_id) * a master, channel ID address * to write to PTI HW. * - * @type: 0- request Application master, channel aperture ID write address. - * 1- request OS master, channel aperture ID write - * address. - * 2- request Modem master, channel aperture ID - * write address. - * Other values, error. + * @type: 0- request Application master, channel aperture ID + * write address. + * 1- request OS master, channel aperture ID write + * address. + * 2- request Modem master, channel aperture ID + * write address. + * Other values, error. + * @thread_name: The thread name associated with the master / channel or + * 'NULL' if using the 'current' global variable. * * Returns: * pti_masterchannel struct * 0 for error */ -struct pti_masterchannel *pti_request_masterchannel(u8 type) +struct pti_masterchannel *pti_request_masterchannel(u8 type, + const char *thread_name) { struct pti_masterchannel *mc; @@ -293,15 +311,18 @@ struct pti_masterchannel *pti_request_masterchannel(u8 type) switch (type) { case 0: - mc = get_id(drv_data->ia_app, MAX_APP_IDS, APP_BASE_ID); + mc = get_id(drv_data->ia_app, MAX_APP_IDS, + APP_BASE_ID, thread_name); break; case 1: - mc = get_id(drv_data->ia_os, MAX_OS_IDS, OS_BASE_ID); + mc = get_id(drv_data->ia_os, MAX_OS_IDS, + OS_BASE_ID, thread_name); break; case 2: - mc = get_id(drv_data->ia_modem, MAX_MODEM_IDS, MODEM_BASE_ID); + mc = get_id(drv_data->ia_modem, MAX_MODEM_IDS, + MODEM_BASE_ID, thread_name); break; default: mc = NULL; @@ -471,9 +492,9 @@ static int pti_tty_install(struct tty_driver *driver, struct tty_struct *tty) return -ENOMEM; if (idx == PTITTY_MINOR_START) - pti_tty_data->mc = pti_request_masterchannel(0); + pti_tty_data->mc = pti_request_masterchannel(0, NULL); else - pti_tty_data->mc = pti_request_masterchannel(2); + pti_tty_data->mc = pti_request_masterchannel(2, NULL); if (pti_tty_data->mc == NULL) return -ENXIO; @@ -560,7 +581,7 @@ static int pti_char_open(struct inode *inode, struct file *filp) * before assigning the value to filp->private_data. * Slightly easier to debug if this driver needs debugging. */ - mc = pti_request_masterchannel(0); + mc = pti_request_masterchannel(0, NULL); if (mc == NULL) return -ENOMEM; filp->private_data = mc; diff --git a/include/linux/pti.h b/include/linux/pti.h index 81af667bb2d5..b3ea01a3197e 100644 --- a/include/linux/pti.h +++ b/include/linux/pti.h @@ -36,7 +36,8 @@ struct pti_masterchannel { /* the following functions are defined in misc/pti.c */ void pti_writedata(struct pti_masterchannel *mc, u8 *buf, int count); -struct pti_masterchannel *pti_request_masterchannel(u8 type); +struct pti_masterchannel *pti_request_masterchannel(u8 type, + const char *thread_name); void pti_release_masterchannel(struct pti_masterchannel *mc); #endif /*PTI_H_*/ -- cgit v1.2.3 From def90f4239f094f3846c108c1c41a4cd55c33e8e Mon Sep 17 00:00:00 2001 From: Shreshtha Kumar Sahu Date: Thu, 9 Jun 2011 22:56:32 +0200 Subject: amba pl011: workaround for uart registers lockup This workaround aims to break the deadlock situation which raises during continuous transfer of data for long duration over uart with hardware flow control. It is observed that CTS interrupt cannot be cleared in uart interrupt register (ICR). Hence further transfer over uart gets blocked. It is seen that during such deadlock condition ICR don't get cleared even on multiple write. This leads pass_counter to decrease and finally reach zero. This can be taken as trigger point to run this UART_BT_WA. Workaround backups the register configuration, does soft reset of UART using BIT-0 of PRCC_K_SOFTRST_SET/CLEAR registers and restores the registers. This patch also provides support for uart init and exit function calls if present. Signed-off-by: Shreshtha Kumar Sahu Signed-off-by: Linus Walleij Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 123 +++++++++++++++++++++++++++++++++++++++- include/linux/amba/serial.h | 3 + 2 files changed, 125 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 8dc0541feecc..f5f6831b0a64 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -50,6 +50,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,30 @@ #define UART_DR_ERROR (UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE) #define UART_DUMMY_DR_RX (1 << 16) + +#define UART_WA_SAVE_NR 14 + +static void pl011_lockup_wa(unsigned long data); +static const u32 uart_wa_reg[UART_WA_SAVE_NR] = { + ST_UART011_DMAWM, + ST_UART011_TIMEOUT, + ST_UART011_LCRH_RX, + UART011_IBRD, + UART011_FBRD, + ST_UART011_LCRH_TX, + UART011_IFLS, + ST_UART011_XFCR, + ST_UART011_XON1, + ST_UART011_XON2, + ST_UART011_XOFF1, + ST_UART011_XOFF2, + UART011_CR, + UART011_IMSC +}; + +static u32 uart_wa_regdata[UART_WA_SAVE_NR]; +static DECLARE_TASKLET(pl011_lockup_tlet, pl011_lockup_wa, 0); + /* There is by now at least one vendor with differing details, so handle it */ struct vendor_data { unsigned int ifls; @@ -72,6 +97,7 @@ struct vendor_data { unsigned int lcrh_tx; unsigned int lcrh_rx; bool oversampling; + bool interrupt_may_hang; /* vendor-specific */ bool dma_threshold; }; @@ -90,9 +116,12 @@ static struct vendor_data vendor_st = { .lcrh_tx = ST_UART011_LCRH_TX, .lcrh_rx = ST_UART011_LCRH_RX, .oversampling = true, + .interrupt_may_hang = true, .dma_threshold = true, }; +static struct uart_amba_port *amba_ports[UART_NR]; + /* Deals with DMA transactions */ struct pl011_sgbuf { @@ -132,6 +161,7 @@ struct uart_amba_port { unsigned int lcrh_rx; /* vendor-specific */ bool autorts; char type[12]; + bool interrupt_may_hang; /* vendor-specific */ #ifdef CONFIG_DMA_ENGINE /* DMA stuff */ bool using_tx_dma; @@ -1008,6 +1038,68 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap) #endif +/* + * pl011_lockup_wa + * This workaround aims to break the deadlock situation + * when after long transfer over uart in hardware flow + * control, uart interrupt registers cannot be cleared. + * Hence uart transfer gets blocked. + * + * It is seen that during such deadlock condition ICR + * don't get cleared even on multiple write. This leads + * pass_counter to decrease and finally reach zero. This + * can be taken as trigger point to run this UART_BT_WA. + * + */ +static void pl011_lockup_wa(unsigned long data) +{ + struct uart_amba_port *uap = amba_ports[0]; + void __iomem *base = uap->port.membase; + struct circ_buf *xmit = &uap->port.state->xmit; + struct tty_struct *tty = uap->port.state->port.tty; + int buf_empty_retries = 200; + int loop; + + /* Stop HCI layer from submitting data for tx */ + tty->hw_stopped = 1; + while (!uart_circ_empty(xmit)) { + if (buf_empty_retries-- == 0) + break; + udelay(100); + } + + /* Backup registers */ + for (loop = 0; loop < UART_WA_SAVE_NR; loop++) + uart_wa_regdata[loop] = readl(base + uart_wa_reg[loop]); + + /* Disable UART so that FIFO data is flushed out */ + writew(0x00, uap->port.membase + UART011_CR); + + /* Soft reset UART module */ + if (uap->port.dev->platform_data) { + struct amba_pl011_data *plat; + + plat = uap->port.dev->platform_data; + if (plat->reset) + plat->reset(); + } + + /* Restore registers */ + for (loop = 0; loop < UART_WA_SAVE_NR; loop++) + writew(uart_wa_regdata[loop] , + uap->port.membase + uart_wa_reg[loop]); + + /* Initialise the old status of the modem signals */ + uap->old_status = readw(uap->port.membase + UART01x_FR) & + UART01x_FR_MODEM_ANY; + + if (readl(base + UART011_MIS) & 0x2) + printk(KERN_EMERG "UART_BT_WA: ***FAILED***\n"); + + /* Start Tx/Rx */ + tty->hw_stopped = 0; +} + static void pl011_stop_tx(struct uart_port *port) { struct uart_amba_port *uap = (struct uart_amba_port *)port; @@ -1158,8 +1250,11 @@ static irqreturn_t pl011_int(int irq, void *dev_id) if (status & UART011_TXIS) pl011_tx_chars(uap); - if (pass_counter-- == 0) + if (pass_counter-- == 0) { + if (uap->interrupt_may_hang) + tasklet_schedule(&pl011_lockup_tlet); break; + } status = readw(uap->port.membase + UART011_MIS); } while (status != 0); @@ -1339,6 +1434,14 @@ static int pl011_startup(struct uart_port *port) writew(uap->im, uap->port.membase + UART011_IMSC); spin_unlock_irq(&uap->port.lock); + if (uap->port.dev->platform_data) { + struct amba_pl011_data *plat; + + plat = uap->port.dev->platform_data; + if (plat->init) + plat->init(); + } + return 0; clk_dis: @@ -1394,6 +1497,15 @@ static void pl011_shutdown(struct uart_port *port) * Shut down the clock producer */ clk_disable(uap->clk); + + if (uap->port.dev->platform_data) { + struct amba_pl011_data *plat; + + plat = uap->port.dev->platform_data; + if (plat->exit) + plat->exit(); + } + } static void @@ -1700,6 +1812,14 @@ static int __init pl011_console_setup(struct console *co, char *options) if (!uap) return -ENODEV; + if (uap->port.dev->platform_data) { + struct amba_pl011_data *plat; + + plat = uap->port.dev->platform_data; + if (plat->init) + plat->init(); + } + uap->port.uartclk = clk_get_rate(uap->clk); if (options) @@ -1774,6 +1894,7 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) uap->lcrh_rx = vendor->lcrh_rx; uap->lcrh_tx = vendor->lcrh_tx; uap->fifosize = vendor->fifosize; + uap->interrupt_may_hang = vendor->interrupt_may_hang; uap->port.dev = &dev->dev; uap->port.mapbase = dev->res.start; uap->port.membase = base; diff --git a/include/linux/amba/serial.h b/include/linux/amba/serial.h index 5479fdc849e9..514ed45c462e 100644 --- a/include/linux/amba/serial.h +++ b/include/linux/amba/serial.h @@ -201,6 +201,9 @@ struct amba_pl011_data { bool (*dma_filter)(struct dma_chan *chan, void *filter_param); void *dma_rx_param; void *dma_tx_param; + void (*init) (void); + void (*exit) (void); + void (*reset) (void); }; #endif -- cgit v1.2.3