diff options
| author | Andras Domokos <andras.domokos@nokia.com> | 2010-09-30 10:18:53 -0400 |
|---|---|---|
| committer | Carlos Chinea <carlos.chinea@nokia.com> | 2012-01-05 08:42:13 -0500 |
| commit | 4e69fc22753fcce1d9275b5517ef3646ffeffcf4 (patch) | |
| tree | c54254789bb016dafc0eaff708aa9fa356ad194b | |
| parent | a056ab8c7a00a0ffc52e9573bf01257004c2d08c (diff) | |
HSI: hsi_char: Add HSI char device driver
Add HSI char device driver to the kernel.
Signed-off-by: Andras Domokos <andras.domokos@nokia.com>
Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com>
| -rw-r--r-- | drivers/hsi/clients/hsi_char.c | 802 | ||||
| -rw-r--r-- | include/linux/hsi/hsi_char.h | 63 |
2 files changed, 865 insertions, 0 deletions
diff --git a/drivers/hsi/clients/hsi_char.c b/drivers/hsi/clients/hsi_char.c new file mode 100644 index 000000000000..88a050df2389 --- /dev/null +++ b/drivers/hsi/clients/hsi_char.c | |||
| @@ -0,0 +1,802 @@ | |||
| 1 | /* | ||
| 2 | * HSI character device driver, implements the character device | ||
| 3 | * interface. | ||
| 4 | * | ||
| 5 | * Copyright (C) 2010 Nokia Corporation. All rights reserved. | ||
| 6 | * | ||
| 7 | * Contact: Andras Domokos <andras.domokos@nokia.com> | ||
| 8 | * | ||
| 9 | * This program is free software; you can redistribute it and/or | ||
| 10 | * modify it under the terms of the GNU General Public License | ||
| 11 | * version 2 as published by the Free Software Foundation. | ||
| 12 | * | ||
| 13 | * This program is distributed in the hope that it will be useful, but | ||
| 14 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 16 | * General Public License for more details. | ||
| 17 | * | ||
| 18 | * You should have received a copy of the GNU General Public License | ||
| 19 | * along with this program; if not, write to the Free Software | ||
| 20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
| 21 | * 02110-1301 USA | ||
| 22 | */ | ||
| 23 | |||
| 24 | #include <linux/errno.h> | ||
| 25 | #include <linux/types.h> | ||
| 26 | #include <linux/atomic.h> | ||
| 27 | #include <linux/kernel.h> | ||
| 28 | #include <linux/init.h> | ||
| 29 | #include <linux/module.h> | ||
| 30 | #include <linux/mutex.h> | ||
| 31 | #include <linux/list.h> | ||
| 32 | #include <linux/slab.h> | ||
| 33 | #include <linux/kmemleak.h> | ||
| 34 | #include <linux/ioctl.h> | ||
| 35 | #include <linux/wait.h> | ||
| 36 | #include <linux/fs.h> | ||
| 37 | #include <linux/sched.h> | ||
| 38 | #include <linux/device.h> | ||
| 39 | #include <linux/cdev.h> | ||
| 40 | #include <linux/uaccess.h> | ||
| 41 | #include <linux/scatterlist.h> | ||
| 42 | #include <linux/stat.h> | ||
| 43 | #include <linux/hsi/hsi.h> | ||
| 44 | #include <linux/hsi/hsi_char.h> | ||
| 45 | |||
| 46 | #define HSC_DEVS 16 /* Num of channels */ | ||
| 47 | #define HSC_MSGS 4 | ||
| 48 | |||
| 49 | #define HSC_RXBREAK 0 | ||
| 50 | |||
| 51 | #define HSC_ID_BITS 6 | ||
| 52 | #define HSC_PORT_ID_BITS 4 | ||
| 53 | #define HSC_ID_MASK 3 | ||
| 54 | #define HSC_PORT_ID_MASK 3 | ||
| 55 | #define HSC_CH_MASK 0xf | ||
| 56 | |||
| 57 | /* | ||
| 58 | * We support up to 4 controllers that can have up to 4 | ||
| 59 | * ports, which should currently be more than enough. | ||
| 60 | */ | ||
| 61 | #define HSC_BASEMINOR(id, port_id) \ | ||
| 62 | ((((id) & HSC_ID_MASK) << HSC_ID_BITS) | \ | ||
| 63 | (((port_id) & HSC_PORT_ID_MASK) << HSC_PORT_ID_BITS)) | ||
| 64 | |||
| 65 | enum { | ||
| 66 | HSC_CH_OPEN, | ||
| 67 | HSC_CH_READ, | ||
| 68 | HSC_CH_WRITE, | ||
| 69 | HSC_CH_WLINE, | ||
| 70 | }; | ||
| 71 | |||
| 72 | enum { | ||
| 73 | HSC_RX, | ||
| 74 | HSC_TX, | ||
| 75 | }; | ||
| 76 | |||
| 77 | struct hsc_client_data; | ||
| 78 | /** | ||
| 79 | * struct hsc_channel - hsi_char internal channel data | ||
| 80 | * @ch: channel number | ||
| 81 | * @flags: Keeps state of the channel (open/close, reading, writing) | ||
| 82 | * @free_msgs_list: List of free HSI messages/requests | ||
| 83 | * @rx_msgs_queue: List of pending RX requests | ||
| 84 | * @tx_msgs_queue: List of pending TX requests | ||
| 85 | * @lock: Serialize access to the lists | ||
| 86 | * @cl: reference to the associated hsi_client | ||
| 87 | * @cl_data: reference to the client data that this channels belongs to | ||
| 88 | * @rx_wait: RX requests wait queue | ||
| 89 | * @tx_wait: TX requests wait queue | ||
| 90 | */ | ||
| 91 | struct hsc_channel { | ||
| 92 | unsigned int ch; | ||
| 93 | unsigned long flags; | ||
| 94 | struct list_head free_msgs_list; | ||
| 95 | struct list_head rx_msgs_queue; | ||
| 96 | struct list_head tx_msgs_queue; | ||
| 97 | spinlock_t lock; | ||
| 98 | struct hsi_client *cl; | ||
| 99 | struct hsc_client_data *cl_data; | ||
| 100 | wait_queue_head_t rx_wait; | ||
| 101 | wait_queue_head_t tx_wait; | ||
| 102 | }; | ||
| 103 | |||
| 104 | /** | ||
| 105 | * struct hsc_client_data - hsi_char internal client data | ||
| 106 | * @cdev: Characther device associated to the hsi_client | ||
| 107 | * @lock: Lock to serialize open/close access | ||
| 108 | * @flags: Keeps track of port state (rx hwbreak armed) | ||
| 109 | * @usecnt: Use count for claiming the HSI port (mutex protected) | ||
| 110 | * @cl: Referece to the HSI client | ||
| 111 | * @channels: Array of channels accessible by the client | ||
| 112 | */ | ||
| 113 | struct hsc_client_data { | ||
| 114 | struct cdev cdev; | ||
| 115 | struct mutex lock; | ||
| 116 | unsigned long flags; | ||
| 117 | unsigned int usecnt; | ||
| 118 | struct hsi_client *cl; | ||
| 119 | struct hsc_channel channels[HSC_DEVS]; | ||
| 120 | }; | ||
| 121 | |||
| 122 | /* Stores the major number dynamically allocated for hsi_char */ | ||
| 123 | static unsigned int hsc_major; | ||
| 124 | /* Maximum buffer size that hsi_char will accept from userspace */ | ||
| 125 | static unsigned int max_data_size = 0x1000; | ||
| 126 | module_param(max_data_size, uint, S_IRUSR | S_IWUSR); | ||
| 127 | MODULE_PARM_DESC(max_data_size, "max read/write data size [4,8..65536] (^2)"); | ||
| 128 | |||
| 129 | static void hsc_add_tail(struct hsc_channel *channel, struct hsi_msg *msg, | ||
| 130 | struct list_head *queue) | ||
| 131 | { | ||
| 132 | unsigned long flags; | ||
| 133 | |||
| 134 | spin_lock_irqsave(&channel->lock, flags); | ||
| 135 | list_add_tail(&msg->link, queue); | ||
| 136 | spin_unlock_irqrestore(&channel->lock, flags); | ||
| 137 | } | ||
| 138 | |||
| 139 | static struct hsi_msg *hsc_get_first_msg(struct hsc_channel *channel, | ||
| 140 | struct list_head *queue) | ||
| 141 | { | ||
| 142 | struct hsi_msg *msg = NULL; | ||
| 143 | unsigned long flags; | ||
| 144 | |||
| 145 | spin_lock_irqsave(&channel->lock, flags); | ||
| 146 | |||
| 147 | if (list_empty(queue)) | ||
| 148 | goto out; | ||
| 149 | |||
| 150 | msg = list_first_entry(queue, struct hsi_msg, link); | ||
| 151 | list_del(&msg->link); | ||
| 152 | out: | ||
| 153 | spin_unlock_irqrestore(&channel->lock, flags); | ||
| 154 | |||
| 155 | return msg; | ||
| 156 | } | ||
| 157 | |||
| 158 | static inline void hsc_msg_free(struct hsi_msg *msg) | ||
| 159 | { | ||
| 160 | kfree(sg_virt(msg->sgt.sgl)); | ||
| 161 | hsi_free_msg(msg); | ||
| 162 | } | ||
| 163 | |||
| 164 | static void hsc_free_list(struct list_head *list) | ||
| 165 | { | ||
| 166 | struct hsi_msg *msg, *tmp; | ||
| 167 | |||
| 168 | list_for_each_entry_safe(msg, tmp, list, link) { | ||
| 169 | list_del(&msg->link); | ||
| 170 | hsc_msg_free(msg); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | static void hsc_reset_list(struct hsc_channel *channel, struct list_head *l) | ||
| 175 | { | ||
| 176 | unsigned long flags; | ||
| 177 | LIST_HEAD(list); | ||
| 178 | |||
| 179 | spin_lock_irqsave(&channel->lock, flags); | ||
| 180 | list_splice_init(l, &list); | ||
| 181 | spin_unlock_irqrestore(&channel->lock, flags); | ||
| 182 | |||
| 183 | hsc_free_list(&list); | ||
| 184 | } | ||
| 185 | |||
| 186 | static inline struct hsi_msg *hsc_msg_alloc(unsigned int alloc_size) | ||
| 187 | { | ||
| 188 | struct hsi_msg *msg; | ||
| 189 | void *buf; | ||
| 190 | |||
| 191 | msg = hsi_alloc_msg(1, GFP_KERNEL); | ||
| 192 | if (!msg) | ||
| 193 | goto out; | ||
| 194 | buf = kmalloc(alloc_size, GFP_KERNEL); | ||
| 195 | if (!buf) { | ||
| 196 | hsi_free_msg(msg); | ||
