/*************************************************************************** * The contents of this file are subject to the NOKOS License Version 1.0a * (the "License"); you may not use this file except in compliance with the * License. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Software is Nokia D211 Software for Linux. * * Copyright (c) Nokia 2002 and others. All Rights Reserved. * * Nokia is a registered trademark of Nokia Corporation. * Other product and company names mentioned herein may be * trademarks or tradenames of their respective owners. * * Contributor(s): ______________________________________. * **************************************************************************/ /*************************************************************************** * * Misc tool functions not fitting to any other category * **************************************************************************/ #include #include "nokia_info.h" #include "nokia_priv.h" #define __KERNEL_SYSCALLS__ #include #include #include #include #include /* This is shared by all module instances */ static int errno; static dt_memitem_t *dt_memroot; #ifdef D_DEBUG static INT32 allocspaces = 0; #endif /* Static functions' declarations */ static int d_procfile_read (char *buffer , char **buffer_location , off_t offset , int buffer_length ); // Gets an I16 value from pointer // Saves us from some type conversions stuff // at the code. INT16 dt_get16(void *q) { INT16 *z = q; return *z; } /* Simple buffer space */ void *dt_getspace(UINT32 size) { static UINT8 bspc[4][1024]; static int bc = 0; dt_memitem_t *mi; if(size<1000) { if(++bc>=4) bc=0; return (&bspc[bc][0]); } //DEBUG("Allocating space!\n"); #ifdef D_DEBUG // Keep checking for memory leaks if(++allocspaces > 20) { DEBUG("WARNING! OVER 20 DT_SPACES ALLOCATED. MEMORY LEAK PROBABLE!\n"); } #endif mi = kmalloc(sizeof(dt_memitem_t),GFP_ATOMIC); size+=16; mi->data = kmalloc(size,GFP_ATOMIC); mi->size = size; mi->next = dt_memroot; dt_memroot = mi; return mi->data; } void dt_freespace(void *p) { dt_memitem_t *mi,*mp; if(!dt_memroot) return; // DEBUG("Freeing space\n"); #ifdef D_DEBUG --allocspaces; #endif if(dt_memroot->data==p) { mi=dt_memroot; dt_memroot=mi->next; dfree(mi->data,mi->size); dfree(mi,sizeof(dt_memitem_t)); return; } for(mi=dt_memroot->next,mp=dt_memroot;mi;mi=mi->next,mp=mp->next) { if(mi->data==p) { mp->next=mi->next; dfree(mi->data,mi->size); dfree(mi,sizeof(dt_memitem_t)); } } } /* Buffer handling */ dt_buf_t *dt_newbuf(UINT32 maxsize) { dt_buf_t *buf; buf = (dt_buf_t *)dt_getspace(sizeof(dt_buf_t)); memset(buf,0,sizeof(dt_buf_t)); buf->data = dt_getspace(maxsize); buf->maxsize=maxsize; return buf; } void dt_freebuf(dt_buf_t *buf) { if(!buf) return; if(buf->data) dt_freespace(buf->data); dt_freespace((UINT8 *)buf); } UINT8 *dt_bufdata(dt_buf_t *buf) { return buf->data; } INT16 dt_buflen(dt_buf_t *buf) { return buf->length; } void dt_addbuf8(dt_buf_t *buf,UINT8 x) { if(buf->length+1>=buf->maxsize) { DEBUG("Buffer overflow.\n"); return; } buf->data[buf->length++]=x; } void dt_addbuf16(dt_buf_t *buf,UINT16 x) { if(buf->length+2>=buf->maxsize) { DEBUG("Buffer overflow.\n"); return; } buf->data[buf->length++]=(UINT8)(x); buf->data[buf->length++]=(UINT8)(x>>8); } void dt_addbufn(dt_buf_t *buf,UINT8 *d,INT16 len) { INT16 i; for(i=0;idata[buf->length++]=d[i]; if(buf->length>=buf->maxsize) { DEBUG("addbufn. Buffer overflow.\n"); break; } } } /* Parses tag from string */ char *dt_parse_tag(char *s,char *tag, char *tmp) { char *z; int i; // DEBUG("Parse tag:%s:(1stchar=%d)\n",tag,(INT16)s[0]); z = s; for(;;) { for(;;) { z = strstr(z,tag); if(!z) return NULL; // Argh.. If the s is like: // use_ssid=xxxx // ssid=xxxx // This would only find the use_ssid with ssid tag... // Which should of cource not happen. Therefore, we check // The prev chracter. If its \n or space or something it is // ok. Otherwise continue strstr... if(z==s) break; // Don't segfault if(z[-1]<33) break; z=z+1; // Don't find the same again } z=z+strlen(tag); while(*z==' ') z=z+1; if(*z++!='=') continue; for(i=0;*z!='\n'&&*z!='\0';i++) { tmp[i]=*z++; } tmp[i]='\0'; return(tmp); } } /* convert a string of hexvalue to a UINT8 */ UINT8 dt_hexstrtochar(char *str) { UINT8 x,y; int n = strlen(str); if(n<1) return 0; x = str[0]; if(x>='0' && x<='9') x = x - '0'; if(x>='a' && x<='f') x = x - 'a' + 10; if(x>='A' && x<='F') x = x - 'A' + 10; if(n<2) { if(x>15) return 0; return x; } y = str[1]; if(y>='0' && y<='9') y = y - '0'; if(y>='a' && y<='f') y = y - 'a' + 10; if(y>='A' && y<='F') y = y - 'A' + 10; return x*16+y; } int dt_readhex(char *out,char *data,int len) { INT16 i,q,n; char tmp[8]; for(n=0,q=0,i=0;nfunction = function; timer_list->data = (UINT32)data; init_timer(timer_list); return (void*)timer_list; } // Mod timer int dt_mod_timer(void* timer_list, unsigned long expires) { if (!timer_list){ ERROR ("timer_list NULL\n"); return 0; } return mod_timer((struct timer_list *) timer_list, expires); } // Del Timer int dt_del_timer(void* timer_list) { if (!timer_list){ ERROR ("timer_list NULL\n"); return 0; } return del_timer((struct timer_list *) timer_list); } // Free Timer int dt_free_timer(void* timer_list) { int ret = 0; if (!timer_list){ ERROR ("timer_list NULL\n"); return 0; } ret = del_timer((struct timer_list *) timer_list); kfree (timer_list); return ret; } void* dt_init_lock(void){ spinlock_t *lock; lock = kmalloc(sizeof(spinlock_t), GFP_ATOMIC); if (!lock){ ERROR ("lock NULL\n"); return NULL; } spin_lock_init(lock); return (void*)lock; } void dt_lock (void* lock){ if (!lock){ ERROR ("lock NULL\n"); return; } spin_lock((spinlock_t*) lock); } int dt_is_locked (void* lock){ if (!lock){ ERROR ("lock NULL\n"); return 0; } return spin_is_locked((spinlock_t*) lock); } void dt_unlock (void* lock){ if (!lock){ ERROR ("lock NULL\n"); return; } spin_unlock((spinlock_t*) lock); } void dt_lock_bh (void* lock){ if (!lock){ ERROR ("lock NULL\n"); return; } spin_lock_bh((spinlock_t*) lock); } void dt_unlock_bh (void* lock){ if (!lock){ ERROR ("lock NULL\n"); return; } spin_unlock_bh((spinlock_t*) lock); } void dt_lock_irqsave (void* lock, unsigned long *flags){ if (!lock){ ERROR ("lock NULL\n"); return; } local_irq_save(*flags); spin_lock((spinlock_t*) lock); } void dt_unlock_irqrestore (void* lock, unsigned long *flags){ if (!lock){ ERROR ("lock NULL\n"); return; } spin_unlock((spinlock_t*) lock); local_irq_restore(*flags); } void dt_del_lock (void* lock){ if (!lock){ ERROR ("lock NULL\n"); return; } kfree (lock); } void* dt_init_waitqueue (){ wait_queue_head_t *queue; queue = kmalloc(sizeof(wait_queue_head_t), GFP_ATOMIC); if (!queue){ ERROR ("queue NULL\n"); return NULL; } init_waitqueue_head (queue); return (void*)queue; } INT32 dt_interruptible_sleep_on_timeout(void* queue, long timeout){ if (!queue){ ERROR ("queue NULL\n"); return 0; } return interruptible_sleep_on_timeout((wait_queue_head_t*) queue, timeout); } int dt_interruptible_sleep_on_state_timeout(void* queue, int* statevar, long timeout){ int ret; wait_queue_t wq; wait_queue_head_t* wh = (wait_queue_head_t*) queue; if (!wh || !statevar) return -1; init_waitqueue_entry(&wq, current); add_wait_queue(wh, &wq); set_current_state(TASK_INTERRUPTIBLE); if (!statevar) ret = 0; else if (signal_pending(current)) ret = -1; else { ret = 0; timeout = schedule_timeout(timeout); if (!timeout) ret = -1; } set_current_state(TASK_RUNNING); remove_wait_queue(wh, &wq); return ret; } void dt_wake_up_interruptible(void* queue){ if (!queue){ ERROR ("queue NULL\n"); return; } return wake_up_interruptible((wait_queue_head_t*) queue); } void dt_delete_waitqueue(void* queue){ if (!queue){ ERROR ("queue NULL\n"); return; } kfree (queue); return; } /* Initialize tasklet */ void* dt_init_tasklet (void (*function)(UINT32), unsigned long data){ struct tasklet_struct *tasklet; tasklet = kmalloc(sizeof(struct tasklet_struct), GFP_ATOMIC); if (!tasklet){ ERROR ("tasklet NULL\n"); return NULL; } tasklet_init(tasklet, function, data); return (void*)tasklet; } /* Schedule tasklet */ void dt_schedule_tasklet(void* tasklet) { if (!tasklet){ ERROR ("tasklet NULL\n"); return; } return tasklet_schedule((struct tasklet_struct*) tasklet); } /* Enable tasklet */ void dt_enable_tasklet(void* tasklet) { if (!tasklet){ ERROR ("tasklet NULL\n"); return; } return tasklet_enable((struct tasklet_struct*) tasklet); } /* Disable tasklet */ void dt_disable_tasklet(void* tasklet) { if (!tasklet){ ERROR ("tasklet NULL\n"); return; } return tasklet_disable((struct tasklet_struct*) tasklet); } // Kill Tasklet void dt_kill_tasklet(void* tasklet) { if (!tasklet){ ERROR ("tasklet NULL\n"); return; } tasklet_kill((struct tasklet_struct*) tasklet); kfree (tasklet); return; } INT dt_in_interrupt (void){ return in_interrupt(); } INT dt_in_irq (void){ return in_irq(); } UINT8 *dt_printmac(UINT8 *src, UINT8 *buf) { if (!src || !buf){ ERROR ("parameters NULL\n"); return NULL; } sprintf(&buf[0],"%.02x:%.02x:%.02x:%.02x:%.02x:%.02x",src[0],src[1],src[2],src[3],src[4],src[5]); return(&buf[0]); } UINT8 *dt_printip(UINT32 src, UINT8 *buf) { UINT8 *q; if (!buf){ ERROR ("parameters NULL\n"); return NULL; } q = (UINT8*)&src; sprintf(&buf[0],"%.03d.%.03d.%.03d.%.03d",q[0],q[1],q[2],q[3]); return(&buf[0]); } void dt_printhex(UINT8 *src,INT16 len) { INT16 i; for(i=0;i 131072) { ERROR("Invalid file length: '%s'.\n",filename); sys_close(fd); set_fs(fs); return NULL; } /* Before 2.6.8 * lseek(fd, 0L, 0); */ vfs_llseek(filep, 0L, 0); dp = dmalloc_atomic(l); if(!dp) { ERROR("Failed to alloc memory for file.\n"); sys_close(fd); set_fs(fs); return NULL; } /* Before 2.6.8 * if (read(fd, dp, l) != l) { */ file_offset=0; if (vfs_read(filep, dp, l, &file_offset) != l) { ERROR("Failed to read file: '%s'.\n",filename); dfree(dp,l); sys_close(fd); set_fs(fs); return NULL; } /* Before 2.6.8 * close(fd); */ filp_close(filep,NULL); set_fs(fs); *length = l; return dp; } #define FOPS(i,f,b,c,p) (f,b,c,p) // Save us per device to retrieve our data from // device read/writes static nokia_cs_t *ddev_saved[MAXDEVC] = { NULL, }; static int wld_open (struct inode *inode, struct file *filp); static void wld_release (struct inode *inode, struct file *filp); static size_t wld_read FOPS (struct inode *inode, struct file *filp, char *buf, size_t count, loff_t *ppos ); static size_t wld_write FOPS (struct inode *inode, struct file *filp, const char *buf, size_t count, loff_t *ppos); static int wld_poll (struct file *filp, poll_table *table); static int wld_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); static int wld_fasync(int fd, struct file *filp, int mode); static struct file_operations fops = { read: (void *)wld_read, write: (void *)wld_write, // poll: (void *)wld_poll, ioctl: wld_ioctl, open: wld_open, release: (void *)wld_release, fasync: wld_fasync, }; static int wld_open (struct inode *inode, struct file *filp) { int i; DEBUG( "CHRDEV OPEN %d:%d \n", MAJOR(inode->i_rdev), MINOR(inode->i_rdev)); /* * Find the private device structure * * Handle smartcard minor device by % 10 * (sc minor = minor + 10); */ i = DEVICE_NR(MINOR(inode->i_rdev)) % 10; if(i>MAXDEVC) { ERROR("Too many devices open simultaneously...\n"); return 1; } filp->private_data = ddev_saved[i]; /* MDU: count managed from outside MOD_INC_USE_COUNT; */ try_module_get(THIS_MODULE); return 0; } extern wait_queue_head_t stop_queue; static void wld_release (struct inode *inode, struct file *filp) { int i; i = MINOR(inode->i_rdev); if(i<10) { DEBUG("CHRDEV Close.\n"); wld_fasync(-1, filp, 0); } else { DEBUG("SMARTCARDDEV Close.\n"); } /* MDU: count managed from outside MOD_DEC_USE_COUNT; */ module_put(THIS_MODULE); wake_up_interruptible(&stop_queue); return; } static size_t wld_read FOPS (struct inode *inode, struct file *filp, char *buf, size_t count, loff_t *ppos) { INT16 len = 0; nokia_cs_t *nokia_cs; nokia_cs = (nokia_cs_t *) filp->private_data; if(!nokia_cs) { DEBUG("No nokia_cs_t passed for wld_read?!\n"); return 0; } len = dnc_read(nokia_cs->driver_priv, buf, count); if (len != 0) DEBUG("message read: %i\n", len); return(len); } static int wld_fasync(int fd, struct file *filp, int mode){ nokia_cs_t *nokia_cs = (nokia_cs_t *) filp->private_data; if(!nokia_cs) { DEBUG("No nokia_cs passed for wld_fasync?!\n"); return 0; } return fasync_helper(fd, filp, mode, &nokia_cs->dev->async_queue); } static int wld_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { nokia_cs_t *nokia_cs = (nokia_cs_t *) filp->private_data; int ret = FALSE; DEBUG( "CHRDEV IOCTL %d:%d \n", MAJOR(inode->i_rdev), MINOR(inode->i_rdev)); if (!nokia_cs || !nokia_cs->driver_priv){ ERROR("NULL arguments\n"); return -EINVAL; } /* Call private ioctl */ ret = dnc_ioctl (nokia_cs->driver_priv, cmd, arg); if (ret != TRUE) return -EINVAL; return 0; } static int wld_poll (struct file *filp, poll_table *table) { return 0; } static size_t wld_write FOPS (struct inode *inode, struct file *filp, const char *buf, size_t count, loff_t *ppos) { nokia_cs_t *nokia_cs; int len = 0, ind; DEBUG("wld_write\n"); nokia_cs = (nokia_cs_t *) filp->private_data; if(!nokia_cs || !nokia_cs->driver_priv) { DEBUG("No pointer to nokia_cs structure in wld_write?!\n"); return -EIO; } for(ind = 0; ind < count; ind += len){ len = dnc_write(nokia_cs->driver_priv, buf, count); if (len < 0){ DEBUG("wld_write failed\n"); return -EIO; } else if (len < count) interruptible_sleep_on(&nokia_cs->wld_wait); } DEBUG("wld_write exit\n"); return len; } void wld_signal_to_user(UINT32 args) { driver_priv_t *priv = (driver_priv_t *)args; nokia_cs_t *nokia_cs; if(!priv || !priv->nokia_cs) { DEBUG("No priv passed for wld_signal_to_user?!\n"); return; } nokia_cs = (nokia_cs_t*) priv->nokia_cs; if (nokia_cs->dev && nokia_cs->dev->async_queue) kill_fasync(&nokia_cs->dev->async_queue, SIGIO, POLL_PRI); } void wld_wake_up_write(driver_priv_t* priv){ nokia_cs_t *nokia_cs; if(!priv || !priv->nokia_cs) { DEBUG("No priv passed for wld_wake_up_write?!\n"); return; } nokia_cs = (nokia_cs_t*) priv->nokia_cs; wake_up_interruptible(&nokia_cs->wld_wait); } int ddev_register(driver_priv_t* priv) { nokia_cs_t *nokia_cs; int result,i; if (!priv) return 0; nokia_cs = (nokia_cs_t *) (priv->nokia_cs); if(!nokia_cs) { ERROR("No nokia_cs given to ddev_register?!\n"); return 0; } for(i=0;idev) { ERROR("PANIC: Device already in nokia_cs structure!\n"); return 0; } nokia_cs->dev = dmalloc_kernel(sizeof(ddev_t)); if(!nokia_cs->dev) { ERROR("Not enough memory for character device!\n"); return 0; } ddev_saved[i] = nokia_cs; dmemset(nokia_cs->dev,0,sizeof(ddev_t)); dstrcpy(&nokia_cs->dev->name[0],"nokia_cs"); nokia_cs->dev->major = 0; nokia_cs->dev->minor = 0; nokia_cs->dev->itemroot = NULL; result = register_chrdev(nokia_cs->dev->major, nokia_cs->dev->name, &fops); if (result < 0) { ERROR( "WLAN: can't get major (got %d) for character device.\n",result); return 0; } nokia_cs->dev->major = result; DEBUG("Character device got major %d\n",result); proc_net_create("nokia_cs", 0, d_procfile_read); DEBUG("ddev_register done\n"); return(1); } int ddev_unregister(driver_priv_t* priv) { nokia_cs_t *nokia_cs; int result,i; if (!priv) return 0; nokia_cs = (nokia_cs_t *) (priv->nokia_cs); if (!nokia_cs) return 0; DEBUG("Unregistering %s (major %d)\n",nokia_cs->dev->name, nokia_cs->dev->major); for(i=0;idev->major,nokia_cs->dev->name); // free procdevice proc_net_remove("nokia_cs"); return(1); } /* This is the result to e.g. procinfo */ #define D_PWRITE(args...) { dsprintf(tmp,args); if(dstrlen(buf)+dstrlen(tmp)nokia_cs); if (!nokia_cs) return 0; buf[0]='\0'; tmp = dt_getspace(256); D_PWRITE("WLAN NetDev =%s\n",nokia_cs->driver_priv->llc->dev->name); D_PWRITE("WLAN MAC =%s\n",dt_printmac(nokia_cs->driver_priv->llc->dev->dev_addr, mac_buf)); //WLAN chr dev D_PWRITE("WLAN ChrDev =%s\n",&nokia_cs->dev->name[0]); D_PWRITE("WLAN ChMajor=%d\n",nokia_cs->dev->major); D_PWRITE("WLAN ChMinor=%d\n",nokia_cs->dev->minor); // GSM tty dev D_PWRITE("GSM TTYDev =%s%s\n",SERIAL_D211_NAME,"0"); D_PWRITE("GSM ChMajor =%d\n",SERIAL_D211_MAJOR); D_PWRITE("GSM ChMinor =%d\n",SERIAL_D211_MINOR); #ifdef D_DEBUG D_PWRITE("Tx_packets_in_queue=%d\n",nokia_cs->driver_priv->llc->tx_packets_in_queue); D_PWRITE("Tx_packets_dropped=%d\n",nokia_cs->driver_priv->llc->tx_packets_dropped); D_PWRITE("Netif_stopped=%d\n",nokia_cs->driver_priv->llc->netif_stopped); #endif len = dstrlen(buf); len += dnc_proc_status(nokia_cs->driver_priv,&buf[len],bufsize-len); return(dstrlen(buf)); } /* Procfile operations */ static int d_procfile_read(char *buffer, char **buffer_location, off_t offset, int buffer_length) { nokia_cs_t *nokia_cs; int i, len = 0; if(offset>0) return 0; for(i=0;idriver_priv) continue; if(!nokia_cs->driver_priv->llc) continue; if(!nokia_cs->driver_priv->mgr) continue; if(!nokia_cs->dev) continue; len+=ddev_proc_status(nokia_cs->driver_priv,&buffer[len],buffer_length-len); } return(len); } /* * Layer 2 handoff function. * Provides layer 2 handoff triggers, for both WLAN and GSM. */ void dt_layer2_handoff(driver_priv_t* priv, int type) { switch(type & LINKLAYER_HANDOFF_TYPE){ //What is happening? case LINKLAYER_HANDOFF_BEGIN: //started to join a network DEBUG("LINKLAYER_HANDOFF_BEGIN\n"); break; case LINKLAYER_HANDOFF_DONE: //now connected to a network DEBUG("LINKLAYER_HANDOFF_DONE\n"); #ifdef ENABLE_LINKLAYER_API /* This is an example how to implement * handoff triggering to another module */ { int (*func)(void); //check if handoff API function exists /* try_module_get */ if ((func = inter_module_get("ll_handoff"))){ /* call handoff function */ func(); /* module_put */ inter_module_put("ll_handoff"); } } #endif break; default: ERROR("Unknown handoff type\n"); break; } switch(type & LINKLAYER_HANDOFF_SUBTYPE){//Which link? case GSM_HANDOFF: //GSM network trigger (GSM data or GPRS) DEBUG("GSM_HANDOFF\n"); break; case WLAN_HANDOFF: //WLAN trigger DEBUG("WLAN_HANDOFF\n"); break; default: ERROR("Unknown handoff subtype\n"); break; } } /* Some functions to provide a consistent kernel interface */ /* Memory handling */ void* dmalloc_atomic(INT32 size) { return (void*)(kmalloc((size_t)size, GFP_ATOMIC)); } void* dmalloc_kernel(INT32 size) { return (void*)(kmalloc((size_t)size, GFP_KERNEL)); } void* dmemcpy(void *dest, const void *src, INT32 size) { return (void*)(memcpy (dest,src,(__kernel_size_t)size)); } void* dmemmove(void *dest, const void *src, INT32 size) { return (void*)(memmove (dest,src,(__kernel_size_t)size)); } void dfree(const void *o, INT32 s) { kfree(o); } int dmemcmp(const void *s1, const void *s2, INT32 size) { return memcmp(s1,s2,(__kernel_size_t)size); } void* dmemset(void *s1,int s2,INT32 size) { return (void*)(memset(s1,s2,(__kernel_size_t)size)); } /* copying between driver and userpace */ unsigned long d_copy_to_user(void* to, const void* from, unsigned long count){ return copy_to_user(to, from, count); } unsigned long d_copy_from_user(void* to, const void* from, unsigned long count){ return copy_from_user(to, from, count); } /* String handling functions */ char* dstrcpy(char *s1,const char *s2) { return strcpy(s1,s2); } char* dstrncpy(char *s1,const char *s2, INT32 size) { return strncpy(s1,s2,(__kernel_size_t)size); } int dstrncmp(const char *s1,const char *s2,INT32 size) { return strncmp(s1,s2,(__kernel_size_t)size); } INT32 dstrlen(const char *s1) { return (INT32) strlen(s1); } INT32 dstrnlen(const char *s1, int size) { return (INT32) strnlen(s1,(__kernel_size_t)size); } int dsprintf(char * buf, const char * fmt, ...) { va_list args; va_start(args,fmt); vsprintf(buf,fmt,args); va_end(args); return 0; } char* dstrcat(char *s1, const char *s2) { return strcat(s1,s2); } // Small delays void d_udelay(unsigned long s1) { udelay(s1); } // Bigger delays void d_mdelay(unsigned long s1) { mdelay(s1); } // Printk int dprintk(const char *fmt, ...) { char s[1024]; va_list args; va_start(args,fmt); vsprintf(s,fmt,args); printk("%s",s); va_end(args); return 0; } unsigned short dinw(UINT32 base) { return inw(base); } void doutw(UINT32 base,UINT32 value) { outw(base,value); }