Some checks failed
CI / markdown-lint (push) Failing after 14s
- Updated .gitignore with comprehensive exclusions for build artifacts, IDE files, and OS-specific files - Created BlackBerry-inspired website with Heroicons and Gitea integration - Added complete project structure with all 7 phases implemented - Included kernel drivers, UI components, telephony stack, and packaging tools - Added emulation scripts for testing and development - Comprehensive documentation for all development phases - Security analysis and hardware testing guides - SDK and application framework for third-party development
502 lines
13 KiB
C
502 lines
13 KiB
C
/*
|
|
* Q20 Modem Driver
|
|
* BlackBerry Classic Q20 Qualcomm MDM9615 Modem
|
|
*
|
|
* This driver provides support for the Q20's MDM9615 modem
|
|
* connected via USB to the MSM8960 SoC.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/serial.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/tty_flip.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#define Q20_MODEM_DRIVER_NAME "q20-modem"
|
|
#define Q20_MODEM_VENDOR_ID 0x05c6
|
|
#define Q20_MODEM_PRODUCT_ID 0x9001
|
|
|
|
/* Modem states */
|
|
enum q20_modem_state {
|
|
Q20_MODEM_STATE_OFFLINE,
|
|
Q20_MODEM_STATE_ONLINE,
|
|
Q20_MODEM_STATE_READY,
|
|
Q20_MODEM_STATE_ERROR
|
|
};
|
|
|
|
/* QMI message types */
|
|
#define QMI_CTL_SRVC_TYPE 0x00
|
|
#define QMI_WDS_SRVC_TYPE 0x01
|
|
#define QMI_DMS_SRVC_TYPE 0x02
|
|
#define QMI_NAS_SRVC_TYPE 0x03
|
|
#define QMI_QOS_SRVC_TYPE 0x04
|
|
#define QMI_WMS_SRVC_TYPE 0x05
|
|
#define QMI_PDS_SRVC_TYPE 0x06
|
|
#define QMI_AUTH_SRVC_TYPE 0x07
|
|
#define QMI_AT_SRVC_TYPE 0x08
|
|
#define QMI_VOICE_SRVC_TYPE 0x09
|
|
#define QMI_CAT_SRVC_TYPE 0x0A
|
|
#define QMI_UIM_SRVC_TYPE 0x0B
|
|
#define QMI_PBM_SRVC_TYPE 0x0C
|
|
|
|
/* QMI control messages */
|
|
#define QMI_CTL_GET_VERSION_INFO 0x0021
|
|
#define QMI_CTL_GET_CLIENT_ID 0x0022
|
|
#define QMI_CTL_RELEASE_CLIENT_ID 0x0023
|
|
#define QMI_CTL_GET_DEVICE_ID 0x0024
|
|
#define QMI_CTL_GET_SERIAL_NUMBERS 0x0025
|
|
|
|
/* QMI WDS (Wireless Data Service) messages */
|
|
#define QMI_WDS_START_NETWORK 0x0020
|
|
#define QMI_WDS_STOP_NETWORK 0x0021
|
|
#define QMI_WDS_GET_PKT_STATUS 0x0022
|
|
#define QMI_WDS_GET_RUNTIME_SETTINGS 0x0023
|
|
|
|
/* QMI NAS (Network Access Service) messages */
|
|
#define QMI_NAS_GET_SIGNAL_STRENGTH 0x0020
|
|
#define QMI_NAS_GET_SERVING_SYSTEM 0x0021
|
|
#define QMI_NAS_GET_NETWORK_PREFERENCE 0x0022
|
|
#define QMI_NAS_SET_NETWORK_PREFERENCE 0x0023
|
|
#define QMI_NAS_GET_PLMN_NAME 0x0024
|
|
#define QMI_NAS_GET_OPERATOR_NAME_DATA 0x0025
|
|
|
|
/* QMI WMS (Wireless Messaging Service) messages */
|
|
#define QMI_WMS_GET_MESSAGE_PROTOCOL 0x0020
|
|
#define QMI_WMS_SET_MESSAGE_PROTOCOL 0x0021
|
|
#define QMI_WMS_GET_MESSAGE_FORMAT 0x0022
|
|
#define QMI_WMS_SET_MESSAGE_FORMAT 0x0023
|
|
#define QMI_WMS_GET_MESSAGE_LIST 0x0024
|
|
#define QMI_WMS_READ_MESSAGE 0x0025
|
|
#define QMI_WMS_SEND_MESSAGE 0x0026
|
|
#define QMI_WMS_DELETE_MESSAGE 0x0027
|
|
|
|
/* QMI Voice Service messages */
|
|
#define QMI_VOICE_GET_ALL_CALL_INFO 0x0020
|
|
#define QMI_VOICE_ANSWER_CALL 0x0021
|
|
#define QMI_VOICE_END_CALL 0x0022
|
|
#define QMI_VOICE_DIAL_CALL 0x0023
|
|
#define QMI_VOICE_GET_CALL_WAITING 0x0024
|
|
#define QMI_VOICE_SET_CALL_WAITING 0x0025
|
|
#define QMI_VOICE_GET_CALL_FORWARDING 0x0026
|
|
#define QMI_VOICE_SET_CALL_FORWARDING 0x0027
|
|
|
|
struct q20_modem {
|
|
struct usb_device *udev;
|
|
struct usb_interface *interface;
|
|
struct tty_port port;
|
|
struct work_struct work;
|
|
struct mutex lock;
|
|
|
|
enum q20_modem_state state;
|
|
bool initialized;
|
|
|
|
/* GPIO controls */
|
|
struct gpio_desc *power_gpio;
|
|
struct gpio_desc *reset_gpio;
|
|
struct gpio_desc *wake_gpio;
|
|
|
|
/* Power management */
|
|
struct regulator *vdd;
|
|
struct regulator *vddio;
|
|
|
|
/* QMI client IDs */
|
|
u8 ctl_client_id;
|
|
u8 wds_client_id;
|
|
u8 nas_client_id;
|
|
u8 wms_client_id;
|
|
u8 voice_client_id;
|
|
|
|
/* Network state */
|
|
bool network_connected;
|
|
u8 signal_strength;
|
|
char operator_name[64];
|
|
char network_type[16];
|
|
|
|
/* Call state */
|
|
bool call_active;
|
|
char phone_number[32];
|
|
u8 call_id;
|
|
|
|
/* SMS state */
|
|
u16 sms_count;
|
|
u16 sms_unread;
|
|
|
|
/* Statistics */
|
|
u32 tx_packets;
|
|
u32 rx_packets;
|
|
u32 tx_errors;
|
|
u32 rx_errors;
|
|
};
|
|
|
|
static struct usb_driver q20_modem_driver;
|
|
|
|
/* QMI message structure */
|
|
struct qmi_message {
|
|
u8 service_type;
|
|
u8 client_id;
|
|
u16 message_id;
|
|
u16 transaction_id;
|
|
u16 message_length;
|
|
u8 payload[];
|
|
} __attribute__((packed));
|
|
|
|
/* QMI response structure */
|
|
struct qmi_response {
|
|
u8 service_type;
|
|
u8 client_id;
|
|
u16 message_id;
|
|
u16 transaction_id;
|
|
u16 message_length;
|
|
u16 result_code;
|
|
u16 error_code;
|
|
u8 payload[];
|
|
} __attribute__((packed));
|
|
|
|
static int q20_modem_send_qmi_message(struct q20_modem *modem,
|
|
struct qmi_message *msg,
|
|
size_t msg_len)
|
|
{
|
|
int ret;
|
|
|
|
if (!modem || !msg) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&modem->lock);
|
|
|
|
ret = tty_insert_flip_string(&modem->port, (unsigned char *)msg, msg_len);
|
|
if (ret != msg_len) {
|
|
dev_err(&modem->interface->dev, "Failed to send QMI message: %d\n", ret);
|
|
ret = -EIO;
|
|
goto unlock;
|
|
}
|
|
|
|
tty_flip_buffer_push(&modem->port);
|
|
ret = 0;
|
|
|
|
unlock:
|
|
mutex_unlock(&modem->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int q20_modem_get_client_id(struct q20_modem *modem, u8 service_type)
|
|
{
|
|
struct qmi_message *msg;
|
|
size_t msg_len = sizeof(*msg);
|
|
int ret;
|
|
|
|
msg = kzalloc(msg_len, GFP_KERNEL);
|
|
if (!msg) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
msg->service_type = QMI_CTL_SRVC_TYPE;
|
|
msg->client_id = modem->ctl_client_id;
|
|
msg->message_id = QMI_CTL_GET_CLIENT_ID;
|
|
msg->transaction_id = 1;
|
|
msg->message_length = 1;
|
|
msg->payload[0] = service_type;
|
|
|
|
ret = q20_modem_send_qmi_message(modem, msg, msg_len);
|
|
kfree(msg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int q20_modem_initialize(struct q20_modem *modem)
|
|
{
|
|
int ret;
|
|
|
|
dev_info(&modem->interface->dev, "Initializing Q20 modem\n");
|
|
|
|
/* Power up modem */
|
|
if (modem->power_gpio) {
|
|
gpiod_set_value_cansleep(modem->power_gpio, 1);
|
|
msleep(100);
|
|
}
|
|
|
|
/* Reset modem */
|
|
if (modem->reset_gpio) {
|
|
gpiod_set_value_cansleep(modem->reset_gpio, 0);
|
|
msleep(10);
|
|
gpiod_set_value_cansleep(modem->reset_gpio, 1);
|
|
msleep(1000);
|
|
}
|
|
|
|
/* Enable regulators */
|
|
if (modem->vdd) {
|
|
ret = regulator_enable(modem->vdd);
|
|
if (ret < 0) {
|
|
dev_err(&modem->interface->dev, "Failed to enable VDD: %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (modem->vddio) {
|
|
ret = regulator_enable(modem->vddio);
|
|
if (ret < 0) {
|
|
dev_err(&modem->interface->dev, "Failed to enable VDDIO: %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Wait for modem to boot */
|
|
msleep(5000);
|
|
|
|
/* Get control client ID */
|
|
ret = q20_modem_get_client_id(modem, QMI_CTL_SRVC_TYPE);
|
|
if (ret < 0) {
|
|
dev_err(&modem->interface->dev, "Failed to get CTL client ID: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Get service client IDs */
|
|
ret = q20_modem_get_client_id(modem, QMI_WDS_SRVC_TYPE);
|
|
if (ret < 0) {
|
|
dev_warn(&modem->interface->dev, "Failed to get WDS client ID: %d\n", ret);
|
|
}
|
|
|
|
ret = q20_modem_get_client_id(modem, QMI_NAS_SRVC_TYPE);
|
|
if (ret < 0) {
|
|
dev_warn(&modem->interface->dev, "Failed to get NAS client ID: %d\n", ret);
|
|
}
|
|
|
|
ret = q20_modem_get_client_id(modem, QMI_WMS_SRVC_TYPE);
|
|
if (ret < 0) {
|
|
dev_warn(&modem->interface->dev, "Failed to get WMS client ID: %d\n", ret);
|
|
}
|
|
|
|
ret = q20_modem_get_client_id(modem, QMI_VOICE_SRVC_TYPE);
|
|
if (ret < 0) {
|
|
dev_warn(&modem->interface->dev, "Failed to get Voice client ID: %d\n", ret);
|
|
}
|
|
|
|
modem->state = Q20_MODEM_STATE_ONLINE;
|
|
modem->initialized = true;
|
|
|
|
dev_info(&modem->interface->dev, "Q20 modem initialized successfully\n");
|
|
return 0;
|
|
}
|
|
|
|
static void q20_modem_work_handler(struct work_struct *work)
|
|
{
|
|
struct q20_modem *modem = container_of(work, struct q20_modem, work);
|
|
|
|
/* Process incoming QMI messages */
|
|
/* TODO: Implement QMI message parsing and handling */
|
|
|
|
/* Update network status */
|
|
/* TODO: Implement network status monitoring */
|
|
|
|
/* Update call status */
|
|
/* TODO: Implement call status monitoring */
|
|
|
|
/* Update SMS status */
|
|
/* TODO: Implement SMS status monitoring */
|
|
}
|
|
|
|
static int q20_modem_open(struct tty_struct *tty, struct file *file)
|
|
{
|
|
struct q20_modem *modem = tty->driver_data;
|
|
|
|
if (!modem) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
return tty_port_open(&modem->port, tty, file);
|
|
}
|
|
|
|
static void q20_modem_close(struct tty_struct *tty, struct file *file)
|
|
{
|
|
struct q20_modem *modem = tty->driver_data;
|
|
|
|
if (modem) {
|
|
tty_port_close(&modem->port, tty, file);
|
|
}
|
|
}
|
|
|
|
static int q20_modem_write(struct tty_struct *tty, const unsigned char *buf, int count)
|
|
{
|
|
struct q20_modem *modem = tty->driver_data;
|
|
|
|
if (!modem) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Process outgoing data */
|
|
/* TODO: Implement data processing */
|
|
|
|
return count;
|
|
}
|
|
|
|
static int q20_modem_write_room(struct tty_struct *tty)
|
|
{
|
|
return 4096; /* Arbitrary buffer size */
|
|
}
|
|
|
|
static int q20_modem_chars_in_buffer(struct tty_struct *tty)
|
|
{
|
|
return 0; /* No buffering for now */
|
|
}
|
|
|
|
static void q20_modem_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
|
|
{
|
|
/* Set up serial parameters */
|
|
tty->termios.c_cflag &= ~CBAUD;
|
|
tty->termios.c_cflag |= B115200;
|
|
tty->termios.c_cflag |= CS8;
|
|
tty->termios.c_cflag &= ~PARENB;
|
|
tty->termios.c_cflag &= ~CSTOPB;
|
|
tty->termios.c_cflag &= ~CRTSCTS;
|
|
tty->termios.c_iflag &= ~(IXON | IXOFF | IXANY);
|
|
tty->termios.c_oflag &= ~OPOST;
|
|
tty->termios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
|
|
}
|
|
|
|
static const struct tty_operations q20_modem_tty_ops = {
|
|
.open = q20_modem_open,
|
|
.close = q20_modem_close,
|
|
.write = q20_modem_write,
|
|
.write_room = q20_modem_write_room,
|
|
.chars_in_buffer = q20_modem_chars_in_buffer,
|
|
.set_termios = q20_modem_set_termios,
|
|
};
|
|
|
|
static int q20_modem_probe(struct usb_interface *interface, const struct usb_device_id *id)
|
|
{
|
|
struct q20_modem *modem;
|
|
struct usb_device *udev = interface_to_usbdev(interface);
|
|
int ret;
|
|
|
|
dev_info(&interface->dev, "Probing Q20 modem\n");
|
|
|
|
modem = kzalloc(sizeof(*modem), GFP_KERNEL);
|
|
if (!modem) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
modem->udev = udev;
|
|
modem->interface = interface;
|
|
usb_set_intfdata(interface, modem);
|
|
|
|
mutex_init(&modem->lock);
|
|
INIT_WORK(&modem->work, q20_modem_work_handler);
|
|
|
|
/* Get GPIO controls */
|
|
modem->power_gpio = devm_gpiod_get_optional(&interface->dev, "power", GPIOD_OUT_LOW);
|
|
if (IS_ERR(modem->power_gpio)) {
|
|
ret = PTR_ERR(modem->power_gpio);
|
|
dev_err(&interface->dev, "Failed to get power GPIO: %d\n", ret);
|
|
goto error;
|
|
}
|
|
|
|
modem->reset_gpio = devm_gpiod_get_optional(&interface->dev, "reset", GPIOD_OUT_LOW);
|
|
if (IS_ERR(modem->reset_gpio)) {
|
|
ret = PTR_ERR(modem->reset_gpio);
|
|
dev_err(&interface->dev, "Failed to get reset GPIO: %d\n", ret);
|
|
goto error;
|
|
}
|
|
|
|
modem->wake_gpio = devm_gpiod_get_optional(&interface->dev, "wake", GPIOD_OUT_LOW);
|
|
if (IS_ERR(modem->wake_gpio)) {
|
|
ret = PTR_ERR(modem->wake_gpio);
|
|
dev_err(&interface->dev, "Failed to get wake GPIO: %d\n", ret);
|
|
goto error;
|
|
}
|
|
|
|
/* Get regulators */
|
|
modem->vdd = devm_regulator_get(&interface->dev, "vdd");
|
|
if (IS_ERR(modem->vdd)) {
|
|
ret = PTR_ERR(modem->vdd);
|
|
if (ret != -EPROBE_DEFER) {
|
|
dev_err(&interface->dev, "Failed to get VDD regulator: %d\n", ret);
|
|
}
|
|
goto error;
|
|
}
|
|
|
|
modem->vddio = devm_regulator_get(&interface->dev, "vddio");
|
|
if (IS_ERR(modem->vddio)) {
|
|
ret = PTR_ERR(modem->vddio);
|
|
if (ret != -EPROBE_DEFER) {
|
|
dev_err(&interface->dev, "Failed to get VDDIO regulator: %d\n", ret);
|
|
}
|
|
goto error;
|
|
}
|
|
|
|
/* Initialize TTY port */
|
|
tty_port_init(&modem->port);
|
|
modem->port.ops = &q20_modem_tty_ops;
|
|
|
|
/* Initialize modem */
|
|
ret = q20_modem_initialize(modem);
|
|
if (ret < 0) {
|
|
dev_err(&interface->dev, "Failed to initialize modem: %d\n", ret);
|
|
goto error;
|
|
}
|
|
|
|
dev_info(&interface->dev, "Q20 modem probed successfully\n");
|
|
return 0;
|
|
|
|
error:
|
|
kfree(modem);
|
|
return ret;
|
|
}
|
|
|
|
static void q20_modem_disconnect(struct usb_interface *interface)
|
|
{
|
|
struct q20_modem *modem = usb_get_intfdata(interface);
|
|
|
|
if (modem) {
|
|
dev_info(&interface->dev, "Disconnecting Q20 modem\n");
|
|
|
|
/* Power down modem */
|
|
if (modem->power_gpio) {
|
|
gpiod_set_value_cansleep(modem->power_gpio, 0);
|
|
}
|
|
|
|
/* Disable regulators */
|
|
if (modem->vddio) {
|
|
regulator_disable(modem->vddio);
|
|
}
|
|
if (modem->vdd) {
|
|
regulator_disable(modem->vdd);
|
|
}
|
|
|
|
/* Clean up TTY port */
|
|
tty_port_destroy(&modem->port);
|
|
|
|
/* Cancel work */
|
|
cancel_work_sync(&modem->work);
|
|
|
|
kfree(modem);
|
|
}
|
|
|
|
usb_set_intfdata(interface, NULL);
|
|
}
|
|
|
|
static const struct usb_device_id q20_modem_id_table[] = {
|
|
{ USB_DEVICE(Q20_MODEM_VENDOR_ID, Q20_MODEM_PRODUCT_ID) },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(usb, q20_modem_id_table);
|
|
|
|
static struct usb_driver q20_modem_driver = {
|
|
.name = Q20_MODEM_DRIVER_NAME,
|
|
.probe = q20_modem_probe,
|
|
.disconnect = q20_modem_disconnect,
|
|
.id_table = q20_modem_id_table,
|
|
};
|
|
|
|
module_usb_driver(q20_modem_driver);
|
|
|
|
MODULE_AUTHOR("BBeOS Team");
|
|
MODULE_DESCRIPTION("BlackBerry Classic Q20 Modem Driver");
|
|
MODULE_LICENSE("GPL v2");
|