upload tizen1.0 source
[kernel/linux-2.6.36.git] / drivers / usb / gadget / serial_acm.c
1 /*
2  * serial_acm.c -- USB modem serial driver
3  *
4  * Copyright 2008 (C) Samsung Electronics
5  *
6  */
7
8 #include <linux/init.h>
9 #include <linux/module.h>
10 #include <linux/kernel.h>
11 #include <linux/fs.h>
12 #include <linux/errno.h>
13 #include <linux/types.h>
14 #include <linux/fcntl.h>
15 #include <linux/device.h>
16 #include <linux/miscdevice.h>
17 #include <linux/mutex.h>
18
19 #include <asm/uaccess.h>
20 #include <asm/io.h>
21
22 #include <linux/interrupt.h>
23 #include <linux/sched.h>
24 #include <linux/wait.h>
25 #include <linux/poll.h>
26 #include <linux/usb/cdc.h>
27 #include <linux/slab.h>
28
29 /*
30  * Changes
31  *
32  * Yongsul Oh <yongsul96.oh> 2011.05.13
33  * Added mutex lock/unlock mechanism  & usb connection state check routine
34  *  to improve state-control-mechanism & stability
35  */
36
37 extern void acm_notify(void * dev, u16 state);
38
39 static struct task_struct *acm_dbg_task;
40 static atomic_t link_state;
41
42 struct serial_acm_dev {
43         unsigned int read_state;
44         unsigned int control_line_state;
45         unsigned char busy;
46
47         void *acm_data;
48         wait_queue_head_t modem_wait_q;
49
50         spinlock_t link_lock;
51 };
52
53 static struct serial_acm_dev *s_acm_dev;
54
55 static int modem_open(struct inode *inode, struct file *file)
56 {
57         if ( s_acm_dev->busy ) {
58                 printk(KERN_DEBUG "ACM_dun is already opened by %s(%d),  tried %s(%d)\n", 
59                         acm_dbg_task ? acm_dbg_task->comm  : "NULL",
60                         acm_dbg_task ? acm_dbg_task->pid : 0,
61                         current->comm, current->pid);           
62                 return -EBUSY;
63         }
64
65         if ( !atomic_read(&link_state) ) {
66                 printk(KERN_DEBUG "Cannot open ACM_dun(not connected)\n");
67                 return -ENODEV;
68         }
69
70         s_acm_dev->busy = 1;
71         s_acm_dev->read_state = 0;
72         acm_dbg_task = current;
73         
74         printk(KERN_DEBUG "Opened ACM_dun by processor: %s(%d), and real_parent: %s(%d)\n",
75                          current->comm , current->pid,  
76                          current->real_parent->comm , current->real_parent->pid);
77
78         return 0;
79 }
80
81 static int modem_close(struct inode *inode, struct file *file)
82 {
83         printk(KERN_DEBUG "Closed ACM_dun from processor: %s(%d), but opend by: %s(%d)\n",
84                         current->comm , current->pid,  
85                         acm_dbg_task ? acm_dbg_task->comm  : "NULL",
86                         acm_dbg_task ? acm_dbg_task->pid : 0 );
87
88         acm_dbg_task = NULL;
89         s_acm_dev->busy = 0;
90         
91         return 0;
92 }
93
94 static ssize_t modem_read(struct file *file, char __user *buf,
95                 size_t count, loff_t *ppos)
96 {
97         int ret = 0;
98
99         if (file->f_flags & O_NONBLOCK)
100                 return -EPERM;
101
102         ret = wait_event_interruptible(s_acm_dev->modem_wait_q,
103                                         (s_acm_dev->read_state || (!atomic_read(&link_state))));
104         if (ret)
105                 return ret;
106
107         if ( !atomic_read(&link_state) ) {
108                 printk(KERN_DEBUG "Cannot read ACM_dun(not connected)\n");
109                 return -ENODEV;
110         }
111
112         if (copy_to_user(buf, &s_acm_dev->control_line_state, sizeof(u32)))
113                 return -EFAULT;
114
115         s_acm_dev->read_state = 0;
116
117         return sizeof(u32);
118 }
119
120 static unsigned int modem_poll(struct file *file, poll_table *wait)
121 {
122         poll_wait(file, &s_acm_dev->modem_wait_q, wait);
123
124         return ( (s_acm_dev->read_state ? (POLLIN | POLLRDNORM) : 0) |
125                          (!atomic_read(&link_state) ? (POLLERR | POLLHUP) : 0) );
126 }
127
128 void notify_control_line_state(u32 value)
129 {
130         if ( s_acm_dev == NULL ) {
131                 printk(KERN_ERR "ACM_dun notify control failed. s_acm_dev is null.\n");
132                 return ;
133         }
134
135         s_acm_dev->control_line_state = value;
136
137         s_acm_dev->read_state = 1;
138
139         wake_up_interruptible(&s_acm_dev->modem_wait_q);
140 }
141 EXPORT_SYMBOL(notify_control_line_state);
142
143 #define GS_CDC_NOTIFY_SERIAL_STATE      _IOW('S', 1, int)
144 #define GS_IOC_NOTIFY_DTR_TEST          _IOW('S', 3, int)
145
146 static long modem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
147 {
148         printk(KERN_DEBUG "ACM_dun modem_ioctl: cmd=0x%x, arg=%lu\n", cmd, arg);
149
150         if ( !atomic_read(&link_state) ) {
151                 printk(KERN_DEBUG "Cannot ioctl ACM_dun(not connected)\n");
152                 return -ENODEV;
153         }
154
155         /* handle ioctls */
156         switch (cmd) {
157         case GS_CDC_NOTIFY_SERIAL_STATE:        
158                 acm_notify(s_acm_dev->acm_data, __constant_cpu_to_le16(arg));
159                 break;
160
161         case GS_IOC_NOTIFY_DTR_TEST:
162                 printk(KERN_ALERT"DUN : DTR %d\n",(int)arg);
163                 notify_control_line_state((int)arg);
164                 break;
165
166         default:
167                 printk(KERN_ERR "ACM_dun modem_ioctl: Unknown ioctl cmd(0x%x).\n", cmd);
168                 return -ENOIOCTLCMD;
169         }
170         return 0;
171 }
172
173 static struct file_operations modem_fops = {
174         .owner          = THIS_MODULE,
175         .open           = modem_open,
176         .release        = modem_close,
177         .read           = modem_read,
178         .poll           = modem_poll,
179         .llseek         = no_llseek,
180         .unlocked_ioctl         = modem_ioctl,
181 };
182
183 static struct miscdevice modem_device = {
184         .minor  = 123,
185         .name   = "dun",
186         .fops   = &modem_fops,
187 };
188
189 int modem_acm_connect(void * data)
190 {
191         if ( (data == NULL) || (s_acm_dev == NULL) ) {
192                 printk(KERN_ERR "ACM_dun connect failed. data or s_acm_dev is null.\n");
193                 return -1;
194         }
195
196         spin_lock(&s_acm_dev->link_lock);
197
198         s_acm_dev->acm_data = data;
199         atomic_set(&link_state, 1);
200
201         spin_unlock(&s_acm_dev->link_lock);
202
203         printk(KERN_INFO "ACM_dun is connected\n");
204
205         return 0;
206 }
207 EXPORT_SYMBOL(modem_acm_connect);
208
209 void modem_acm_disconnect(void)
210 {
211         if ( s_acm_dev == NULL ) {
212                 printk(KERN_ERR "ACM_dun disconnect failed. s_acm_dev is null.\n");
213                 return ;
214         }
215
216         spin_lock(&s_acm_dev->link_lock);
217
218         atomic_set(&link_state, 0);
219         s_acm_dev->acm_data = NULL;
220         wake_up(&s_acm_dev->modem_wait_q);
221
222         spin_unlock(&s_acm_dev->link_lock);
223
224         printk(KERN_INFO "ACM_dun is disconnected\n");
225 }
226 EXPORT_SYMBOL(modem_acm_disconnect);
227
228 static int __init init(void)
229 {
230         struct serial_acm_dev *dev;
231         int ret = 0;
232
233         dev = kzalloc(sizeof(*dev), GFP_KERNEL);
234         if (!dev)
235                 return -ENOMEM;
236
237         ret = misc_register(&modem_device);
238         if (ret) {
239                 printk(KERN_ERR "[serial_acm] misc_register failed\n");
240                 kfree(dev);
241                 goto err_reg;
242         }
243
244         spin_lock_init(&dev->link_lock);
245         init_waitqueue_head(&dev->modem_wait_q);
246         atomic_set(&link_state, 0);
247
248         acm_dbg_task = NULL;
249
250         s_acm_dev = dev;
251 err_reg:
252         return ret;
253 }
254 module_init(init);
255
256 static void __exit cleanup(void)
257 {
258         struct serial_acm_dev *dev = s_acm_dev;
259
260         misc_deregister(&modem_device);
261
262         atomic_set(&link_state, 0);
263         dev->acm_data = NULL;
264
265         wake_up(&s_acm_dev->modem_wait_q);
266         kfree(s_acm_dev);       
267 }
268 module_exit(cleanup);
269