mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2142 lines
54 KiB
2142 lines
54 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
/* |
|
* ipmi_ssif.c |
|
* |
|
* The interface to the IPMI driver for SMBus access to a SMBus |
|
* compliant device. Called SSIF by the IPMI spec. |
|
* |
|
* Author: Intel Corporation |
|
* Todd Davis <[email protected]> |
|
* |
|
* Rewritten by Corey Minyard <[email protected]> to support the |
|
* non-blocking I2C interface, add support for multi-part |
|
* transactions, add PEC support, and general clenaup. |
|
* |
|
* Copyright 2003 Intel Corporation |
|
* Copyright 2005 MontaVista Software |
|
*/ |
|
|
|
/* |
|
* This file holds the "policy" for the interface to the SSIF state |
|
* machine. It does the configuration, handles timers and interrupts, |
|
* and drives the real SSIF state machine. |
|
*/ |
|
|
|
#define pr_fmt(fmt) "ipmi_ssif: " fmt |
|
#define dev_fmt(fmt) "ipmi_ssif: " fmt |
|
|
|
#if defined(MODVERSIONS) |
|
#include <linux/modversions.h> |
|
#endif |
|
|
|
#include <linux/module.h> |
|
#include <linux/moduleparam.h> |
|
#include <linux/sched.h> |
|
#include <linux/seq_file.h> |
|
#include <linux/timer.h> |
|
#include <linux/delay.h> |
|
#include <linux/errno.h> |
|
#include <linux/spinlock.h> |
|
#include <linux/slab.h> |
|
#include <linux/list.h> |
|
#include <linux/i2c.h> |
|
#include <linux/ipmi_smi.h> |
|
#include <linux/init.h> |
|
#include <linux/dmi.h> |
|
#include <linux/kthread.h> |
|
#include <linux/acpi.h> |
|
#include <linux/ctype.h> |
|
#include <linux/time64.h> |
|
#include "ipmi_dmi.h" |
|
|
|
#define DEVICE_NAME "ipmi_ssif" |
|
|
|
#define IPMI_GET_SYSTEM_INTERFACE_CAPABILITIES_CMD 0x57 |
|
|
|
#define SSIF_IPMI_REQUEST 2 |
|
#define SSIF_IPMI_MULTI_PART_REQUEST_START 6 |
|
#define SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE 7 |
|
#define SSIF_IPMI_MULTI_PART_REQUEST_END 8 |
|
#define SSIF_IPMI_RESPONSE 3 |
|
#define SSIF_IPMI_MULTI_PART_RESPONSE_MIDDLE 9 |
|
|
|
/* ssif_debug is a bit-field |
|
* SSIF_DEBUG_MSG - commands and their responses |
|
* SSIF_DEBUG_STATES - message states |
|
* SSIF_DEBUG_TIMING - Measure times between events in the driver |
|
*/ |
|
#define SSIF_DEBUG_TIMING 4 |
|
#define SSIF_DEBUG_STATE 2 |
|
#define SSIF_DEBUG_MSG 1 |
|
#define SSIF_NODEBUG 0 |
|
#define SSIF_DEFAULT_DEBUG (SSIF_NODEBUG) |
|
|
|
/* |
|
* Timer values |
|
*/ |
|
#define SSIF_MSG_USEC 20000 /* 20ms between message tries. */ |
|
#define SSIF_MSG_PART_USEC 5000 /* 5ms for a message part */ |
|
|
|
/* How many times to we retry sending/receiving the message. */ |
|
#define SSIF_SEND_RETRIES 5 |
|
#define SSIF_RECV_RETRIES 250 |
|
|
|
#define SSIF_MSG_MSEC (SSIF_MSG_USEC / 1000) |
|
#define SSIF_MSG_JIFFIES ((SSIF_MSG_USEC * 1000) / TICK_NSEC) |
|
#define SSIF_MSG_PART_JIFFIES ((SSIF_MSG_PART_USEC * 1000) / TICK_NSEC) |
|
|
|
/* |
|
* Timeout for the watch, only used for get flag timer. |
|
*/ |
|
#define SSIF_WATCH_MSG_TIMEOUT msecs_to_jiffies(10) |
|
#define SSIF_WATCH_WATCHDOG_TIMEOUT msecs_to_jiffies(250) |
|
|
|
enum ssif_intf_state { |
|
SSIF_NORMAL, |
|
SSIF_GETTING_FLAGS, |
|
SSIF_GETTING_EVENTS, |
|
SSIF_CLEARING_FLAGS, |
|
SSIF_GETTING_MESSAGES, |
|
/* FIXME - add watchdog stuff. */ |
|
}; |
|
|
|
#define SSIF_IDLE(ssif) ((ssif)->ssif_state == SSIF_NORMAL \ |
|
&& (ssif)->curr_msg == NULL) |
|
|
|
/* |
|
* Indexes into stats[] in ssif_info below. |
|
*/ |
|
enum ssif_stat_indexes { |
|
/* Number of total messages sent. */ |
|
SSIF_STAT_sent_messages = 0, |
|
|
|
/* |
|
* Number of message parts sent. Messages may be broken into |
|
* parts if they are long. |
|
*/ |
|
SSIF_STAT_sent_messages_parts, |
|
|
|
/* |
|
* Number of time a message was retried. |
|
*/ |
|
SSIF_STAT_send_retries, |
|
|
|
/* |
|
* Number of times the send of a message failed. |
|
*/ |
|
SSIF_STAT_send_errors, |
|
|
|
/* |
|
* Number of message responses received. |
|
*/ |
|
SSIF_STAT_received_messages, |
|
|
|
/* |
|
* Number of message fragments received. |
|
*/ |
|
SSIF_STAT_received_message_parts, |
|
|
|
/* |
|
* Number of times the receive of a message was retried. |
|
*/ |
|
SSIF_STAT_receive_retries, |
|
|
|
/* |
|
* Number of errors receiving messages. |
|
*/ |
|
SSIF_STAT_receive_errors, |
|
|
|
/* |
|
* Number of times a flag fetch was requested. |
|
*/ |
|
SSIF_STAT_flag_fetches, |
|
|
|
/* |
|
* Number of times the hardware didn't follow the state machine. |
|
*/ |
|
SSIF_STAT_hosed, |
|
|
|
/* |
|
* Number of received events. |
|
*/ |
|
SSIF_STAT_events, |
|
|
|
/* Number of asyncronous messages received. */ |
|
SSIF_STAT_incoming_messages, |
|
|
|
/* Number of watchdog pretimeouts. */ |
|
SSIF_STAT_watchdog_pretimeouts, |
|
|
|
/* Number of alers received. */ |
|
SSIF_STAT_alerts, |
|
|
|
/* Always add statistics before this value, it must be last. */ |
|
SSIF_NUM_STATS |
|
}; |
|
|
|
struct ssif_addr_info { |
|
struct i2c_board_info binfo; |
|
char *adapter_name; |
|
int debug; |
|
int slave_addr; |
|
enum ipmi_addr_src addr_src; |
|
union ipmi_smi_info_union addr_info; |
|
struct device *dev; |
|
struct i2c_client *client; |
|
|
|
struct mutex clients_mutex; |
|
struct list_head clients; |
|
|
|
struct list_head link; |
|
}; |
|
|
|
struct ssif_info; |
|
|
|
typedef void (*ssif_i2c_done)(struct ssif_info *ssif_info, int result, |
|
unsigned char *data, unsigned int len); |
|
|
|
struct ssif_info { |
|
struct ipmi_smi *intf; |
|
spinlock_t lock; |
|
struct ipmi_smi_msg *waiting_msg; |
|
struct ipmi_smi_msg *curr_msg; |
|
enum ssif_intf_state ssif_state; |
|
unsigned long ssif_debug; |
|
|
|
struct ipmi_smi_handlers handlers; |
|
|
|
enum ipmi_addr_src addr_source; /* ACPI, PCI, SMBIOS, hardcode, etc. */ |
|
union ipmi_smi_info_union addr_info; |
|
|
|
/* |
|
* Flags from the last GET_MSG_FLAGS command, used when an ATTN |
|
* is set to hold the flags until we are done handling everything |
|
* from the flags. |
|
*/ |
|
#define RECEIVE_MSG_AVAIL 0x01 |
|
#define EVENT_MSG_BUFFER_FULL 0x02 |
|
#define WDT_PRE_TIMEOUT_INT 0x08 |
|
unsigned char msg_flags; |
|
|
|
u8 global_enables; |
|
bool has_event_buffer; |
|
bool supports_alert; |
|
|
|
/* |
|
* Used to tell what we should do with alerts. If we are |
|
* waiting on a response, read the data immediately. |
|
*/ |
|
bool got_alert; |
|
bool waiting_alert; |
|
|
|
/* |
|
* If set to true, this will request events the next time the |
|
* state machine is idle. |
|
*/ |
|
bool req_events; |
|
|
|
/* |
|
* If set to true, this will request flags the next time the |
|
* state machine is idle. |
|
*/ |
|
bool req_flags; |
|
|
|
/* |
|
* Used to perform timer operations when run-to-completion |
|
* mode is on. This is a countdown timer. |
|
*/ |
|
int rtc_us_timer; |
|
|
|
/* Used for sending/receiving data. +1 for the length. */ |
|
unsigned char data[IPMI_MAX_MSG_LENGTH + 1]; |
|
unsigned int data_len; |
|
|
|
/* Temp receive buffer, gets copied into data. */ |
|
unsigned char recv[I2C_SMBUS_BLOCK_MAX]; |
|
|
|
struct i2c_client *client; |
|
ssif_i2c_done done_handler; |
|
|
|
/* Thread interface handling */ |
|
struct task_struct *thread; |
|
struct completion wake_thread; |
|
bool stopping; |
|
int i2c_read_write; |
|
int i2c_command; |
|
unsigned char *i2c_data; |
|
unsigned int i2c_size; |
|
|
|
struct timer_list retry_timer; |
|
int retries_left; |
|
|
|
long watch_timeout; /* Timeout for flags check, 0 if off. */ |
|
struct timer_list watch_timer; /* Flag fetch timer. */ |
|
|
|
/* Info from SSIF cmd */ |
|
unsigned char max_xmit_msg_size; |
|
unsigned char max_recv_msg_size; |
|
bool cmd8_works; /* See test_multipart_messages() for details. */ |
|
unsigned int multi_support; |
|
int supports_pec; |
|
|
|
#define SSIF_NO_MULTI 0 |
|
#define SSIF_MULTI_2_PART 1 |
|
#define SSIF_MULTI_n_PART 2 |
|
unsigned char *multi_data; |
|
unsigned int multi_len; |
|
unsigned int multi_pos; |
|
|
|
atomic_t stats[SSIF_NUM_STATS]; |
|
}; |
|
|
|
#define ssif_inc_stat(ssif, stat) \ |
|
atomic_inc(&(ssif)->stats[SSIF_STAT_ ## stat]) |
|
#define ssif_get_stat(ssif, stat) \ |
|
((unsigned int) atomic_read(&(ssif)->stats[SSIF_STAT_ ## stat])) |
|
|
|
static bool initialized; |
|
static bool platform_registered; |
|
|
|
static void return_hosed_msg(struct ssif_info *ssif_info, |
|
struct ipmi_smi_msg *msg); |
|
static void start_next_msg(struct ssif_info *ssif_info, unsigned long *flags); |
|
static int start_send(struct ssif_info *ssif_info, |
|
unsigned char *data, |
|
unsigned int len); |
|
|
|
static unsigned long *ipmi_ssif_lock_cond(struct ssif_info *ssif_info, |
|
unsigned long *flags) |
|
__acquires(&ssif_info->lock) |
|
{ |
|
spin_lock_irqsave(&ssif_info->lock, *flags); |
|
return flags; |
|
} |
|
|
|
static void ipmi_ssif_unlock_cond(struct ssif_info *ssif_info, |
|
unsigned long *flags) |
|
__releases(&ssif_info->lock) |
|
{ |
|
spin_unlock_irqrestore(&ssif_info->lock, *flags); |
|
} |
|
|
|
static void deliver_recv_msg(struct ssif_info *ssif_info, |
|
struct ipmi_smi_msg *msg) |
|
{ |
|
if (msg->rsp_size < 0) { |
|
return_hosed_msg(ssif_info, msg); |
|
dev_err(&ssif_info->client->dev, |
|
"%s: Malformed message: rsp_size = %d\n", |
|
__func__, msg->rsp_size); |
|
} else { |
|
ipmi_smi_msg_received(ssif_info->intf, msg); |
|
} |
|
} |
|
|
|
static void return_hosed_msg(struct ssif_info *ssif_info, |
|
struct ipmi_smi_msg *msg) |
|
{ |
|
ssif_inc_stat(ssif_info, hosed); |
|
|
|
/* Make it a response */ |
|
msg->rsp[0] = msg->data[0] | 4; |
|
msg->rsp[1] = msg->data[1]; |
|
msg->rsp[2] = 0xFF; /* Unknown error. */ |
|
msg->rsp_size = 3; |
|
|
|
deliver_recv_msg(ssif_info, msg); |
|
} |
|
|
|
/* |
|
* Must be called with the message lock held. This will release the |
|
* message lock. Note that the caller will check SSIF_IDLE and start a |
|
* new operation, so there is no need to check for new messages to |
|
* start in here. |
|
*/ |
|
static void start_clear_flags(struct ssif_info *ssif_info, unsigned long *flags) |
|
{ |
|
unsigned char msg[3]; |
|
|
|
ssif_info->msg_flags &= ~WDT_PRE_TIMEOUT_INT; |
|
ssif_info->ssif_state = SSIF_CLEARING_FLAGS; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
|
|
/* Make sure the watchdog pre-timeout flag is not set at startup. */ |
|
msg[0] = (IPMI_NETFN_APP_REQUEST << 2); |
|
msg[1] = IPMI_CLEAR_MSG_FLAGS_CMD; |
|
msg[2] = WDT_PRE_TIMEOUT_INT; |
|
|
|
if (start_send(ssif_info, msg, 3) != 0) { |
|
/* Error, just go to normal state. */ |
|
ssif_info->ssif_state = SSIF_NORMAL; |
|
} |
|
} |
|
|
|
static void start_flag_fetch(struct ssif_info *ssif_info, unsigned long *flags) |
|
{ |
|
unsigned char mb[2]; |
|
|
|
ssif_info->req_flags = false; |
|
ssif_info->ssif_state = SSIF_GETTING_FLAGS; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
|
|
mb[0] = (IPMI_NETFN_APP_REQUEST << 2); |
|
mb[1] = IPMI_GET_MSG_FLAGS_CMD; |
|
if (start_send(ssif_info, mb, 2) != 0) |
|
ssif_info->ssif_state = SSIF_NORMAL; |
|
} |
|
|
|
static void check_start_send(struct ssif_info *ssif_info, unsigned long *flags, |
|
struct ipmi_smi_msg *msg) |
|
{ |
|
if (start_send(ssif_info, msg->data, msg->data_size) != 0) { |
|
unsigned long oflags; |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
ssif_info->curr_msg = NULL; |
|
ssif_info->ssif_state = SSIF_NORMAL; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
ipmi_free_smi_msg(msg); |
|
} |
|
} |
|
|
|
static void start_event_fetch(struct ssif_info *ssif_info, unsigned long *flags) |
|
{ |
|
struct ipmi_smi_msg *msg; |
|
|
|
ssif_info->req_events = false; |
|
|
|
msg = ipmi_alloc_smi_msg(); |
|
if (!msg) { |
|
ssif_info->ssif_state = SSIF_NORMAL; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
return; |
|
} |
|
|
|
ssif_info->curr_msg = msg; |
|
ssif_info->ssif_state = SSIF_GETTING_EVENTS; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
|
|
msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); |
|
msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD; |
|
msg->data_size = 2; |
|
|
|
check_start_send(ssif_info, flags, msg); |
|
} |
|
|
|
static void start_recv_msg_fetch(struct ssif_info *ssif_info, |
|
unsigned long *flags) |
|
{ |
|
struct ipmi_smi_msg *msg; |
|
|
|
msg = ipmi_alloc_smi_msg(); |
|
if (!msg) { |
|
ssif_info->ssif_state = SSIF_NORMAL; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
return; |
|
} |
|
|
|
ssif_info->curr_msg = msg; |
|
ssif_info->ssif_state = SSIF_GETTING_MESSAGES; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
|
|
msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); |
|
msg->data[1] = IPMI_GET_MSG_CMD; |
|
msg->data_size = 2; |
|
|
|
check_start_send(ssif_info, flags, msg); |
|
} |
|
|
|
/* |
|
* Must be called with the message lock held. This will release the |
|
* message lock. Note that the caller will check SSIF_IDLE and start a |
|
* new operation, so there is no need to check for new messages to |
|
* start in here. |
|
*/ |
|
static void handle_flags(struct ssif_info *ssif_info, unsigned long *flags) |
|
{ |
|
if (ssif_info->msg_flags & WDT_PRE_TIMEOUT_INT) { |
|
/* Watchdog pre-timeout */ |
|
ssif_inc_stat(ssif_info, watchdog_pretimeouts); |
|
start_clear_flags(ssif_info, flags); |
|
ipmi_smi_watchdog_pretimeout(ssif_info->intf); |
|
} else if (ssif_info->msg_flags & RECEIVE_MSG_AVAIL) |
|
/* Messages available. */ |
|
start_recv_msg_fetch(ssif_info, flags); |
|
else if (ssif_info->msg_flags & EVENT_MSG_BUFFER_FULL) |
|
/* Events available. */ |
|
start_event_fetch(ssif_info, flags); |
|
else { |
|
ssif_info->ssif_state = SSIF_NORMAL; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
} |
|
} |
|
|
|
static int ipmi_ssif_thread(void *data) |
|
{ |
|
struct ssif_info *ssif_info = data; |
|
|
|
while (!kthread_should_stop()) { |
|
int result; |
|
|
|
/* Wait for something to do */ |
|
result = wait_for_completion_interruptible( |
|
&ssif_info->wake_thread); |
|
if (ssif_info->stopping) |
|
break; |
|
if (result == -ERESTARTSYS) |
|
continue; |
|
init_completion(&ssif_info->wake_thread); |
|
|
|
if (ssif_info->i2c_read_write == I2C_SMBUS_WRITE) { |
|
result = i2c_smbus_write_block_data( |
|
ssif_info->client, ssif_info->i2c_command, |
|
ssif_info->i2c_data[0], |
|
ssif_info->i2c_data + 1); |
|
ssif_info->done_handler(ssif_info, result, NULL, 0); |
|
} else { |
|
result = i2c_smbus_read_block_data( |
|
ssif_info->client, ssif_info->i2c_command, |
|
ssif_info->i2c_data); |
|
if (result < 0) |
|
ssif_info->done_handler(ssif_info, result, |
|
NULL, 0); |
|
else |
|
ssif_info->done_handler(ssif_info, 0, |
|
ssif_info->i2c_data, |
|
result); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void ssif_i2c_send(struct ssif_info *ssif_info, |
|
ssif_i2c_done handler, |
|
int read_write, int command, |
|
unsigned char *data, unsigned int size) |
|
{ |
|
ssif_info->done_handler = handler; |
|
|
|
ssif_info->i2c_read_write = read_write; |
|
ssif_info->i2c_command = command; |
|
ssif_info->i2c_data = data; |
|
ssif_info->i2c_size = size; |
|
complete(&ssif_info->wake_thread); |
|
} |
|
|
|
|
|
static void msg_done_handler(struct ssif_info *ssif_info, int result, |
|
unsigned char *data, unsigned int len); |
|
|
|
static void start_get(struct ssif_info *ssif_info) |
|
{ |
|
ssif_info->rtc_us_timer = 0; |
|
ssif_info->multi_pos = 0; |
|
|
|
ssif_i2c_send(ssif_info, msg_done_handler, I2C_SMBUS_READ, |
|
SSIF_IPMI_RESPONSE, |
|
ssif_info->recv, I2C_SMBUS_BLOCK_DATA); |
|
} |
|
|
|
static void retry_timeout(struct timer_list *t) |
|
{ |
|
struct ssif_info *ssif_info = from_timer(ssif_info, t, retry_timer); |
|
unsigned long oflags, *flags; |
|
bool waiting; |
|
|
|
if (ssif_info->stopping) |
|
return; |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
waiting = ssif_info->waiting_alert; |
|
ssif_info->waiting_alert = false; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
|
|
if (waiting) |
|
start_get(ssif_info); |
|
} |
|
|
|
static void watch_timeout(struct timer_list *t) |
|
{ |
|
struct ssif_info *ssif_info = from_timer(ssif_info, t, watch_timer); |
|
unsigned long oflags, *flags; |
|
|
|
if (ssif_info->stopping) |
|
return; |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
if (ssif_info->watch_timeout) { |
|
mod_timer(&ssif_info->watch_timer, |
|
jiffies + ssif_info->watch_timeout); |
|
if (SSIF_IDLE(ssif_info)) { |
|
start_flag_fetch(ssif_info, flags); /* Releases lock */ |
|
return; |
|
} |
|
ssif_info->req_flags = true; |
|
} |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
} |
|
|
|
static void ssif_alert(struct i2c_client *client, enum i2c_alert_protocol type, |
|
unsigned int data) |
|
{ |
|
struct ssif_info *ssif_info = i2c_get_clientdata(client); |
|
unsigned long oflags, *flags; |
|
bool do_get = false; |
|
|
|
if (type != I2C_PROTOCOL_SMBUS_ALERT) |
|
return; |
|
|
|
ssif_inc_stat(ssif_info, alerts); |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
if (ssif_info->waiting_alert) { |
|
ssif_info->waiting_alert = false; |
|
del_timer(&ssif_info->retry_timer); |
|
do_get = true; |
|
} else if (ssif_info->curr_msg) { |
|
ssif_info->got_alert = true; |
|
} |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
if (do_get) |
|
start_get(ssif_info); |
|
} |
|
|
|
static int start_resend(struct ssif_info *ssif_info); |
|
|
|
static void msg_done_handler(struct ssif_info *ssif_info, int result, |
|
unsigned char *data, unsigned int len) |
|
{ |
|
struct ipmi_smi_msg *msg; |
|
unsigned long oflags, *flags; |
|
|
|
/* |
|
* We are single-threaded here, so no need for a lock until we |
|
* start messing with driver states or the queues. |
|
*/ |
|
|
|
if (result < 0) { |
|
ssif_info->retries_left--; |
|
if (ssif_info->retries_left > 0) { |
|
ssif_inc_stat(ssif_info, receive_retries); |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
ssif_info->waiting_alert = true; |
|
ssif_info->rtc_us_timer = SSIF_MSG_USEC; |
|
if (!ssif_info->stopping) |
|
mod_timer(&ssif_info->retry_timer, |
|
jiffies + SSIF_MSG_JIFFIES); |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
return; |
|
} |
|
|
|
ssif_inc_stat(ssif_info, receive_errors); |
|
|
|
if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) |
|
dev_dbg(&ssif_info->client->dev, |
|
"%s: Error %d\n", __func__, result); |
|
len = 0; |
|
goto continue_op; |
|
} |
|
|
|
if ((len > 1) && (ssif_info->multi_pos == 0) |
|
&& (data[0] == 0x00) && (data[1] == 0x01)) { |
|
/* Start of multi-part read. Start the next transaction. */ |
|
int i; |
|
|
|
ssif_inc_stat(ssif_info, received_message_parts); |
|
|
|
/* Remove the multi-part read marker. */ |
|
len -= 2; |
|
data += 2; |
|
for (i = 0; i < len; i++) |
|
ssif_info->data[i] = data[i]; |
|
ssif_info->multi_len = len; |
|
ssif_info->multi_pos = 1; |
|
|
|
ssif_i2c_send(ssif_info, msg_done_handler, I2C_SMBUS_READ, |
|
SSIF_IPMI_MULTI_PART_RESPONSE_MIDDLE, |
|
ssif_info->recv, I2C_SMBUS_BLOCK_DATA); |
|
return; |
|
} else if (ssif_info->multi_pos) { |
|
/* Middle of multi-part read. Start the next transaction. */ |
|
int i; |
|
unsigned char blocknum; |
|
|
|
if (len == 0) { |
|
result = -EIO; |
|
if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) |
|
dev_dbg(&ssif_info->client->dev, |
|
"Middle message with no data\n"); |
|
|
|
goto continue_op; |
|
} |
|
|
|
blocknum = data[0]; |
|
len--; |
|
data++; |
|
|
|
if (blocknum != 0xff && len != 31) { |
|
/* All blocks but the last must have 31 data bytes. */ |
|
result = -EIO; |
|
if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) |
|
dev_dbg(&ssif_info->client->dev, |
|
"Received middle message <31\n"); |
|
|
|
goto continue_op; |
|
} |
|
|
|
if (ssif_info->multi_len + len > IPMI_MAX_MSG_LENGTH) { |
|
/* Received message too big, abort the operation. */ |
|
result = -E2BIG; |
|
if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) |
|
dev_dbg(&ssif_info->client->dev, |
|
"Received message too big\n"); |
|
|
|
goto continue_op; |
|
} |
|
|
|
for (i = 0; i < len; i++) |
|
ssif_info->data[i + ssif_info->multi_len] = data[i]; |
|
ssif_info->multi_len += len; |
|
if (blocknum == 0xff) { |
|
/* End of read */ |
|
len = ssif_info->multi_len; |
|
data = ssif_info->data; |
|
} else if (blocknum + 1 != ssif_info->multi_pos) { |
|
/* |
|
* Out of sequence block, just abort. Block |
|
* numbers start at zero for the second block, |
|
* but multi_pos starts at one, so the +1. |
|
*/ |
|
if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) |
|
dev_dbg(&ssif_info->client->dev, |
|
"Received message out of sequence, expected %u, got %u\n", |
|
ssif_info->multi_pos - 1, blocknum); |
|
result = -EIO; |
|
} else { |
|
ssif_inc_stat(ssif_info, received_message_parts); |
|
|
|
ssif_info->multi_pos++; |
|
|
|
ssif_i2c_send(ssif_info, msg_done_handler, |
|
I2C_SMBUS_READ, |
|
SSIF_IPMI_MULTI_PART_RESPONSE_MIDDLE, |
|
ssif_info->recv, |
|
I2C_SMBUS_BLOCK_DATA); |
|
return; |
|
} |
|
} |
|
|
|
continue_op: |
|
if (result < 0) { |
|
ssif_inc_stat(ssif_info, receive_errors); |
|
} else { |
|
ssif_inc_stat(ssif_info, received_messages); |
|
ssif_inc_stat(ssif_info, received_message_parts); |
|
} |
|
|
|
if (ssif_info->ssif_debug & SSIF_DEBUG_STATE) |
|
dev_dbg(&ssif_info->client->dev, |
|
"DONE 1: state = %d, result=%d\n", |
|
ssif_info->ssif_state, result); |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
msg = ssif_info->curr_msg; |
|
if (msg) { |
|
if (data) { |
|
if (len > IPMI_MAX_MSG_LENGTH) |
|
len = IPMI_MAX_MSG_LENGTH; |
|
memcpy(msg->rsp, data, len); |
|
} else { |
|
len = 0; |
|
} |
|
msg->rsp_size = len; |
|
ssif_info->curr_msg = NULL; |
|
} |
|
|
|
switch (ssif_info->ssif_state) { |
|
case SSIF_NORMAL: |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
if (!msg) |
|
break; |
|
|
|
if (result < 0) |
|
return_hosed_msg(ssif_info, msg); |
|
else |
|
deliver_recv_msg(ssif_info, msg); |
|
break; |
|
|
|
case SSIF_GETTING_FLAGS: |
|
/* We got the flags from the SSIF, now handle them. */ |
|
if ((result < 0) || (len < 4) || (data[2] != 0)) { |
|
/* |
|
* Error fetching flags, or invalid length, |
|
* just give up for now. |
|
*/ |
|
ssif_info->ssif_state = SSIF_NORMAL; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
dev_warn(&ssif_info->client->dev, |
|
"Error getting flags: %d %d, %x\n", |
|
result, len, (len >= 3) ? data[2] : 0); |
|
} else if (data[0] != (IPMI_NETFN_APP_REQUEST | 1) << 2 |
|
|| data[1] != IPMI_GET_MSG_FLAGS_CMD) { |
|
/* |
|
* Don't abort here, maybe it was a queued |
|
* response to a previous command. |
|
*/ |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
dev_warn(&ssif_info->client->dev, |
|
"Invalid response getting flags: %x %x\n", |
|
data[0], data[1]); |
|
} else { |
|
ssif_inc_stat(ssif_info, flag_fetches); |
|
ssif_info->msg_flags = data[3]; |
|
handle_flags(ssif_info, flags); |
|
} |
|
break; |
|
|
|
case SSIF_CLEARING_FLAGS: |
|
/* We cleared the flags. */ |
|
if ((result < 0) || (len < 3) || (data[2] != 0)) { |
|
/* Error clearing flags */ |
|
dev_warn(&ssif_info->client->dev, |
|
"Error clearing flags: %d %d, %x\n", |
|
result, len, (len >= 3) ? data[2] : 0); |
|
} else if (data[0] != (IPMI_NETFN_APP_REQUEST | 1) << 2 |
|
|| data[1] != IPMI_CLEAR_MSG_FLAGS_CMD) { |
|
dev_warn(&ssif_info->client->dev, |
|
"Invalid response clearing flags: %x %x\n", |
|
data[0], data[1]); |
|
} |
|
ssif_info->ssif_state = SSIF_NORMAL; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
break; |
|
|
|
case SSIF_GETTING_EVENTS: |
|
if ((result < 0) || (len < 3) || (msg->rsp[2] != 0)) { |
|
/* Error getting event, probably done. */ |
|
msg->done(msg); |
|
|
|
/* Take off the event flag. */ |
|
ssif_info->msg_flags &= ~EVENT_MSG_BUFFER_FULL; |
|
handle_flags(ssif_info, flags); |
|
} else if (msg->rsp[0] != (IPMI_NETFN_APP_REQUEST | 1) << 2 |
|
|| msg->rsp[1] != IPMI_READ_EVENT_MSG_BUFFER_CMD) { |
|
dev_warn(&ssif_info->client->dev, |
|
"Invalid response getting events: %x %x\n", |
|
msg->rsp[0], msg->rsp[1]); |
|
msg->done(msg); |
|
/* Take off the event flag. */ |
|
ssif_info->msg_flags &= ~EVENT_MSG_BUFFER_FULL; |
|
handle_flags(ssif_info, flags); |
|
} else { |
|
handle_flags(ssif_info, flags); |
|
ssif_inc_stat(ssif_info, events); |
|
deliver_recv_msg(ssif_info, msg); |
|
} |
|
break; |
|
|
|
case SSIF_GETTING_MESSAGES: |
|
if ((result < 0) || (len < 3) || (msg->rsp[2] != 0)) { |
|
/* Error getting event, probably done. */ |
|
msg->done(msg); |
|
|
|
/* Take off the msg flag. */ |
|
ssif_info->msg_flags &= ~RECEIVE_MSG_AVAIL; |
|
handle_flags(ssif_info, flags); |
|
} else if (msg->rsp[0] != (IPMI_NETFN_APP_REQUEST | 1) << 2 |
|
|| msg->rsp[1] != IPMI_GET_MSG_CMD) { |
|
dev_warn(&ssif_info->client->dev, |
|
"Invalid response clearing flags: %x %x\n", |
|
msg->rsp[0], msg->rsp[1]); |
|
msg->done(msg); |
|
|
|
/* Take off the msg flag. */ |
|
ssif_info->msg_flags &= ~RECEIVE_MSG_AVAIL; |
|
handle_flags(ssif_info, flags); |
|
} else { |
|
ssif_inc_stat(ssif_info, incoming_messages); |
|
handle_flags(ssif_info, flags); |
|
deliver_recv_msg(ssif_info, msg); |
|
} |
|
break; |
|
} |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
if (SSIF_IDLE(ssif_info) && !ssif_info->stopping) { |
|
if (ssif_info->req_events) |
|
start_event_fetch(ssif_info, flags); |
|
else if (ssif_info->req_flags) |
|
start_flag_fetch(ssif_info, flags); |
|
else |
|
start_next_msg(ssif_info, flags); |
|
} else |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
|
|
if (ssif_info->ssif_debug & SSIF_DEBUG_STATE) |
|
dev_dbg(&ssif_info->client->dev, |
|
"DONE 2: state = %d.\n", ssif_info->ssif_state); |
|
} |
|
|
|
static void msg_written_handler(struct ssif_info *ssif_info, int result, |
|
unsigned char *data, unsigned int len) |
|
{ |
|
/* We are single-threaded here, so no need for a lock. */ |
|
if (result < 0) { |
|
ssif_info->retries_left--; |
|
if (ssif_info->retries_left > 0) { |
|
if (!start_resend(ssif_info)) { |
|
ssif_inc_stat(ssif_info, send_retries); |
|
return; |
|
} |
|
/* request failed, just return the error. */ |
|
ssif_inc_stat(ssif_info, send_errors); |
|
|
|
if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) |
|
dev_dbg(&ssif_info->client->dev, |
|
"%s: Out of retries\n", __func__); |
|
msg_done_handler(ssif_info, -EIO, NULL, 0); |
|
return; |
|
} |
|
|
|
ssif_inc_stat(ssif_info, send_errors); |
|
|
|
/* |
|
* Got an error on transmit, let the done routine |
|
* handle it. |
|
*/ |
|
if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) |
|
dev_dbg(&ssif_info->client->dev, |
|
"%s: Error %d\n", __func__, result); |
|
|
|
msg_done_handler(ssif_info, result, NULL, 0); |
|
return; |
|
} |
|
|
|
if (ssif_info->multi_data) { |
|
/* |
|
* In the middle of a multi-data write. See the comment |
|
* in the SSIF_MULTI_n_PART case in the probe function |
|
* for details on the intricacies of this. |
|
*/ |
|
int left, to_write; |
|
unsigned char *data_to_send; |
|
unsigned char cmd; |
|
|
|
ssif_inc_stat(ssif_info, sent_messages_parts); |
|
|
|
left = ssif_info->multi_len - ssif_info->multi_pos; |
|
to_write = left; |
|
if (to_write > 32) |
|
to_write = 32; |
|
/* Length byte. */ |
|
ssif_info->multi_data[ssif_info->multi_pos] = to_write; |
|
data_to_send = ssif_info->multi_data + ssif_info->multi_pos; |
|
ssif_info->multi_pos += to_write; |
|
cmd = SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE; |
|
if (ssif_info->cmd8_works) { |
|
if (left == to_write) { |
|
cmd = SSIF_IPMI_MULTI_PART_REQUEST_END; |
|
ssif_info->multi_data = NULL; |
|
} |
|
} else if (to_write < 32) { |
|
ssif_info->multi_data = NULL; |
|
} |
|
|
|
ssif_i2c_send(ssif_info, msg_written_handler, |
|
I2C_SMBUS_WRITE, cmd, |
|
data_to_send, I2C_SMBUS_BLOCK_DATA); |
|
} else { |
|
/* Ready to request the result. */ |
|
unsigned long oflags, *flags; |
|
|
|
ssif_inc_stat(ssif_info, sent_messages); |
|
ssif_inc_stat(ssif_info, sent_messages_parts); |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
if (ssif_info->got_alert) { |
|
/* The result is already ready, just start it. */ |
|
ssif_info->got_alert = false; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
start_get(ssif_info); |
|
} else { |
|
/* Wait a jiffie then request the next message */ |
|
ssif_info->waiting_alert = true; |
|
ssif_info->retries_left = SSIF_RECV_RETRIES; |
|
ssif_info->rtc_us_timer = SSIF_MSG_PART_USEC; |
|
if (!ssif_info->stopping) |
|
mod_timer(&ssif_info->retry_timer, |
|
jiffies + SSIF_MSG_PART_JIFFIES); |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
} |
|
} |
|
} |
|
|
|
static int start_resend(struct ssif_info *ssif_info) |
|
{ |
|
int command; |
|
|
|
ssif_info->got_alert = false; |
|
|
|
if (ssif_info->data_len > 32) { |
|
command = SSIF_IPMI_MULTI_PART_REQUEST_START; |
|
ssif_info->multi_data = ssif_info->data; |
|
ssif_info->multi_len = ssif_info->data_len; |
|
/* |
|
* Subtle thing, this is 32, not 33, because we will |
|
* overwrite the thing at position 32 (which was just |
|
* transmitted) with the new length. |
|
*/ |
|
ssif_info->multi_pos = 32; |
|
ssif_info->data[0] = 32; |
|
} else { |
|
ssif_info->multi_data = NULL; |
|
command = SSIF_IPMI_REQUEST; |
|
ssif_info->data[0] = ssif_info->data_len; |
|
} |
|
|
|
ssif_i2c_send(ssif_info, msg_written_handler, I2C_SMBUS_WRITE, |
|
command, ssif_info->data, I2C_SMBUS_BLOCK_DATA); |
|
return 0; |
|
} |
|
|
|
static int start_send(struct ssif_info *ssif_info, |
|
unsigned char *data, |
|
unsigned int len) |
|
{ |
|
if (len > IPMI_MAX_MSG_LENGTH) |
|
return -E2BIG; |
|
if (len > ssif_info->max_xmit_msg_size) |
|
return -E2BIG; |
|
|
|
ssif_info->retries_left = SSIF_SEND_RETRIES; |
|
memcpy(ssif_info->data + 1, data, len); |
|
ssif_info->data_len = len; |
|
return start_resend(ssif_info); |
|
} |
|
|
|
/* Must be called with the message lock held. */ |
|
static void start_next_msg(struct ssif_info *ssif_info, unsigned long *flags) |
|
{ |
|
struct ipmi_smi_msg *msg; |
|
unsigned long oflags; |
|
|
|
restart: |
|
if (!SSIF_IDLE(ssif_info)) { |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
return; |
|
} |
|
|
|
if (!ssif_info->waiting_msg) { |
|
ssif_info->curr_msg = NULL; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
} else { |
|
int rv; |
|
|
|
ssif_info->curr_msg = ssif_info->waiting_msg; |
|
ssif_info->waiting_msg = NULL; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
rv = start_send(ssif_info, |
|
ssif_info->curr_msg->data, |
|
ssif_info->curr_msg->data_size); |
|
if (rv) { |
|
msg = ssif_info->curr_msg; |
|
ssif_info->curr_msg = NULL; |
|
return_hosed_msg(ssif_info, msg); |
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
goto restart; |
|
} |
|
} |
|
} |
|
|
|
static void sender(void *send_info, |
|
struct ipmi_smi_msg *msg) |
|
{ |
|
struct ssif_info *ssif_info = (struct ssif_info *) send_info; |
|
unsigned long oflags, *flags; |
|
|
|
BUG_ON(ssif_info->waiting_msg); |
|
ssif_info->waiting_msg = msg; |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
start_next_msg(ssif_info, flags); |
|
|
|
if (ssif_info->ssif_debug & SSIF_DEBUG_TIMING) { |
|
struct timespec64 t; |
|
|
|
ktime_get_real_ts64(&t); |
|
dev_dbg(&ssif_info->client->dev, |
|
"**Enqueue %02x %02x: %lld.%6.6ld\n", |
|
msg->data[0], msg->data[1], |
|
(long long)t.tv_sec, (long)t.tv_nsec / NSEC_PER_USEC); |
|
} |
|
} |
|
|
|
static int get_smi_info(void *send_info, struct ipmi_smi_info *data) |
|
{ |
|
struct ssif_info *ssif_info = send_info; |
|
|
|
data->addr_src = ssif_info->addr_source; |
|
data->dev = &ssif_info->client->dev; |
|
data->addr_info = ssif_info->addr_info; |
|
get_device(data->dev); |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* Upper layer wants us to request events. |
|
*/ |
|
static void request_events(void *send_info) |
|
{ |
|
struct ssif_info *ssif_info = (struct ssif_info *) send_info; |
|
unsigned long oflags, *flags; |
|
|
|
if (!ssif_info->has_event_buffer) |
|
return; |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
ssif_info->req_events = true; |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
} |
|
|
|
/* |
|
* Upper layer is changing the flag saying whether we need to request |
|
* flags periodically or not. |
|
*/ |
|
static void ssif_set_need_watch(void *send_info, unsigned int watch_mask) |
|
{ |
|
struct ssif_info *ssif_info = (struct ssif_info *) send_info; |
|
unsigned long oflags, *flags; |
|
long timeout = 0; |
|
|
|
if (watch_mask & IPMI_WATCH_MASK_CHECK_MESSAGES) |
|
timeout = SSIF_WATCH_MSG_TIMEOUT; |
|
else if (watch_mask) |
|
timeout = SSIF_WATCH_WATCHDOG_TIMEOUT; |
|
|
|
flags = ipmi_ssif_lock_cond(ssif_info, &oflags); |
|
if (timeout != ssif_info->watch_timeout) { |
|
ssif_info->watch_timeout = timeout; |
|
if (ssif_info->watch_timeout) |
|
mod_timer(&ssif_info->watch_timer, |
|
jiffies + ssif_info->watch_timeout); |
|
} |
|
ipmi_ssif_unlock_cond(ssif_info, flags); |
|
} |
|
|
|
static int ssif_start_processing(void *send_info, |
|
struct ipmi_smi *intf) |
|
{ |
|
struct ssif_info *ssif_info = send_info; |
|
|
|
ssif_info->intf = intf; |
|
|
|
return 0; |
|
} |
|
|
|
#define MAX_SSIF_BMCS 4 |
|
|
|
static unsigned short addr[MAX_SSIF_BMCS]; |
|
static int num_addrs; |
|
module_param_array(addr, ushort, &num_addrs, 0); |
|
MODULE_PARM_DESC(addr, "The addresses to scan for IPMI BMCs on the SSIFs."); |
|
|
|
static char *adapter_name[MAX_SSIF_BMCS]; |
|
static int num_adapter_names; |
|
module_param_array(adapter_name, charp, &num_adapter_names, 0); |
|
MODULE_PARM_DESC(adapter_name, "The string name of the I2C device that has the BMC. By default all devices are scanned."); |
|
|
|
static int slave_addrs[MAX_SSIF_BMCS]; |
|
static int num_slave_addrs; |
|
module_param_array(slave_addrs, int, &num_slave_addrs, 0); |
|
MODULE_PARM_DESC(slave_addrs, |
|
"The default IPMB slave address for the controller."); |
|
|
|
static bool alerts_broken; |
|
module_param(alerts_broken, bool, 0); |
|
MODULE_PARM_DESC(alerts_broken, "Don't enable alerts for the controller."); |
|
|
|
/* |
|
* Bit 0 enables message debugging, bit 1 enables state debugging, and |
|
* bit 2 enables timing debugging. This is an array indexed by |
|
* interface number" |
|
*/ |
|
static int dbg[MAX_SSIF_BMCS]; |
|
static int num_dbg; |
|
module_param_array(dbg, int, &num_dbg, 0); |
|
MODULE_PARM_DESC(dbg, "Turn on debugging."); |
|
|
|
static bool ssif_dbg_probe; |
|
module_param_named(dbg_probe, ssif_dbg_probe, bool, 0); |
|
MODULE_PARM_DESC(dbg_probe, "Enable debugging of probing of adapters."); |
|
|
|
static bool ssif_tryacpi = true; |
|
module_param_named(tryacpi, ssif_tryacpi, bool, 0); |
|
MODULE_PARM_DESC(tryacpi, "Setting this to zero will disable the default scan of the interfaces identified via ACPI"); |
|
|
|
static bool ssif_trydmi = true; |
|
module_param_named(trydmi, ssif_trydmi, bool, 0); |
|
MODULE_PARM_DESC(trydmi, "Setting this to zero will disable the default scan of the interfaces identified via DMI (SMBIOS)"); |
|
|
|
static DEFINE_MUTEX(ssif_infos_mutex); |
|
static LIST_HEAD(ssif_infos); |
|
|
|
#define IPMI_SSIF_ATTR(name) \ |
|
static ssize_t ipmi_##name##_show(struct device *dev, \ |
|
struct device_attribute *attr, \ |
|
char *buf) \ |
|
{ \ |
|
struct ssif_info *ssif_info = dev_get_drvdata(dev); \ |
|
\ |
|
return snprintf(buf, 10, "%u\n", ssif_get_stat(ssif_info, name));\ |
|
} \ |
|
static DEVICE_ATTR(name, S_IRUGO, ipmi_##name##_show, NULL) |
|
|
|
static ssize_t ipmi_type_show(struct device *dev, |
|
struct device_attribute *attr, |
|
char *buf) |
|
{ |
|
return snprintf(buf, 10, "ssif\n"); |
|
} |
|
static DEVICE_ATTR(type, S_IRUGO, ipmi_type_show, NULL); |
|
|
|
IPMI_SSIF_ATTR(sent_messages); |
|
IPMI_SSIF_ATTR(sent_messages_parts); |
|
IPMI_SSIF_ATTR(send_retries); |
|
IPMI_SSIF_ATTR(send_errors); |
|
IPMI_SSIF_ATTR(received_messages); |
|
IPMI_SSIF_ATTR(received_message_parts); |
|
IPMI_SSIF_ATTR(receive_retries); |
|
IPMI_SSIF_ATTR(receive_errors); |
|
IPMI_SSIF_ATTR(flag_fetches); |
|
IPMI_SSIF_ATTR(hosed); |
|
IPMI_SSIF_ATTR(events); |
|
IPMI_SSIF_ATTR(watchdog_pretimeouts); |
|
IPMI_SSIF_ATTR(alerts); |
|
|
|
static struct attribute *ipmi_ssif_dev_attrs[] = { |
|
&dev_attr_type.attr, |
|
&dev_attr_sent_messages.attr, |
|
&dev_attr_sent_messages_parts.attr, |
|
&dev_attr_send_retries.attr, |
|
&dev_attr_send_errors.attr, |
|
&dev_attr_received_messages.attr, |
|
&dev_attr_received_message_parts.attr, |
|
&dev_attr_receive_retries.attr, |
|
&dev_attr_receive_errors.attr, |
|
&dev_attr_flag_fetches.attr, |
|
&dev_attr_hosed.attr, |
|
&dev_attr_events.attr, |
|
&dev_attr_watchdog_pretimeouts.attr, |
|
&dev_attr_alerts.attr, |
|
NULL |
|
}; |
|
|
|
static const struct attribute_group ipmi_ssif_dev_attr_group = { |
|
.attrs = ipmi_ssif_dev_attrs, |
|
}; |
|
|
|
static void shutdown_ssif(void *send_info) |
|
{ |
|
struct ssif_info *ssif_info = send_info; |
|
|
|
device_remove_group(&ssif_info->client->dev, &ipmi_ssif_dev_attr_group); |
|
dev_set_drvdata(&ssif_info->client->dev, NULL); |
|
|
|
/* make sure the driver is not looking for flags any more. */ |
|
while (ssif_info->ssif_state != SSIF_NORMAL) |
|
schedule_timeout(1); |
|
|
|
ssif_info->stopping = true; |
|
del_timer_sync(&ssif_info->watch_timer); |
|
del_timer_sync(&ssif_info->retry_timer); |
|
if (ssif_info->thread) { |
|
complete(&ssif_info->wake_thread); |
|
kthread_stop(ssif_info->thread); |
|
} |
|
} |
|
|
|
static int ssif_remove(struct i2c_client *client) |
|
{ |
|
struct ssif_info *ssif_info = i2c_get_clientdata(client); |
|
struct ssif_addr_info *addr_info; |
|
|
|
if (!ssif_info) |
|
return 0; |
|
|
|
/* |
|
* After this point, we won't deliver anything asychronously |
|
* to the message handler. We can unregister ourself. |
|
*/ |
|
ipmi_unregister_smi(ssif_info->intf); |
|
|
|
list_for_each_entry(addr_info, &ssif_infos, link) { |
|
if (addr_info->client == client) { |
|
addr_info->client = NULL; |
|
break; |
|
} |
|
} |
|
|
|
kfree(ssif_info); |
|
|
|
return 0; |
|
} |
|
|
|
static int read_response(struct i2c_client *client, unsigned char *resp) |
|
{ |
|
int ret = -ENODEV, retry_cnt = SSIF_RECV_RETRIES; |
|
|
|
while (retry_cnt > 0) { |
|
ret = i2c_smbus_read_block_data(client, SSIF_IPMI_RESPONSE, |
|
resp); |
|
if (ret > 0) |
|
break; |
|
msleep(SSIF_MSG_MSEC); |
|
retry_cnt--; |
|
if (retry_cnt <= 0) |
|
break; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int do_cmd(struct i2c_client *client, int len, unsigned char *msg, |
|
int *resp_len, unsigned char *resp) |
|
{ |
|
int retry_cnt; |
|
int ret; |
|
|
|
retry_cnt = SSIF_SEND_RETRIES; |
|
retry1: |
|
ret = i2c_smbus_write_block_data(client, SSIF_IPMI_REQUEST, len, msg); |
|
if (ret) { |
|
retry_cnt--; |
|
if (retry_cnt > 0) |
|
goto retry1; |
|
return -ENODEV; |
|
} |
|
|
|
ret = read_response(client, resp); |
|
if (ret > 0) { |
|
/* Validate that the response is correct. */ |
|
if (ret < 3 || |
|
(resp[0] != (msg[0] | (1 << 2))) || |
|
(resp[1] != msg[1])) |
|
ret = -EINVAL; |
|
else if (ret > IPMI_MAX_MSG_LENGTH) { |
|
ret = -E2BIG; |
|
} else { |
|
*resp_len = ret; |
|
ret = 0; |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int ssif_detect(struct i2c_client *client, struct i2c_board_info *info) |
|
{ |
|
unsigned char *resp; |
|
unsigned char msg[3]; |
|
int rv; |
|
int len; |
|
|
|
resp = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL); |
|
if (!resp) |
|
return -ENOMEM; |
|
|
|
/* Do a Get Device ID command, since it is required. */ |
|
msg[0] = IPMI_NETFN_APP_REQUEST << 2; |
|
msg[1] = IPMI_GET_DEVICE_ID_CMD; |
|
rv = do_cmd(client, 2, msg, &len, resp); |
|
if (rv) |
|
rv = -ENODEV; |
|
else |
|
strlcpy(info->type, DEVICE_NAME, I2C_NAME_SIZE); |
|
kfree(resp); |
|
return rv; |
|
} |
|
|
|
static int strcmp_nospace(char *s1, char *s2) |
|
{ |
|
while (*s1 && *s2) { |
|
while (isspace(*s1)) |
|
s1++; |
|
while (isspace(*s2)) |
|
s2++; |
|
if (*s1 > *s2) |
|
return 1; |
|
if (*s1 < *s2) |
|
return -1; |
|
s1++; |
|
s2++; |
|
} |
|
return 0; |
|
} |
|
|
|
static struct ssif_addr_info *ssif_info_find(unsigned short addr, |
|
char *adapter_name, |
|
bool match_null_name) |
|
{ |
|
struct ssif_addr_info *info, *found = NULL; |
|
|
|
restart: |
|
list_for_each_entry(info, &ssif_infos, link) { |
|
if (info->binfo.addr == addr) { |
|
if (info->addr_src == SI_SMBIOS) |
|
info->adapter_name = kstrdup(adapter_name, |
|
GFP_KERNEL); |
|
|
|
if (info->adapter_name || adapter_name) { |
|
if (!info->adapter_name != !adapter_name) { |
|
/* One is NULL and one is not */ |
|
continue; |
|
} |
|
if (adapter_name && |
|
strcmp_nospace(info->adapter_name, |
|
adapter_name)) |
|
/* Names do not match */ |
|
continue; |
|
} |
|
found = info; |
|
break; |
|
} |
|
} |
|
|
|
if (!found && match_null_name) { |
|
/* Try to get an exact match first, then try with a NULL name */ |
|
adapter_name = NULL; |
|
match_null_name = false; |
|
goto restart; |
|
} |
|
|
|
return found; |
|
} |
|
|
|
static bool check_acpi(struct ssif_info *ssif_info, struct device *dev) |
|
{ |
|
#ifdef CONFIG_ACPI |
|
acpi_handle acpi_handle; |
|
|
|
acpi_handle = ACPI_HANDLE(dev); |
|
if (acpi_handle) { |
|
ssif_info->addr_source = SI_ACPI; |
|
ssif_info->addr_info.acpi_info.acpi_handle = acpi_handle; |
|
request_module("acpi_ipmi"); |
|
return true; |
|
} |
|
#endif |
|
return false; |
|
} |
|
|
|
static int find_slave_address(struct i2c_client *client, int slave_addr) |
|
{ |
|
#ifdef CONFIG_IPMI_DMI_DECODE |
|
if (!slave_addr) |
|
slave_addr = ipmi_dmi_get_slave_addr( |
|
SI_TYPE_INVALID, |
|
i2c_adapter_id(client->adapter), |
|
client->addr); |
|
#endif |
|
|
|
return slave_addr; |
|
} |
|
|
|
static int start_multipart_test(struct i2c_client *client, |
|
unsigned char *msg, bool do_middle) |
|
{ |
|
int retry_cnt = SSIF_SEND_RETRIES, ret; |
|
|
|
retry_write: |
|
ret = i2c_smbus_write_block_data(client, |
|
SSIF_IPMI_MULTI_PART_REQUEST_START, |
|
32, msg); |
|
if (ret) { |
|
retry_cnt--; |
|
if (retry_cnt > 0) |
|
goto retry_write; |
|
dev_err(&client->dev, "Could not write multi-part start, though the BMC said it could handle it. Just limit sends to one part.\n"); |
|
return ret; |
|
} |
|
|
|
if (!do_middle) |
|
return 0; |
|
|
|
ret = i2c_smbus_write_block_data(client, |
|
SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE, |
|
32, msg + 32); |
|
if (ret) { |
|
dev_err(&client->dev, "Could not write multi-part middle, though the BMC said it could handle it. Just limit sends to one part.\n"); |
|
return ret; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void test_multipart_messages(struct i2c_client *client, |
|
struct ssif_info *ssif_info, |
|
unsigned char *resp) |
|
{ |
|
unsigned char msg[65]; |
|
int ret; |
|
bool do_middle; |
|
|
|
if (ssif_info->max_xmit_msg_size <= 32) |
|
return; |
|
|
|
do_middle = ssif_info->max_xmit_msg_size > 63; |
|
|
|
memset(msg, 0, sizeof(msg)); |
|
msg[0] = IPMI_NETFN_APP_REQUEST << 2; |
|
msg[1] = IPMI_GET_DEVICE_ID_CMD; |
|
|
|
/* |
|
* The specification is all messed up dealing with sending |
|
* multi-part messages. Per what the specification says, it |
|
* is impossible to send a message that is a multiple of 32 |
|
* bytes, except for 32 itself. It talks about a "start" |
|
* transaction (cmd=6) that must be 32 bytes, "middle" |
|
* transaction (cmd=7) that must be 32 bytes, and an "end" |
|
* transaction. The "end" transaction is shown as cmd=7 in |
|
* the text, but if that's the case there is no way to |
|
* differentiate between a middle and end part except the |
|
* length being less than 32. But there is a table at the far |
|
* end of the section (that I had never noticed until someone |
|
* pointed it out to me) that mentions it as cmd=8. |
|
* |
|
* After some thought, I think the example is wrong and the |
|
* end transaction should be cmd=8. But some systems don't |
|
* implement cmd=8, they use a zero-length end transaction, |
|
* even though that violates the SMBus specification. |
|
* |
|
* So, to work around this, this code tests if cmd=8 works. |
|
* If it does, then we use that. If not, it tests zero- |
|
* byte end transactions. If that works, good. If not, |
|
* we only allow 63-byte transactions max. |
|
*/ |
|
|
|
ret = start_multipart_test(client, msg, do_middle); |
|
if (ret) |
|
goto out_no_multi_part; |
|
|
|
ret = i2c_smbus_write_block_data(client, |
|
SSIF_IPMI_MULTI_PART_REQUEST_END, |
|
1, msg + 64); |
|
|
|
if (!ret) |
|
ret = read_response(client, resp); |
|
|
|
if (ret > 0) { |
|
/* End transactions work, we are good. */ |
|
ssif_info->cmd8_works = true; |
|
return; |
|
} |
|
|
|
ret = start_multipart_test(client, msg, do_middle); |
|
if (ret) { |
|
dev_err(&client->dev, "Second multipart test failed.\n"); |
|
goto out_no_multi_part; |
|
} |
|
|
|
ret = i2c_smbus_write_block_data(client, |
|
SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE, |
|
0, msg + 64); |
|
if (!ret) |
|
ret = read_response(client, resp); |
|
if (ret > 0) |
|
/* Zero-size end parts work, use those. */ |
|
return; |
|
|
|
/* Limit to 63 bytes and use a short middle command to mark the end. */ |
|
if (ssif_info->max_xmit_msg_size > 63) |
|
ssif_info->max_xmit_msg_size = 63; |
|
return; |
|
|
|
out_no_multi_part: |
|
ssif_info->max_xmit_msg_size = 32; |
|
return; |
|
} |
|
|
|
/* |
|
* Global enables we care about. |
|
*/ |
|
#define GLOBAL_ENABLES_MASK (IPMI_BMC_EVT_MSG_BUFF | IPMI_BMC_RCV_MSG_INTR | \ |
|
IPMI_BMC_EVT_MSG_INTR) |
|
|
|
static void ssif_remove_dup(struct i2c_client *client) |
|
{ |
|
struct ssif_info *ssif_info = i2c_get_clientdata(client); |
|
|
|
ipmi_unregister_smi(ssif_info->intf); |
|
kfree(ssif_info); |
|
} |
|
|
|
static int ssif_add_infos(struct i2c_client *client) |
|
{ |
|
struct ssif_addr_info *info; |
|
|
|
info = kzalloc(sizeof(*info), GFP_KERNEL); |
|
if (!info) |
|
return -ENOMEM; |
|
info->addr_src = SI_ACPI; |
|
info->client = client; |
|
info->adapter_name = kstrdup(client->adapter->name, GFP_KERNEL); |
|
info->binfo.addr = client->addr; |
|
list_add_tail(&info->link, &ssif_infos); |
|
return 0; |
|
} |
|
|
|
/* |
|
* Prefer ACPI over SMBIOS, if both are available. |
|
* So if we get an ACPI interface and have already registered a SMBIOS |
|
* interface at the same address, remove the SMBIOS and add the ACPI one. |
|
*/ |
|
static int ssif_check_and_remove(struct i2c_client *client, |
|
struct ssif_info *ssif_info) |
|
{ |
|
struct ssif_addr_info *info; |
|
|
|
list_for_each_entry(info, &ssif_infos, link) { |
|
if (!info->client) |
|
return 0; |
|
if (!strcmp(info->adapter_name, client->adapter->name) && |
|
info->binfo.addr == client->addr) { |
|
if (info->addr_src == SI_ACPI) |
|
return -EEXIST; |
|
|
|
if (ssif_info->addr_source == SI_ACPI && |
|
info->addr_src == SI_SMBIOS) { |
|
dev_info(&client->dev, |
|
"Removing %s-specified SSIF interface in favor of ACPI\n", |
|
ipmi_addr_src_to_str(info->addr_src)); |
|
ssif_remove_dup(info->client); |
|
return 0; |
|
} |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
static int ssif_probe(struct i2c_client *client, const struct i2c_device_id *id) |
|
{ |
|
unsigned char msg[3]; |
|
unsigned char *resp; |
|
struct ssif_info *ssif_info; |
|
int rv = 0; |
|
int len; |
|
int i; |
|
u8 slave_addr = 0; |
|
struct ssif_addr_info *addr_info = NULL; |
|
|
|
mutex_lock(&ssif_infos_mutex); |
|
resp = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL); |
|
if (!resp) { |
|
mutex_unlock(&ssif_infos_mutex); |
|
return -ENOMEM; |
|
} |
|
|
|
ssif_info = kzalloc(sizeof(*ssif_info), GFP_KERNEL); |
|
if (!ssif_info) { |
|
kfree(resp); |
|
mutex_unlock(&ssif_infos_mutex); |
|
return -ENOMEM; |
|
} |
|
|
|
if (!check_acpi(ssif_info, &client->dev)) { |
|
addr_info = ssif_info_find(client->addr, client->adapter->name, |
|
true); |
|
if (!addr_info) { |
|
/* Must have come in through sysfs. */ |
|
ssif_info->addr_source = SI_HOTMOD; |
|
} else { |
|
ssif_info->addr_source = addr_info->addr_src; |
|
ssif_info->ssif_debug = addr_info->debug; |
|
ssif_info->addr_info = addr_info->addr_info; |
|
addr_info->client = client; |
|
slave_addr = addr_info->slave_addr; |
|
} |
|
} |
|
|
|
rv = ssif_check_and_remove(client, ssif_info); |
|
/* If rv is 0 and addr source is not SI_ACPI, continue probing */ |
|
if (!rv && ssif_info->addr_source == SI_ACPI) { |
|
rv = ssif_add_infos(client); |
|
if (rv) { |
|
dev_err(&client->dev, "Out of memory!, exiting ..\n"); |
|
goto out; |
|
} |
|
} else if (rv) { |
|
dev_err(&client->dev, "Not probing, Interface already present\n"); |
|
goto out; |
|
} |
|
|
|
slave_addr = find_slave_address(client, slave_addr); |
|
|
|
dev_info(&client->dev, |
|
"Trying %s-specified SSIF interface at i2c address 0x%x, adapter %s, slave address 0x%x\n", |
|
ipmi_addr_src_to_str(ssif_info->addr_source), |
|
client->addr, client->adapter->name, slave_addr); |
|
|
|
ssif_info->client = client; |
|
i2c_set_clientdata(client, ssif_info); |
|
|
|
/* Now check for system interface capabilities */ |
|
msg[0] = IPMI_NETFN_APP_REQUEST << 2; |
|
msg[1] = IPMI_GET_SYSTEM_INTERFACE_CAPABILITIES_CMD; |
|
msg[2] = 0; /* SSIF */ |
|
rv = do_cmd(client, 3, msg, &len, resp); |
|
if (!rv && (len >= 3) && (resp[2] == 0)) { |
|
if (len < 7) { |
|
if (ssif_dbg_probe) |
|
dev_dbg(&ssif_info->client->dev, |
|
"SSIF info too short: %d\n", len); |
|
goto no_support; |
|
} |
|
|
|
/* Got a good SSIF response, handle it. */ |
|
ssif_info->max_xmit_msg_size = resp[5]; |
|
ssif_info->max_recv_msg_size = resp[6]; |
|
ssif_info->multi_support = (resp[4] >> 6) & 0x3; |
|
ssif_info->supports_pec = (resp[4] >> 3) & 0x1; |
|
|
|
/* Sanitize the data */ |
|
switch (ssif_info->multi_support) { |
|
case SSIF_NO_MULTI: |
|
if (ssif_info->max_xmit_msg_size > 32) |
|
ssif_info->max_xmit_msg_size = 32; |
|
if (ssif_info->max_recv_msg_size > 32) |
|
ssif_info->max_recv_msg_size = 32; |
|
break; |
|
|
|
case SSIF_MULTI_2_PART: |
|
if (ssif_info->max_xmit_msg_size > 63) |
|
ssif_info->max_xmit_msg_size = 63; |
|
if (ssif_info->max_recv_msg_size > 62) |
|
ssif_info->max_recv_msg_size = 62; |
|
break; |
|
|
|
case SSIF_MULTI_n_PART: |
|
/* We take whatever size given, but do some testing. */ |
|
break; |
|
|
|
default: |
|
/* Data is not sane, just give up. */ |
|
goto no_support; |
|
} |
|
} else { |
|
no_support: |
|
/* Assume no multi-part or PEC support */ |
|
dev_info(&ssif_info->client->dev, |
|
"Error fetching SSIF: %d %d %2.2x, your system probably doesn't support this command so using defaults\n", |
|
rv, len, resp[2]); |
|
|
|
ssif_info->max_xmit_msg_size = 32; |
|
ssif_info->max_recv_msg_size = 32; |
|
ssif_info->multi_support = SSIF_NO_MULTI; |
|
ssif_info->supports_pec = 0; |
|
} |
|
|
|
test_multipart_messages(client, ssif_info, resp); |
|
|
|
/* Make sure the NMI timeout is cleared. */ |
|
msg[0] = IPMI_NETFN_APP_REQUEST << 2; |
|
msg[1] = IPMI_CLEAR_MSG_FLAGS_CMD; |
|
msg[2] = WDT_PRE_TIMEOUT_INT; |
|
rv = do_cmd(client, 3, msg, &len, resp); |
|
if (rv || (len < 3) || (resp[2] != 0)) |
|
dev_warn(&ssif_info->client->dev, |
|
"Unable to clear message flags: %d %d %2.2x\n", |
|
rv, len, resp[2]); |
|
|
|
/* Attempt to enable the event buffer. */ |
|
msg[0] = IPMI_NETFN_APP_REQUEST << 2; |
|
msg[1] = IPMI_GET_BMC_GLOBAL_ENABLES_CMD; |
|
rv = do_cmd(client, 2, msg, &len, resp); |
|
if (rv || (len < 4) || (resp[2] != 0)) { |
|
dev_warn(&ssif_info->client->dev, |
|
"Error getting global enables: %d %d %2.2x\n", |
|
rv, len, resp[2]); |
|
rv = 0; /* Not fatal */ |
|
goto found; |
|
} |
|
|
|
ssif_info->global_enables = resp[3]; |
|
|
|
if (resp[3] & IPMI_BMC_EVT_MSG_BUFF) { |
|
ssif_info->has_event_buffer = true; |
|
/* buffer is already enabled, nothing to do. */ |
|
goto found; |
|
} |
|
|
|
msg[0] = IPMI_NETFN_APP_REQUEST << 2; |
|
msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD; |
|
msg[2] = ssif_info->global_enables | IPMI_BMC_EVT_MSG_BUFF; |
|
rv = do_cmd(client, 3, msg, &len, resp); |
|
if (rv || (len < 2)) { |
|
dev_warn(&ssif_info->client->dev, |
|
"Error setting global enables: %d %d %2.2x\n", |
|
rv, len, resp[2]); |
|
rv = 0; /* Not fatal */ |
|
goto found; |
|
} |
|
|
|
if (resp[2] == 0) { |
|
/* A successful return means the event buffer is supported. */ |
|
ssif_info->has_event_buffer = true; |
|
ssif_info->global_enables |= IPMI_BMC_EVT_MSG_BUFF; |
|
} |
|
|
|
/* Some systems don't behave well if you enable alerts. */ |
|
if (alerts_broken) |
|
goto found; |
|
|
|
msg[0] = IPMI_NETFN_APP_REQUEST << 2; |
|
msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD; |
|
msg[2] = ssif_info->global_enables | IPMI_BMC_RCV_MSG_INTR; |
|
rv = do_cmd(client, 3, msg, &len, resp); |
|
if (rv || (len < 2)) { |
|
dev_warn(&ssif_info->client->dev, |
|
"Error setting global enables: %d %d %2.2x\n", |
|
rv, len, resp[2]); |
|
rv = 0; /* Not fatal */ |
|
goto found; |
|
} |
|
|
|
if (resp[2] == 0) { |
|
/* A successful return means the alert is supported. */ |
|
ssif_info->supports_alert = true; |
|
ssif_info->global_enables |= IPMI_BMC_RCV_MSG_INTR; |
|
} |
|
|
|
found: |
|
if (ssif_dbg_probe) { |
|
dev_dbg(&ssif_info->client->dev, |
|
"%s: i2c_probe found device at i2c address %x\n", |
|
__func__, client->addr); |
|
} |
|
|
|
spin_lock_init(&ssif_info->lock); |
|
ssif_info->ssif_state = SSIF_NORMAL; |
|
timer_setup(&ssif_info->retry_timer, retry_timeout, 0); |
|
timer_setup(&ssif_info->watch_timer, watch_timeout, 0); |
|
|
|
for (i = 0; i < SSIF_NUM_STATS; i++) |
|
atomic_set(&ssif_info->stats[i], 0); |
|
|
|
if (ssif_info->supports_pec) |
|
ssif_info->client->flags |= I2C_CLIENT_PEC; |
|
|
|
ssif_info->handlers.owner = THIS_MODULE; |
|
ssif_info->handlers.start_processing = ssif_start_processing; |
|
ssif_info->handlers.shutdown = shutdown_ssif; |
|
ssif_info->handlers.get_smi_info = get_smi_info; |
|
ssif_info->handlers.sender = sender; |
|
ssif_info->handlers.request_events = request_events; |
|
ssif_info->handlers.set_need_watch = ssif_set_need_watch; |
|
|
|
{ |
|
unsigned int thread_num; |
|
|
|
thread_num = ((i2c_adapter_id(ssif_info->client->adapter) |
|
<< 8) | |
|
ssif_info->client->addr); |
|
init_completion(&ssif_info->wake_thread); |
|
ssif_info->thread = kthread_run(ipmi_ssif_thread, ssif_info, |
|
"kssif%4.4x", thread_num); |
|
if (IS_ERR(ssif_info->thread)) { |
|
rv = PTR_ERR(ssif_info->thread); |
|
dev_notice(&ssif_info->client->dev, |
|
"Could not start kernel thread: error %d\n", |
|
rv); |
|
goto out; |
|
} |
|
} |
|
|
|
dev_set_drvdata(&ssif_info->client->dev, ssif_info); |
|
rv = device_add_group(&ssif_info->client->dev, |
|
&ipmi_ssif_dev_attr_group); |
|
if (rv) { |
|
dev_err(&ssif_info->client->dev, |
|
"Unable to add device attributes: error %d\n", |
|
rv); |
|
goto out; |
|
} |
|
|
|
rv = ipmi_register_smi(&ssif_info->handlers, |
|
ssif_info, |
|
&ssif_info->client->dev, |
|
slave_addr); |
|
if (rv) { |
|
dev_err(&ssif_info->client->dev, |
|
"Unable to register device: error %d\n", rv); |
|
goto out_remove_attr; |
|
} |
|
|
|
out: |
|
if (rv) { |
|
if (addr_info) |
|
addr_info->client = NULL; |
|
|
|
dev_err(&ssif_info->client->dev, |
|
"Unable to start IPMI SSIF: %d\n", rv); |
|
kfree(ssif_info); |
|
} |
|
kfree(resp); |
|
mutex_unlock(&ssif_infos_mutex); |
|
return rv; |
|
|
|
out_remove_attr: |
|
device_remove_group(&ssif_info->client->dev, &ipmi_ssif_dev_attr_group); |
|
dev_set_drvdata(&ssif_info->client->dev, NULL); |
|
goto out; |
|
} |
|
|
|
static int new_ssif_client(int addr, char *adapter_name, |
|
int debug, int slave_addr, |
|
enum ipmi_addr_src addr_src, |
|
struct device *dev) |
|
{ |
|
struct ssif_addr_info *addr_info; |
|
int rv = 0; |
|
|
|
mutex_lock(&ssif_infos_mutex); |
|
if (ssif_info_find(addr, adapter_name, false)) { |
|
rv = -EEXIST; |
|
goto out_unlock; |
|
} |
|
|
|
addr_info = kzalloc(sizeof(*addr_info), GFP_KERNEL); |
|
if (!addr_info) { |
|
rv = -ENOMEM; |
|
goto out_unlock; |
|
} |
|
|
|
if (adapter_name) { |
|
addr_info->adapter_name = kstrdup(adapter_name, GFP_KERNEL); |
|
if (!addr_info->adapter_name) { |
|
kfree(addr_info); |
|
rv = -ENOMEM; |
|
goto out_unlock; |
|
} |
|
} |
|
|
|
strncpy(addr_info->binfo.type, DEVICE_NAME, |
|
sizeof(addr_info->binfo.type)); |
|
addr_info->binfo.addr = addr; |
|
addr_info->binfo.platform_data = addr_info; |
|
addr_info->debug = debug; |
|
addr_info->slave_addr = slave_addr; |
|
addr_info->addr_src = addr_src; |
|
addr_info->dev = dev; |
|
|
|
if (dev) |
|
dev_set_drvdata(dev, addr_info); |
|
|
|
list_add_tail(&addr_info->link, &ssif_infos); |
|
|
|
/* Address list will get it */ |
|
|
|
out_unlock: |
|
mutex_unlock(&ssif_infos_mutex); |
|
return rv; |
|
} |
|
|
|
static void free_ssif_clients(void) |
|
{ |
|
struct ssif_addr_info *info, *tmp; |
|
|
|
mutex_lock(&ssif_infos_mutex); |
|
list_for_each_entry_safe(info, tmp, &ssif_infos, link) { |
|
list_del(&info->link); |
|
kfree(info->adapter_name); |
|
kfree(info); |
|
} |
|
mutex_unlock(&ssif_infos_mutex); |
|
} |
|
|
|
static unsigned short *ssif_address_list(void) |
|
{ |
|
struct ssif_addr_info *info; |
|
unsigned int count = 0, i = 0; |
|
unsigned short *address_list; |
|
|
|
list_for_each_entry(info, &ssif_infos, link) |
|
count++; |
|
|
|
address_list = kcalloc(count + 1, sizeof(*address_list), |
|
GFP_KERNEL); |
|
if (!address_list) |
|
return NULL; |
|
|
|
list_for_each_entry(info, &ssif_infos, link) { |
|
unsigned short addr = info->binfo.addr; |
|
int j; |
|
|
|
for (j = 0; j < i; j++) { |
|
if (address_list[j] == addr) |
|
/* Found a dup. */ |
|
break; |
|
} |
|
if (j == i) /* Didn't find it in the list. */ |
|
address_list[i++] = addr; |
|
} |
|
address_list[i] = I2C_CLIENT_END; |
|
|
|
return address_list; |
|
} |
|
|
|
#ifdef CONFIG_ACPI |
|
static const struct acpi_device_id ssif_acpi_match[] = { |
|
{ "IPI0001", 0 }, |
|
{ }, |
|
}; |
|
MODULE_DEVICE_TABLE(acpi, ssif_acpi_match); |
|
#endif |
|
|
|
#ifdef CONFIG_DMI |
|
static int dmi_ipmi_probe(struct platform_device *pdev) |
|
{ |
|
u8 slave_addr = 0; |
|
u16 i2c_addr; |
|
int rv; |
|
|
|
if (!ssif_trydmi) |
|
return -ENODEV; |
|
|
|
rv = device_property_read_u16(&pdev->dev, "i2c-addr", &i2c_addr); |
|
if (rv) { |
|
dev_warn(&pdev->dev, "No i2c-addr property\n"); |
|
return -ENODEV; |
|
} |
|
|
|
rv = device_property_read_u8(&pdev->dev, "slave-addr", &slave_addr); |
|
if (rv) |
|
slave_addr = 0x20; |
|
|
|
return new_ssif_client(i2c_addr, NULL, 0, |
|
slave_addr, SI_SMBIOS, &pdev->dev); |
|
} |
|
#else |
|
static int dmi_ipmi_probe(struct platform_device *pdev) |
|
{ |
|
return -ENODEV; |
|
} |
|
#endif |
|
|
|
static const struct i2c_device_id ssif_id[] = { |
|
{ DEVICE_NAME, 0 }, |
|
{ } |
|
}; |
|
MODULE_DEVICE_TABLE(i2c, ssif_id); |
|
|
|
static struct i2c_driver ssif_i2c_driver = { |
|
.class = I2C_CLASS_HWMON, |
|
.driver = { |
|
.name = DEVICE_NAME |
|
}, |
|
.probe = ssif_probe, |
|
.remove = ssif_remove, |
|
.alert = ssif_alert, |
|
.id_table = ssif_id, |
|
.detect = ssif_detect |
|
}; |
|
|
|
static int ssif_platform_probe(struct platform_device *dev) |
|
{ |
|
return dmi_ipmi_probe(dev); |
|
} |
|
|
|
static int ssif_platform_remove(struct platform_device *dev) |
|
{ |
|
struct ssif_addr_info *addr_info = dev_get_drvdata(&dev->dev); |
|
|
|
if (!addr_info) |
|
return 0; |
|
|
|
mutex_lock(&ssif_infos_mutex); |
|
list_del(&addr_info->link); |
|
kfree(addr_info); |
|
mutex_unlock(&ssif_infos_mutex); |
|
return 0; |
|
} |
|
|
|
static const struct platform_device_id ssif_plat_ids[] = { |
|
{ "dmi-ipmi-ssif", 0 }, |
|
{ } |
|
}; |
|
|
|
static struct platform_driver ipmi_driver = { |
|
.driver = { |
|
.name = DEVICE_NAME, |
|
}, |
|
.probe = ssif_platform_probe, |
|
.remove = ssif_platform_remove, |
|
.id_table = ssif_plat_ids |
|
}; |
|
|
|
static int init_ipmi_ssif(void) |
|
{ |
|
int i; |
|
int rv; |
|
|
|
if (initialized) |
|
return 0; |
|
|
|
pr_info("IPMI SSIF Interface driver\n"); |
|
|
|
/* build list for i2c from addr list */ |
|
for (i = 0; i < num_addrs; i++) { |
|
rv = new_ssif_client(addr[i], adapter_name[i], |
|
dbg[i], slave_addrs[i], |
|
SI_HARDCODED, NULL); |
|
if (rv) |
|
pr_err("Couldn't add hardcoded device at addr 0x%x\n", |
|
addr[i]); |
|
} |
|
|
|
if (ssif_tryacpi) |
|
ssif_i2c_driver.driver.acpi_match_table = |
|
ACPI_PTR(ssif_acpi_match); |
|
|
|
if (ssif_trydmi) { |
|
rv = platform_driver_register(&ipmi_driver); |
|
if (rv) |
|
pr_err("Unable to register driver: %d\n", rv); |
|
else |
|
platform_registered = true; |
|
} |
|
|
|
ssif_i2c_driver.address_list = ssif_address_list(); |
|
|
|
rv = i2c_add_driver(&ssif_i2c_driver); |
|
if (!rv) |
|
initialized = true; |
|
|
|
return rv; |
|
} |
|
module_init(init_ipmi_ssif); |
|
|
|
static void cleanup_ipmi_ssif(void) |
|
{ |
|
if (!initialized) |
|
return; |
|
|
|
initialized = false; |
|
|
|
i2c_del_driver(&ssif_i2c_driver); |
|
|
|
kfree(ssif_i2c_driver.address_list); |
|
|
|
if (ssif_trydmi && platform_registered) |
|
platform_driver_unregister(&ipmi_driver); |
|
|
|
free_ssif_clients(); |
|
} |
|
module_exit(cleanup_ipmi_ssif); |
|
|
|
MODULE_ALIAS("platform:dmi-ipmi-ssif"); |
|
MODULE_AUTHOR("Todd C Davis <[email protected]>, Corey Minyard <[email protected]>"); |
|
MODULE_DESCRIPTION("IPMI driver for management controllers on a SMBus"); |
|
MODULE_LICENSE("GPL");
|
|
|