2019-05-27 09:55:05 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2005-04-17 02:20:36 +04:00
/*
*
* Digianswer Bluetooth USB driver
*
2007-10-20 15:41:33 +04:00
* Copyright ( C ) 2004 - 2007 Marcel Holtmann < marcel @ holtmann . org >
2005-04-17 02:20:36 +04:00
*/
# include <linux/kernel.h>
2007-10-20 15:41:33 +04:00
# include <linux/module.h>
2005-04-17 02:20:36 +04:00
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/types.h>
2007-10-20 15:41:33 +04:00
# include <linux/sched.h>
2005-04-17 02:20:36 +04:00
# include <linux/errno.h>
2007-10-20 15:41:33 +04:00
# include <linux/skbuff.h>
2005-04-17 02:20:36 +04:00
# include <linux/usb.h>
# include <net/bluetooth/bluetooth.h>
# include <net/bluetooth/hci_core.h>
2018-03-24 12:19:53 +03:00
# include "h4_recv.h"
2015-10-08 04:06:53 +03:00
# define VERSION "0.11"
2005-04-17 02:20:36 +04:00
2013-10-11 18:46:20 +04:00
static const struct usb_device_id bpa10x_table [ ] = {
2005-04-17 02:20:36 +04:00
/* Tektronix BPA 100/105 (Digianswer) */
{ USB_DEVICE ( 0x08fd , 0x0002 ) } ,
{ } /* Terminating entry */
} ;
MODULE_DEVICE_TABLE ( usb , bpa10x_table ) ;
struct bpa10x_data {
2007-10-20 15:41:33 +04:00
struct hci_dev * hdev ;
struct usb_device * udev ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
struct usb_anchor tx_anchor ;
struct usb_anchor rx_anchor ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
struct sk_buff * rx_skb [ 2 ] ;
2005-04-17 02:20:36 +04:00
} ;
2007-10-20 15:41:33 +04:00
static void bpa10x_tx_complete ( struct urb * urb )
2005-04-17 02:20:36 +04:00
{
2007-10-20 15:41:33 +04:00
struct sk_buff * skb = urb - > context ;
struct hci_dev * hdev = ( struct hci_dev * ) skb - > dev ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
BT_DBG ( " %s urb %p status %d count %d " , hdev - > name ,
urb , urb - > status , urb - > actual_length ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
if ( ! test_bit ( HCI_RUNNING , & hdev - > flags ) )
goto done ;
if ( ! urb - > status )
hdev - > stat . byte_tx + = urb - > transfer_buffer_length ;
2005-04-17 02:20:36 +04:00
else
2007-10-20 15:41:33 +04:00
hdev - > stat . err_tx + + ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
done :
kfree ( urb - > setup_packet ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
kfree_skb ( skb ) ;
}
2015-10-08 04:06:53 +03:00
# define HCI_VENDOR_HDR_SIZE 5
# define HCI_RECV_VENDOR \
. type = HCI_VENDOR_PKT , \
. hlen = HCI_VENDOR_HDR_SIZE , \
. loff = 3 , \
. lsize = 2 , \
. maxlen = HCI_MAX_FRAME_SIZE
static const struct h4_recv_pkt bpa10x_recv_pkts [ ] = {
{ H4_RECV_ACL , . recv = hci_recv_frame } ,
{ H4_RECV_SCO , . recv = hci_recv_frame } ,
{ H4_RECV_EVENT , . recv = hci_recv_frame } ,
{ HCI_RECV_VENDOR , . recv = hci_recv_diag } ,
} ;
2007-10-20 15:41:33 +04:00
static void bpa10x_rx_complete ( struct urb * urb )
{
struct hci_dev * hdev = urb - > context ;
2012-02-10 00:58:32 +04:00
struct bpa10x_data * data = hci_get_drvdata ( hdev ) ;
2007-10-20 15:41:33 +04:00
int err ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
BT_DBG ( " %s urb %p status %d count %d " , hdev - > name ,
urb , urb - > status , urb - > actual_length ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
if ( ! test_bit ( HCI_RUNNING , & hdev - > flags ) )
return ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
if ( urb - > status = = 0 ) {
2015-10-08 04:06:53 +03:00
bool idx = usb_pipebulk ( urb - > pipe ) ;
data - > rx_skb [ idx ] = h4_recv_buf ( hdev , data - > rx_skb [ idx ] ,
2007-10-20 15:41:33 +04:00
urb - > transfer_buffer ,
2015-10-08 04:06:53 +03:00
urb - > actual_length ,
bpa10x_recv_pkts ,
ARRAY_SIZE ( bpa10x_recv_pkts ) ) ;
if ( IS_ERR ( data - > rx_skb [ idx ] ) ) {
2017-10-30 12:42:59 +03:00
bt_dev_err ( hdev , " corrupted event packet " ) ;
2007-10-20 15:41:33 +04:00
hdev - > stat . err_rx + + ;
2015-10-08 04:06:53 +03:00
data - > rx_skb [ idx ] = NULL ;
2007-10-20 15:41:33 +04:00
}
2005-04-17 02:20:36 +04:00
}
2007-10-20 15:41:33 +04:00
usb_anchor_urb ( urb , & data - > rx_anchor ) ;
err = usb_submit_urb ( urb , GFP_ATOMIC ) ;
if ( err < 0 ) {
2017-10-30 12:42:59 +03:00
bt_dev_err ( hdev , " urb %p failed to resubmit (%d) " , urb , - err ) ;
2007-10-20 15:41:33 +04:00
usb_unanchor_urb ( urb ) ;
2005-04-17 02:20:36 +04:00
}
}
2007-10-20 15:41:33 +04:00
static inline int bpa10x_submit_intr_urb ( struct hci_dev * hdev )
2005-04-17 02:20:36 +04:00
{
2012-02-10 00:58:32 +04:00
struct bpa10x_data * data = hci_get_drvdata ( hdev ) ;
2007-10-20 15:41:33 +04:00
struct urb * urb ;
unsigned char * buf ;
unsigned int pipe ;
int err , size = 16 ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
BT_DBG ( " %s " , hdev - > name ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
if ( ! urb )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
buf = kmalloc ( size , GFP_KERNEL ) ;
if ( ! buf ) {
usb_free_urb ( urb ) ;
return - ENOMEM ;
}
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
pipe = usb_rcvintpipe ( data - > udev , 0x81 ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
usb_fill_int_urb ( urb , data - > udev , pipe , buf , size ,
bpa10x_rx_complete , hdev , 1 ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
urb - > transfer_flags | = URB_FREE_BUFFER ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
usb_anchor_urb ( urb , & data - > rx_anchor ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
err = usb_submit_urb ( urb , GFP_KERNEL ) ;
if ( err < 0 ) {
2017-10-30 12:42:59 +03:00
bt_dev_err ( hdev , " urb %p submission failed (%d) " , urb , - err ) ;
2007-10-20 15:41:33 +04:00
usb_unanchor_urb ( urb ) ;
2005-04-17 02:20:36 +04:00
}
2007-10-20 15:41:33 +04:00
usb_free_urb ( urb ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
return err ;
2005-04-17 02:20:36 +04:00
}
2007-10-20 15:41:33 +04:00
static inline int bpa10x_submit_bulk_urb ( struct hci_dev * hdev )
2005-04-17 02:20:36 +04:00
{
2012-02-10 00:58:32 +04:00
struct bpa10x_data * data = hci_get_drvdata ( hdev ) ;
2005-04-17 02:20:36 +04:00
struct urb * urb ;
unsigned char * buf ;
2007-10-20 15:41:33 +04:00
unsigned int pipe ;
int err , size = 64 ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
BT_DBG ( " %s " , hdev - > name ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! urb )
2007-10-20 15:41:33 +04:00
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
buf = kmalloc ( size , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! buf ) {
usb_free_urb ( urb ) ;
2007-10-20 15:41:33 +04:00
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
}
2007-10-20 15:41:33 +04:00
pipe = usb_rcvbulkpipe ( data - > udev , 0x82 ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
usb_fill_bulk_urb ( urb , data - > udev , pipe ,
buf , size , bpa10x_rx_complete , hdev ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
urb - > transfer_flags | = URB_FREE_BUFFER ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
usb_anchor_urb ( urb , & data - > rx_anchor ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
err = usb_submit_urb ( urb , GFP_KERNEL ) ;
if ( err < 0 ) {
2017-10-30 12:42:59 +03:00
bt_dev_err ( hdev , " urb %p submission failed (%d) " , urb , - err ) ;
2007-10-20 15:41:33 +04:00
usb_unanchor_urb ( urb ) ;
2005-04-17 02:20:36 +04:00
}
usb_free_urb ( urb ) ;
2007-10-20 15:41:33 +04:00
return err ;
2005-04-17 02:20:36 +04:00
}
static int bpa10x_open ( struct hci_dev * hdev )
{
2012-02-10 00:58:32 +04:00
struct bpa10x_data * data = hci_get_drvdata ( hdev ) ;
2005-04-17 02:20:36 +04:00
int err ;
2007-10-20 15:41:33 +04:00
BT_DBG ( " %s " , hdev - > name ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
err = bpa10x_submit_intr_urb ( hdev ) ;
if ( err < 0 )
goto error ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
err = bpa10x_submit_bulk_urb ( hdev ) ;
if ( err < 0 )
goto error ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
error :
usb_kill_anchored_urbs ( & data - > rx_anchor ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
static int bpa10x_close ( struct hci_dev * hdev )
{
2012-02-10 00:58:32 +04:00
struct bpa10x_data * data = hci_get_drvdata ( hdev ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
BT_DBG ( " %s " , hdev - > name ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
usb_kill_anchored_urbs ( & data - > rx_anchor ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int bpa10x_flush ( struct hci_dev * hdev )
{
2012-02-10 00:58:32 +04:00
struct bpa10x_data * data = hci_get_drvdata ( hdev ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
BT_DBG ( " %s " , hdev - > name ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
usb_kill_anchored_urbs ( & data - > tx_anchor ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2015-10-08 03:24:06 +03:00
static int bpa10x_setup ( struct hci_dev * hdev )
{
2018-01-06 19:15:14 +03:00
static const u8 req [ ] = { 0x07 } ;
2015-10-08 03:24:06 +03:00
struct sk_buff * skb ;
BT_DBG ( " %s " , hdev - > name ) ;
/* Read revision string */
skb = __hci_cmd_sync ( hdev , 0xfc0e , sizeof ( req ) , req , HCI_INIT_TIMEOUT ) ;
if ( IS_ERR ( skb ) )
return PTR_ERR ( skb ) ;
2017-10-30 12:42:59 +03:00
bt_dev_info ( hdev , " %s " , ( char * ) ( skb - > data + 1 ) ) ;
2015-10-08 03:24:06 +03:00
2016-07-17 20:55:17 +03:00
hci_set_fw_info ( hdev , " %s " , skb - > data + 1 ) ;
2015-10-08 03:24:06 +03:00
kfree_skb ( skb ) ;
return 0 ;
}
2013-10-11 17:19:18 +04:00
static int bpa10x_send_frame ( struct hci_dev * hdev , struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
2012-02-10 00:58:32 +04:00
struct bpa10x_data * data = hci_get_drvdata ( hdev ) ;
2007-10-20 15:41:33 +04:00
struct usb_ctrlrequest * dr ;
struct urb * urb ;
unsigned int pipe ;
int err ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
BT_DBG ( " %s " , hdev - > name ) ;
2005-04-17 02:20:36 +04:00
2013-10-11 17:19:18 +04:00
skb - > dev = ( void * ) hdev ;
2018-07-23 06:24:02 +03:00
urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
2007-10-20 15:41:33 +04:00
if ( ! urb )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
/* Prepend skb with frame type */
networking: make skb_push & __skb_push return void pointers
It seems like a historic accident that these return unsigned char *,
and in many places that means casts are required, more often than not.
Make these functions return void * and remove all the casts across
the tree, adding a (u8 *) cast only where the unsigned char pointer
was used directly, all done with the following spatch:
@@
expression SKB, LEN;
typedef u8;
identifier fn = { skb_push, __skb_push, skb_push_rcsum };
@@
- *(fn(SKB, LEN))
+ *(u8 *)fn(SKB, LEN)
@@
expression E, SKB, LEN;
identifier fn = { skb_push, __skb_push, skb_push_rcsum };
type T;
@@
- E = ((T *)(fn(SKB, LEN)))
+ E = fn(SKB, LEN)
@@
expression SKB, LEN;
identifier fn = { skb_push, __skb_push, skb_push_rcsum };
@@
- fn(SKB, LEN)[0]
+ *(u8 *)fn(SKB, LEN)
Note that the last part there converts from push(...)[0] to the
more idiomatic *(u8 *)push(...).
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 15:29:23 +03:00
* ( u8 * ) skb_push ( skb , 1 ) = hci_skb_pkt_type ( skb ) ;
2005-04-17 02:20:36 +04:00
2015-11-05 09:33:56 +03:00
switch ( hci_skb_pkt_type ( skb ) ) {
2005-04-17 02:20:36 +04:00
case HCI_COMMAND_PKT :
2018-07-23 06:24:02 +03:00
dr = kmalloc ( sizeof ( * dr ) , GFP_KERNEL ) ;
2007-10-20 15:41:33 +04:00
if ( ! dr ) {
usb_free_urb ( urb ) ;
return - ENOMEM ;
}
dr - > bRequestType = USB_TYPE_VENDOR ;
dr - > bRequest = 0 ;
dr - > wIndex = 0 ;
dr - > wValue = 0 ;
dr - > wLength = __cpu_to_le16 ( skb - > len ) ;
pipe = usb_sndctrlpipe ( data - > udev , 0x00 ) ;
usb_fill_control_urb ( urb , data - > udev , pipe , ( void * ) dr ,
skb - > data , skb - > len , bpa10x_tx_complete , skb ) ;
2005-04-17 02:20:36 +04:00
hdev - > stat . cmd_tx + + ;
break ;
case HCI_ACLDATA_PKT :
2007-10-20 15:41:33 +04:00
pipe = usb_sndbulkpipe ( data - > udev , 0x02 ) ;
usb_fill_bulk_urb ( urb , data - > udev , pipe ,
skb - > data , skb - > len , bpa10x_tx_complete , skb ) ;
2005-04-17 02:20:36 +04:00
hdev - > stat . acl_tx + + ;
break ;
case HCI_SCODATA_PKT :
2007-10-20 15:41:33 +04:00
pipe = usb_sndbulkpipe ( data - > udev , 0x02 ) ;
usb_fill_bulk_urb ( urb , data - > udev , pipe ,
skb - > data , skb - > len , bpa10x_tx_complete , skb ) ;
2005-04-17 02:20:36 +04:00
hdev - > stat . sco_tx + + ;
break ;
2007-10-20 15:41:33 +04:00
default :
2008-02-05 14:08:45 +03:00
usb_free_urb ( urb ) ;
2007-10-20 15:41:33 +04:00
return - EILSEQ ;
}
usb_anchor_urb ( urb , & data - > tx_anchor ) ;
2005-04-17 02:20:36 +04:00
2018-07-23 06:24:02 +03:00
err = usb_submit_urb ( urb , GFP_KERNEL ) ;
2007-10-20 15:41:33 +04:00
if ( err < 0 ) {
2017-10-30 12:42:59 +03:00
bt_dev_err ( hdev , " urb %p submission failed " , urb ) ;
2007-10-20 15:41:33 +04:00
kfree ( urb - > setup_packet ) ;
usb_unanchor_urb ( urb ) ;
}
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
usb_free_urb ( urb ) ;
2005-04-17 02:20:36 +04:00
2019-08-31 22:23:40 +03:00
return err ;
2005-04-17 02:20:36 +04:00
}
2015-10-08 04:01:44 +03:00
static int bpa10x_set_diag ( struct hci_dev * hdev , bool enable )
{
const u8 req [ ] = { 0x00 , enable } ;
struct sk_buff * skb ;
BT_DBG ( " %s " , hdev - > name ) ;
if ( ! test_bit ( HCI_RUNNING , & hdev - > flags ) )
return - ENETDOWN ;
/* Enable sniffer operation */
skb = __hci_cmd_sync ( hdev , 0xfc0e , sizeof ( req ) , req , HCI_INIT_TIMEOUT ) ;
if ( IS_ERR ( skb ) )
return PTR_ERR ( skb ) ;
kfree_skb ( skb ) ;
return 0 ;
}
2019-06-24 00:15:48 +03:00
static int bpa10x_probe ( struct usb_interface * intf ,
const struct usb_device_id * id )
2005-04-17 02:20:36 +04:00
{
struct bpa10x_data * data ;
2007-10-20 15:41:33 +04:00
struct hci_dev * hdev ;
2005-04-17 02:20:36 +04:00
int err ;
BT_DBG ( " intf %p id %p " , intf , id ) ;
2007-10-20 15:41:33 +04:00
if ( intf - > cur_altsetting - > desc . bInterfaceNumber ! = 0 )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
2012-07-27 11:08:34 +04:00
data = devm_kzalloc ( & intf - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
2007-10-20 15:41:33 +04:00
if ( ! data )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2007-10-20 15:41:33 +04:00
data - > udev = interface_to_usbdev ( intf ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
init_usb_anchor ( & data - > tx_anchor ) ;
init_usb_anchor ( & data - > rx_anchor ) ;
2005-04-17 02:20:36 +04:00
hdev = hci_alloc_dev ( ) ;
2012-07-27 11:08:34 +04:00
if ( ! hdev )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2010-02-08 17:27:07 +03:00
hdev - > bus = HCI_USB ;
2012-02-10 00:58:32 +04:00
hci_set_drvdata ( hdev , data ) ;
2007-10-20 15:41:33 +04:00
data - > hdev = hdev ;
2005-04-17 02:20:36 +04:00
SET_HCIDEV_DEV ( hdev , & intf - > dev ) ;
2007-10-20 15:41:33 +04:00
hdev - > open = bpa10x_open ;
hdev - > close = bpa10x_close ;
hdev - > flush = bpa10x_flush ;
2015-10-08 03:24:06 +03:00
hdev - > setup = bpa10x_setup ;
2007-10-20 15:41:33 +04:00
hdev - > send = bpa10x_send_frame ;
2015-10-08 04:01:44 +03:00
hdev - > set_diag = bpa10x_set_diag ;
2005-04-17 02:20:36 +04:00
2012-05-23 14:35:46 +04:00
set_bit ( HCI_QUIRK_RESET_ON_CLOSE , & hdev - > quirks ) ;
2008-11-30 14:17:26 +03:00
2005-04-17 02:20:36 +04:00
err = hci_register_dev ( hdev ) ;
if ( err < 0 ) {
hci_free_dev ( hdev ) ;
return err ;
}
usb_set_intfdata ( intf , data ) ;
return 0 ;
}
static void bpa10x_disconnect ( struct usb_interface * intf )
{
struct bpa10x_data * data = usb_get_intfdata ( intf ) ;
BT_DBG ( " intf %p " , intf ) ;
2007-10-20 15:41:33 +04:00
if ( ! data )
2005-04-17 02:20:36 +04:00
return ;
usb_set_intfdata ( intf , NULL ) ;
2007-10-20 15:41:33 +04:00
hci_unregister_dev ( data - > hdev ) ;
2005-04-17 02:20:36 +04:00
2007-10-20 15:41:33 +04:00
hci_free_dev ( data - > hdev ) ;
2012-01-07 18:47:17 +04:00
kfree_skb ( data - > rx_skb [ 0 ] ) ;
kfree_skb ( data - > rx_skb [ 1 ] ) ;
2005-04-17 02:20:36 +04:00
}
static struct usb_driver bpa10x_driver = {
. name = " bpa10x " ,
. probe = bpa10x_probe ,
. disconnect = bpa10x_disconnect ,
. id_table = bpa10x_table ,
2012-04-23 21:08:51 +04:00
. disable_hub_initiated_lpm = 1 ,
2005-04-17 02:20:36 +04:00
} ;
2011-11-18 21:47:34 +04:00
module_usb_driver ( bpa10x_driver ) ;
2005-04-17 02:20:36 +04:00
MODULE_AUTHOR ( " Marcel Holtmann <marcel@holtmann.org> " ) ;
MODULE_DESCRIPTION ( " Digianswer Bluetooth USB driver ver " VERSION ) ;
MODULE_VERSION ( VERSION ) ;
MODULE_LICENSE ( " GPL " ) ;