* [Xenomai] rtdm_safe_copy_from_user() causing mode switch
@ 2017-10-07 1:56 Jackson Jones
2017-11-01 21:14 ` Philippe Gerum
0 siblings, 1 reply; 4+ messages in thread
From: Jackson Jones @ 2017-10-07 1:56 UTC (permalink / raw)
To: xenomai
I noticed that when I call rtdm_safe_copy_from_user() it causes a mode
switch. I am looking to pass a uint32_t (u32 in kernel space) from
user-space to a rtdm driver.
Is there a way of doing this that avoids a mode switch. I am doing this in
an rtdm spi driver for the imx6. It is used to change the transfer size
(number of words) for the next call to transfer_iobufs (using the
SPI_RTIOC_TRANSFER ioctl).
Below is a modified spi_master_ioctl_rt which is inside of spi-master.c. I
added an ioctl to do this. I verified that the ioctls SPI_RTIOC_SET_CONFIG,
and SPI_RTIOC_GET_CONFIG cause a mode switch as well as the former calls
rtdm_safe_copy_from_user() and the latter calls rtdm_safe_copy_to_user().
Any advice would be appreciated.
147 static int spi_master_ioctl_rt(struct rtdm_fd *fd,
148 unsigned int request, void *arg)
149 {
150 struct rtdm_spi_remote_slave *slave = fd_to_slave(fd);
151 struct rtdm_spi_master *master = slave->master;
152 struct rtdm_spi_config config;
153 u32 new_size = 0;
154 int ret;
155
156 switch (request) {
157 case SPI_RTIOC_SET_CONFIG:
158 ret = rtdm_safe_copy_from_user(fd, &config,
159 arg, sizeof(config));
160 if (ret == 0)
161 ret = update_slave_config(slave, &config);
162 break;
163 case SPI_RTIOC_GET_CONFIG:
164 rtdm_mutex_lock(&master->bus_lock);
165 config = slave->config;
166 rtdm_mutex_unlock(&master->bus_lock);
167 ret = rtdm_safe_copy_to_user(fd, arg,
168 &config, sizeof(config));
169 break;
170 case SPI_RTIOC_TRANSFER:
171 ret = -EINVAL;
172 if (master->ops->transfer_iobufs) {
173 rtdm_mutex_lock(&master->bus_lock);
174 ret = do_chip_select(slave);
175 if (ret == 0) {
176 ret = master->ops->transfer_iobufs(slave);
177 do_chip_deselect(slave);
178 }
179 rtdm_mutex_unlock(&master->bus_lock);
180 }
181 break;
182
183 // A hack to change the transfer size of the transfer buffers
184 case SPI_RTIOC_CHANGE_XFER_SIZE:
185 ret = rtdm_safe_copy_from_user(fd, &new_size,
186 arg, sizeof(u32));
187 if (ret == 0)
188 master->ops->change_xfer_size(slave, new_size);
189 break;
190
191 default:
192 ret = -ENOSYS;
193 }
194
195 return ret;
196 }
Thanks,
Jackson Jones
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [Xenomai] rtdm_safe_copy_from_user() causing mode switch
2017-10-07 1:56 [Xenomai] rtdm_safe_copy_from_user() causing mode switch Jackson Jones
@ 2017-11-01 21:14 ` Philippe Gerum
2017-11-01 21:19 ` Philippe Gerum
0 siblings, 1 reply; 4+ messages in thread
From: Philippe Gerum @ 2017-11-01 21:14 UTC (permalink / raw)
To: Jackson Jones, xenomai
On 10/07/2017 03:56 AM, Jackson Jones wrote:
> I noticed that when I call rtdm_safe_copy_from_user() it causes a mode
> switch. I am looking to pass a uint32_t (u32 in kernel space) from
> user-space to a rtdm driver.
>
> Is there a way of doing this that avoids a mode switch. I am doing this in
> an rtdm spi driver for the imx6. It is used to change the transfer size
> (number of words) for the next call to transfer_iobufs (using the
> SPI_RTIOC_TRANSFER ioctl).
>
The mode switch is most likely caused by an access fault taken by the
CPU. Is /proc/xenomai/fault confirming this?
> Below is a modified spi_master_ioctl_rt which is inside of spi-master.c. I
> added an ioctl to do this. I verified that the ioctls SPI_RTIOC_SET_CONFIG,
> and SPI_RTIOC_GET_CONFIG cause a mode switch as well as the former calls
> rtdm_safe_copy_from_user() and the latter calls rtdm_safe_copy_to_user().
> Any advice would be appreciated.
What kind of source memory in user-space, stack-based?
--
Philippe.
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [Xenomai] rtdm_safe_copy_from_user() causing mode switch
2017-11-01 21:14 ` Philippe Gerum
@ 2017-11-01 21:19 ` Philippe Gerum
2017-11-01 22:40 ` Jackson Jones
0 siblings, 1 reply; 4+ messages in thread
From: Philippe Gerum @ 2017-11-01 21:19 UTC (permalink / raw)
To: Jackson Jones, xenomai
On 11/01/2017 10:14 PM, Philippe Gerum wrote:
> On 10/07/2017 03:56 AM, Jackson Jones wrote:
>> I noticed that when I call rtdm_safe_copy_from_user() it causes a mode
>> switch. I am looking to pass a uint32_t (u32 in kernel space) from
>> user-space to a rtdm driver.
>>
>> Is there a way of doing this that avoids a mode switch. I am doing this in
>> an rtdm spi driver for the imx6. It is used to change the transfer size
>> (number of words) for the next call to transfer_iobufs (using the
>> SPI_RTIOC_TRANSFER ioctl).
>>
>
> The mode switch is most likely caused by an access fault taken by the
> CPU. Is /proc/xenomai/fault confirming this?
>
More precisely, this is most likely a fault due to a page table miss
which cannot be resolved on the fly by a simple page table fixup then
return. Instead, the whole thing has to go through the regular kernel
page fault handler, which requires Cobalt to switch to secondary mode
first.
--
Philippe.
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [Xenomai] rtdm_safe_copy_from_user() causing mode switch
2017-11-01 21:19 ` Philippe Gerum
@ 2017-11-01 22:40 ` Jackson Jones
0 siblings, 0 replies; 4+ messages in thread
From: Jackson Jones @ 2017-11-01 22:40 UTC (permalink / raw)
To: Philippe Gerum, xenomai
yes, I confirmed the page fault, I use this script:
#!/bin/bash
while true
do
cat /proc/xenomai/sched/stat | grep demo
sleep 1
done
exit 0
I am trying to pass an integer from a real-time app to the SPI driver. I am
changing the value of a u32 in the device specific slave. I added an IOCTL
which I really did not want to do. I may be able to work around this. The
others in my group want to be able to change the size of the SPI message
(number of words) like they can when they used spidev. For spidev you just
update the value in the struct that is the message length and pass it back
into spidev equivalent of transfer buffers. Below is the source of my
driver. It works, I had it running for days sending a bunch of different
command (message are received by an FPGA) and checking the results. The
driver is not "finished", I only have support for a word size of 16 bit
words. I plan on being able to support all the word sizes that the hardware
does (through the configure).
/*
* Copyright 2004-2007, 2016 Freescale Semiconductor, Inc. All Rights
Reserved.
* Copyright (C) 2008 Juergen Beisert
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301, USA.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/spi/spi.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include "spi-master.h"
#include <linux/platform_data/spi-imx.h>
#define RTDM_SUBCLASS_IMX6 1
#define MXC_CSPIRXDATA 0x00
#define MXC_CSPITXDATA 0x04
#define MXC_CSPICTRL 0x08
#define MXC_CSPIINT 0x0c
#define MXC_RESET 0x1c
#define MX51_ECSPI_CTRL 0x08
#define MX51_ECSPI_CTRL_ENABLE (1 << 0)
#define MX51_ECSPI_CTRL_XCH (1 << 2)
#define MX51_ECSPI_CTRL_SMC (1 << 3)
#define MX51_ECSPI_CTRL_MODE_MASK (0xf << 4)
#define MX51_ECSPI_CTRL_POSTDIV_OFFSET 8
#define MX51_ECSPI_CTRL_PREDIV_OFFSET 12
#define MX51_ECSPI_CTRL_CS(cs) ((cs) << 18)
#define MX51_ECSPI_CTRL_BL_OFFSET 20
#define MX51_ECSPI_CONFIG 0x0c
#define MX51_ECSPI_CONFIG_SCLKPHA(cs) (1 << ((cs) + 0))
#define MX51_ECSPI_CONFIG_SCLKPOL(cs) (1 << ((cs) + 4))
#define MX51_ECSPI_CONFIG_SBBCTRL(cs) (1 << ((cs) + 8))
#define MX51_ECSPI_CONFIG_SSBPOL(cs) (1 << ((cs) + 12))
#define MX51_ECSPI_CONFIG_SCLKCTL(cs) (1 << ((cs) + 20))
#define MX51_ECSPI_INT 0x10
#define MX51_ECSPI_INT_TEEN (1 << 0)
#define MX51_ECSPI_INT_RREN (1 << 3)
#define MX51_ECSPI_INT_TCEN (1 << 7)
#define MX51_ECSPI_DMA 0x14
#define MX51_ECSPI_DMA_TX_WML_OFFSET 0
#define MX51_ECSPI_DMA_TX_WML_MASK 0x3F
#define MX51_ECSPI_DMA_RX_WML_OFFSET 16
#define MX51_ECSPI_DMA_RX_WML_MASK (0x3F << 16)
#define MX51_ECSPI_DMA_RXT_WML_OFFSET 24
#define MX51_ECSPI_DMA_RXT_WML_MASK (0x3F << 24)
#define MX51_ECSPI_DMA_TEDEN_OFFSET 7
#define MX51_ECSPI_DMA_RXDEN_OFFSET 23
#define MX51_ECSPI_DMA_RXTDEN_OFFSET 31
#define MX51_ECSPI_STAT 0x18
#define MX51_ECSPI_STAT_RR (1 << 3)
#define MX51_ECSPI_STAT_TE 0x01
#define MX51_ECSPI_STAT_TF (1 << 2)
// defines for test register
#define MX51_ECSPI_TESTREG 0x20
#define MX51_ECSPI_TESTREG__TXCNT 0x00 // num words in TX counter
#define MX51_ECSPI_TESTREG__RXCNT 0x08 // num words in RX counter
#define MX51_ECSPI_TESTREG_LBC BIT(31) // 1 enable loopback, 0 disable
loopback
/* generic defines to abstract from the different register layouts */
#define MXC_INT_RR (1 << 0) /* Receive data ready interrupt */
#define MXC_INT_TE (1 << 1) /* Transmit FIFO empty interrupt */
#define MXC_INT_TCEN (1 << 7) /* Transfer complete */
/* The maximum bytes that a sdma BD can transfer.*/
#define MAX_SDMA_BD_BYTES (1 << 15)
// defines for DMA, we are not ready for that yet.
/* 3 Sec for 1MB or less than 1MB, else change with the transfer length */
#define IMX_DEFAULT_DMA_TIMEOUT (msecs_to_jiffies(3000))
#define IMX_DMA_TIMEOUT(len) ((len < 0x100000) ? IMX_DEFAULT_DMA_TIMEOUT : \
len * IMX_DEFAULT_DMA_TIMEOUT / 0x100000)
#define DRIVER_NAME "spi_imx"
// defines, not part of spi.h
#define SPI_CS_ACTIVE 1
#define SPI_CS_INACTIVE 0
// Defined this as a constant instead of a function, since all
// the imx6 variants have TX/RX sizes of 64
#define spi_imx6_fifosize 64
// RJJ defines to turn on TX or RX debug messages
//#define TXDBG 1
//#define RXDBG 1
#define HACK_TRACK 6 //RJJ remove after testing
static int HackCount = 0; //RJJ remove after testing
static int SubHackCount = 0; //RJJ remove after testing
//static u32 new_size;
// Xenomai master device struct
struct spi_master_imx6
{
struct rtdm_spi_master master;
void __iomem *regs;
struct clk *clk;
struct clk *clk_per;
struct clk *clk_ipg;
unsigned long clk_hz;
rtdm_irq_t irqh;
const u8 *tx_buf;
u8 *rx_buf;
int tx_len;
int rx_len;
rtdm_event_t transfer_done;
u32 count; // RJJ not checked, but keeping just in case.
};
// Xenomai slave device struct
struct spi_slave_imx6
{
struct rtdm_spi_remote_slave slave;
void *io_virt;
void *tx_buf; //RJJ This kind of sucks
dma_addr_t io_dma;
//dma_addr_t new_length;
size_t io_len;
u32 transfer_length;
};
static void imx6_intctrl(struct spi_master_imx6 *spim, int enable)
{
unsigned val = 0;
if (enable & MXC_INT_TE)
val |= MX51_ECSPI_INT_TEEN;
if (enable & MXC_INT_RR)
val |= MX51_ECSPI_INT_RREN;
if (enable & MXC_INT_TCEN)
val |= MX51_ECSPI_INT_TCEN;
writel(val, spim->regs + MX51_ECSPI_INT);
}
static inline struct spi_slave_imx6 *to_slave_imx6(struct
rtdm_spi_remote_slave *slave)
{
return container_of(slave, struct spi_slave_imx6, slave);
}
static inline struct spi_master_imx6 *to_master_imx6(struct
rtdm_spi_remote_slave *slave)
{
return container_of(slave->master, struct spi_master_imx6, master);
}
static inline struct device *master_to_kdev(struct rtdm_spi_master *master)
{
return &master->kmaster->dev;
}
static int mx51_ecspi_rx_available(struct spi_master_imx6 *spim)
{
//return readl(spim->regs + MX51_ECSPI_STAT) & MX51_ECSPI_STAT_RR;
u32 stat = readl(spim->regs + MX51_ECSPI_STAT);
stat &= MX51_ECSPI_STAT_RR;
return stat;
}
static inline int mx51_ecspi_tx_not_full(struct spi_master_imx6 *spim)
{
u32 stat = readl(spim->regs + MX51_ECSPI_STAT);
stat &= MX51_ECSPI_STAT_TF;
if (stat)
return 0;
else
return 1;
}
static inline int mx51_ecspi_tx_empty(struct spi_master_imx6 *spim)
{
u32 stat = readl(spim->regs + MX51_ECSPI_STAT);
stat &= MX51_ECSPI_STAT_TE;
if (stat)
return 1;
else
return 0;
}
static inline int mx51_ecspi_tx_done(struct spi_master_imx6 *spim)
{
u32 ctrl = readl(spim->regs + MX51_ECSPI_CTRL);
ctrl &= MX51_ECSPI_CTRL_XCH;
if (ctrl)
return 0;
else
return 1;
}
static inline u32 mx51_ecspi_rx_word_count(struct spi_master_imx6 *spim)
{
u32 count = 0;
count = readl(spim->regs + MX51_ECSPI_TESTREG);
count = ((count & 0x00007f00) >> MX51_ECSPI_TESTREG__RXCNT);
return count;
}
static inline void trigger(struct spi_master_imx6 *spim)
{
u32 reg = readl(spim->regs + MX51_ECSPI_CTRL);
reg |= (MX51_ECSPI_CTRL_XCH);
writel(reg, spim->regs + MX51_ECSPI_CTRL);
}
static inline u32 imx6_rx(struct spi_master_imx6 *spim)
{
u16 data = readl(spim->regs + MXC_CSPIRXDATA);
return data;
}
static inline void imx6_tx(struct spi_master_imx6 *spim)
{
u16 data = 0;
if (spim->tx_buf)
{
data = *(u16 *)spim->tx_buf;
}
writel(data, spim->regs + MXC_CSPITXDATA);
}
static inline void imx6_rx_fifo(struct spi_master_imx6 *spim)
{
u16 data = 0;
while (spim->rx_len > 0 && mx51_ecspi_rx_available(spim))
{
data = imx6_rx(spim);
if (spim->rx_buf)
{
*(u16 *)spim->rx_buf = data;
#ifdef RXDBG
pr_err("rx = 0x%.2X 0x%.2X rx_len = %u rx_buf = 0x%X\n",
*spim->rx_buf, *(spim->rx_buf + 1),
spim->rx_len, (unsigned int)(spim->rx_buf));
#endif
spim->rx_buf += sizeof(u16);
}
spim->rx_len--;
spim->count--;
}
}
static inline void imx6_tx_fifo(struct spi_master_imx6 *spim)
{
if (mx51_ecspi_tx_empty(spim) && mx51_ecspi_tx_done(spim) &&
(spim->tx_len) > 0)
{
#ifdef TXDBG
pr_err("<%d>-------------------------------------------------------<%d>\n",
HackCount, HackCount);
#endif
spim->count = 0;
while (spim->tx_len > 0 && mx51_ecspi_tx_not_full(spim))
{
imx6_tx(spim);
#ifdef TXDBG
pr_err("tx = 0x%.2X 0x%.2X tx_len = %u tx_buf = 0x%X\n",
*spim->tx_buf, *(spim->tx_buf + 1),
spim->tx_len, (unsigned int)(spim->tx_buf));
#endif
spim->tx_buf += sizeof(u16);
spim->tx_len--;
spim->count++; // words transmitted
}
#ifdef TXDBG
pr_err("\n");
#endif
trigger(spim);
imx6_intctrl(spim, MXC_INT_TE);
SubHackCount++;
if (SubHackCount == HACK_TRACK)
{
SubHackCount = 0;
HackCount++;
}
}
}
static void imx6_reset(struct spi_master_imx6 *spim)
{
/* drain RX */
while (mx51_ecspi_rx_available(spim))
readl(spim->regs + MXC_CSPIRXDATA);
}
static void inline _reset_block(struct spi_master_imx6 *spim)
{
u32 CONTROL_REG;
CONTROL_REG = 0x00000000;
writel(CONTROL_REG, spim->regs + MX51_ECSPI_CTRL);
CONTROL_REG = 0x00000001;
writel(CONTROL_REG, spim->regs + MX51_ECSPI_CTRL);
}
static int spi_imx_isr(rtdm_irq_t *irqh)
{
u32 inttype = 0;
struct spi_master_imx6 *spim;
spim = rtdm_irq_get_arg(irqh, struct spi_master_imx6);
inttype = readl(spim->regs + MX51_ECSPI_INT);
switch (inttype)
{
case MX51_ECSPI_INT_TEEN :
//pr_err("isr TEEN");
if (mx51_ecspi_tx_done(spim))
{
imx6_intctrl(spim, 0);
rtdm_event_signal(&spim->transfer_done);
imx6_intctrl(spim, MXC_INT_RR);
}
break;
case MX51_ECSPI_INT_RREN :
//pr_err("isr RREN");
if (mx51_ecspi_rx_available(spim))
{
imx6_intctrl(spim, 0);
imx6_rx_fifo(spim);
}
break;
default :
pr_err("isr Unhandled IRQ Type!!");
break;
}
imx6_tx_fifo(spim);
return RTDM_IRQ_HANDLED;
}
static unsigned int mx51_ecspi_clkdiv(unsigned int fin, unsigned int fspi,
unsigned int *fres)
{
/*
* there are two 4-bit dividers, the pre-divider divides by
* $pre, the post-divider by 2^$post
*/
unsigned int pre, post;
if (unlikely(fspi > fin))
return 0;
post = fls(fin) - fls(fspi);
if (fin > fspi << post)
post++;
/* now we have: (fin <= fspi << post) with post being minimal */
post = max(4U, post) - 4;
if (unlikely(post > 0xf))
{
pr_err("%s: cannot set clock freq: %u (base freq: %u)\n",
__func__, fspi, fin);
return 0xff;
}
pre = DIV_ROUND_UP(fin, fspi << post) - 1;
pr_debug("%s: fin: %u, fspi: %u, post: %u, pre: %u\n",
__func__, fin, fspi, post, pre);
/* Resulting frequency for the SCLK line. */
*fres = (fin / (pre + 1)) >> post;
return (pre << MX51_ECSPI_CTRL_PREDIV_OFFSET) |
(post << MX51_ECSPI_CTRL_POSTDIV_OFFSET);
}
static int imx6_configure(struct rtdm_spi_remote_slave *slave)
{
struct spi_master_imx6 *spim = to_master_imx6(slave);
struct rtdm_spi_config *config = &slave->config;
u32 ctrl = MX51_ECSPI_CTRL_ENABLE, cfg = 0;
u32 clk = config->speed_hz, delay, reg;
_reset_block(spim);
HackCount = 0;
SubHackCount = 0;
/*
* The hardware seems to have a race condition when changing modes. The
* current assumption is that the selection of the channel arrives
* earlier in the hardware than the mode bits when they are written at
* the same time.
* So set master mode for all channels as we do not support slave mode.
*/
ctrl |= MX51_ECSPI_CTRL_MODE_MASK;
/* set clock speed */
ctrl |= mx51_ecspi_clkdiv(spim->clk_hz, config->speed_hz, &clk);
/* set chip select to use */
ctrl |= MX51_ECSPI_CTRL_CS(slave->chip_select);
ctrl |= (config->bits_per_word - 1) << MX51_ECSPI_CTRL_BL_OFFSET;
cfg |= MX51_ECSPI_CONFIG_SBBCTRL(slave->chip_select);
if (config->mode & SPI_CPHA)
cfg |= MX51_ECSPI_CONFIG_SCLKPHA(slave->chip_select);
if (config->mode & SPI_CPOL)
{
cfg |= MX51_ECSPI_CONFIG_SCLKPOL(slave->chip_select);
cfg |= MX51_ECSPI_CONFIG_SCLKCTL(slave->chip_select);
}
if (config->mode & SPI_CS_HIGH)
cfg |= MX51_ECSPI_CONFIG_SSBPOL(slave->chip_select);
else
cfg |= ~MX51_ECSPI_CONFIG_SSBPOL(slave->chip_select);
reg = readl(spim->regs + MX51_ECSPI_TESTREG);
if (config->mode & SPI_LOOP)
{
reg |= MX51_ECSPI_TESTREG_LBC;
pr_err("In Loopback");
}
else
{
reg &= ~MX51_ECSPI_TESTREG_LBC;
pr_err("Not in Loopback");
}
writel(ctrl, spim->regs + MX51_ECSPI_CTRL);
writel(cfg, spim->regs + MX51_ECSPI_CONFIG);
writel(reg, spim->regs + MX51_ECSPI_TESTREG);
// Disable DMA for now
writel(0x00, spim->regs + MX51_ECSPI_DMA);
/*
* Wait until the changes in the configuration register CONFIGREG
* propagate into the hardware. It takes exactly one tick of the
* SCLK clock, but we will wait two SCLK clock just to be sure. The
* effect of the delay it takes for the hardware to apply changes
* is noticable if the SCLK clock run very slow. In such a case, if
* the polarity of SCLK should be inverted, the GPIO ChipSelect might
* be asserted before the SCLK polarity changes, which would disrupt
* the SPI communication as the device on the other end would consider
* the change of SCLK polarity as a clock tick already.
*/
delay = (2 * 1000000) / clk;
if (likely(delay < 10)) /* SCLK is faster than 100 kHz */
udelay(delay);
else /* SCLK is _very_ slow */
usleep_range(delay, delay + 10);
return 0;
}
static void imx6_chip_select(struct rtdm_spi_remote_slave *slave, bool
is_active)
{
struct rtdm_spi_config *config = &slave->config;
int gpio = slave->cs_gpio;
int active = is_active != SPI_CS_INACTIVE;
int dev_is_lowactive = !(config->mode & SPI_CS_HIGH);
if (!gpio_is_valid(gpio))
{
return;
}
gpio_set_value(gpio, dev_is_lowactive ^ active);
}
static int do_transfer_irq(struct rtdm_spi_remote_slave *slave)
{
struct spi_master_imx6 *spim = to_master_imx6(slave);
int ret = 0;
u32 cs;
cs = readl(spim->regs + MX51_ECSPI_CTRL);
if (gpio_is_valid(slave->cs_gpio))
{
imx6_tx_fifo(spim);
}
ret = rtdm_event_wait(&spim->transfer_done);
if (ret)
{
imx6_reset(spim);
return ret;
}
return 0;
}
static int imx6_transfer_iobufs(struct rtdm_spi_remote_slave *slave)
{
struct spi_master_imx6 *spim = to_master_imx6(slave);
struct spi_slave_imx6 *imx = to_slave_imx6(slave);
if (imx->io_len == 0)
return -EINVAL; /* No I/O buffers set. */
spim->tx_len = imx->transfer_length;
spim->rx_len = imx->transfer_length;
spim->rx_buf = imx->io_virt;
spim->tx_buf = imx->tx_buf;
return do_transfer_irq(slave);
}
static void imx6_change_xfer_size(struct rtdm_spi_remote_slave *slave,
u32 new_size)
{
#if 0
struct spi_slave_imx6 *imx = to_slave_imx6(slave);
if (imx->new_size == NULL)
{
}
imx->transfer_length = new_size;
#endif
}
static ssize_t imx6_read(struct rtdm_spi_remote_slave *slave,
void *rx, size_t len)
{
struct spi_master_imx6 *spim = to_master_imx6(slave);
//pr_err("imx6_read->len = %u\n", len);
spim->tx_len = len;
spim->rx_len = len;
spim->tx_buf = NULL;
spim->rx_buf = rx;
return do_transfer_irq(slave) ?: len;
}
static ssize_t imx6_write(struct rtdm_spi_remote_slave *slave,
const void *tx, size_t len)
{
struct spi_master_imx6 *spim = to_master_imx6(slave);
//pr_err("imx6_write->len = %u\n", len);
spim->tx_len = len;
spim->rx_len = len;
spim->tx_buf = tx;
spim->rx_buf = NULL;
return do_transfer_irq(slave) ?: len;
}
static int set_iobufs(struct spi_slave_imx6 *imx, size_t len)
{
struct rtdm_spi_remote_slave *slave = &imx->slave;
struct rtdm_spi_config *config = &slave->config;
dma_addr_t dma;
void *p;
if (len == 0)
return -EINVAL;
// The L1 cache align sizes up to 64 (which is the size of the
// Cortex-A9 L1 cache line).
len = L1_CACHE_ALIGN(len) * 2 * (config->bits_per_word / 8);
if (len == imx->io_len)
return 0;
if (imx->io_len)
return -EINVAL; /* I/O buffers may not be resized. */
/*
* Since we need the I/O buffers to be set for starting a
* transfer, there is no need for serializing this routine and
* transfer_iobufs(), provided io_len is set last.
*
* NOTE: We don't need coherent memory until we actually get
* DMA transfers working, this code is a bit ahead of
* schedule.
*
* Revisit: this assumes DMA mask is 4Gb.
*/
p = dma_alloc_coherent(NULL, len, &dma, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
imx->io_dma = dma;
imx->io_virt = p;
smp_mb();
/*
* May race with transfer_iobufs(), must be assigned after all
* the rest is set up, enforcing a membar.
*/
imx->io_len = len;
return 0;
}
static int imx6_set_iobufs(struct rtdm_spi_remote_slave *slave,
struct rtdm_spi_iobufs *p)
{
struct spi_slave_imx6 *imx = to_slave_imx6(slave);
int ret;
imx->transfer_length = p->io_len;
ret = set_iobufs(imx, p->io_len);
if (ret)
return ret;
p->i_offset = 0;
p->o_offset = imx->io_len / 2;
p->map_len = imx->io_len;
imx->tx_buf = imx->io_virt + p->o_offset;
//@@@RJJ ma
return 0;
}
static int imx6_mmap_iobufs(struct rtdm_spi_remote_slave *slave,
struct vm_area_struct *vma)
{
struct spi_slave_imx6 *imx = to_slave_imx6(slave);
/*
* dma_alloc_coherent() delivers non-cached memory, make sure
* to return consistent mapping attributes. Typically, mixing
* memory attributes across address spaces referring to the
* same physical area is architecturally wrong on ARM.
*/
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return rtdm_mmap_kmem(vma, imx->io_virt);
}
static void imx6_mmap_release(struct rtdm_spi_remote_slave *slave)
{
struct spi_slave_imx6 *imx = to_slave_imx6(slave);
dma_free_coherent(NULL, imx->io_len, imx->io_virt, imx->io_dma);
imx->io_len = 0;
}
static int find_cs_gpio(struct spi_device *spi, struct rtdm_spi_master
*master)
{
struct spi_master *kmaster;
struct spi_imx_master *mxc_platform_info;
struct platform_device *pdev;
struct device_node *np;
int cs_gpio = 0;
int i, num_cs;
int ret = 0;
kmaster = master->kmaster;
pdev = to_platform_device(&kmaster->dev);
np = pdev->dev.of_node;
mxc_platform_info = dev_get_platdata(&kmaster->dev);
if (!np && !mxc_platform_info)
{
dev_err(&pdev->dev, "can't get the platform data\n");
return -EINVAL;
}
ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs);
if (ret < 0)
{
if (mxc_platform_info)
num_cs = mxc_platform_info->num_chipselect;
else
return ret;
}
for (i = 0; i < kmaster->num_chipselect; i++)
{
cs_gpio = of_get_named_gpio(np, "cs-gpios", i);
if (!gpio_is_valid(cs_gpio) && mxc_platform_info)
{
cs_gpio = mxc_platform_info->chipselect[i];
}
if (!gpio_is_valid(cs_gpio))
continue;
spi->chip_select = i;
spi->cs_gpio = cs_gpio;
}
ret = gpio_direction_output(spi->cs_gpio, (spi->mode & SPI_CS_HIGH) ? 0
: 1);
if (ret)
{
dev_err(&spi->dev, "could not set CS%i gpio %i as output: %i",
spi->chip_select, spi->cs_gpio, ret);
return ret;
}
gpio_set_value(spi->cs_gpio, (spi->mode & SPI_CS_HIGH) ? 0 : 1);
return 0;
}
static struct rtdm_spi_remote_slave *imx6_attach_slave(struct
rtdm_spi_master *master,
struct spi_device
*spi)
{
struct spi_slave_imx6 *imx;
int ret;
if (spi->chip_select > 3)
{
dev_err(&spi->dev, "%s: only two native chip-selects are
supported\n", __func__);
return ERR_PTR(-EINVAL);
}
ret = find_cs_gpio(spi, master);
if (ret)
{
pr_err("got an error from find_cs_gpio: %d\n", ret);
return ERR_PTR(ret);
}
imx = kzalloc(sizeof(*imx), GFP_KERNEL);
if (imx == NULL)
{
return ERR_PTR(-ENOMEM);
}
ret = rtdm_spi_add_remote_slave(&imx->slave, master, spi);
if (ret)
{
dev_err(&spi->dev, "%s: failed to attach slave\n", __func__);
kfree(imx);
return ERR_PTR(ret);
}
return &imx->slave;
}
static void imx6_detach_slave(struct rtdm_spi_remote_slave *slave)
{
struct spi_slave_imx6 *imx = to_slave_imx6(slave);
rtdm_spi_remove_remote_slave(slave);
kfree(imx);
}
static int inline imx6_prepare_clocks(struct platform_device *pdev,
struct spi_master_imx6 *spim)
{
int ret;
spim->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(spim->clk))
{
ret = PTR_ERR(spim->clk);
pr_err("Can't get clk\n");
return -1;
}
spim->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
if (IS_ERR(spim->clk_ipg))
{
ret = PTR_ERR(spim->clk_ipg);
pr_err("Can't get clk_ipg\n");
return -1;
}
spim->clk_per = devm_clk_get(&pdev->dev, "per");
if (IS_ERR(spim->clk_per))
{
ret = PTR_ERR(spim->clk_per);
pr_err("Can't get clk_per\n");
return -1;
}
ret = clk_prepare_enable(spim->clk);
if(ret)
{
pr_err("unable to prepair clk\n");
return -1;
}
ret = clk_prepare_enable(spim->clk_per);
if(ret)
{
pr_err("unable to prepair clk_per\n");
return -1;
}
ret = clk_prepare_enable(spim->clk_ipg);
if(ret)
{
pr_err("unable to prepair clk_ipg\n");
return -1;
}
return 0;
}
static struct rtdm_spi_master_ops imx6_master_ops =
{
.configure = imx6_configure,
.chip_select = imx6_chip_select,
.set_iobufs = imx6_set_iobufs,
.mmap_iobufs = imx6_mmap_iobufs,
.mmap_release = imx6_mmap_release,
.transfer_iobufs = imx6_transfer_iobufs,
.change_xfer_size = imx6_change_xfer_size,
.write = imx6_write,
.read = imx6_read,
.attach_slave = imx6_attach_slave,
.detach_slave = imx6_detach_slave,
};
static int spi_imx_probe(struct platform_device *pdev)
{
// Linux devices
struct spi_master *kmaster;
// Xenomai devices
struct spi_master_imx6 *spim;
struct rtdm_spi_master *master;
struct resource *r;
int ret = 0, irq;
dev_dbg(&pdev->dev, "%s: entered\n", __func__);
master = rtdm_spi_alloc_master(&pdev->dev, struct spi_master_imx6,
master);
if (master == NULL)
return -ENOMEM;
master->subclass = RTDM_SUBCLASS_IMX6;
master->ops = &imx6_master_ops;
platform_set_drvdata(pdev, master);
kmaster = master->kmaster;
kmaster->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
kmaster->bits_per_word_mask = SPI_BPW_MASK(16);
kmaster->dev.of_node = pdev->dev.of_node;
kmaster->bus_num = pdev->id;
spim = container_of(master, struct spi_master_imx6, master);
rtdm_event_init(&spim->transfer_done, 0);
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
spim->regs = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(spim->regs))
{
dev_err(&pdev->dev, "%s: cannot map I/O memory\n", __func__);
ret = PTR_ERR(spim->regs);
goto fail;
}
// Get irq
irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
if (irq <= 0)
{
ret = irq ?: -ENODEV;
goto fail;
}
ret = rtdm_irq_request(&spim->irqh, irq, spi_imx_isr, 0,
dev_name(&pdev->dev), spim);
if (ret)
{
dev_err(&pdev->dev, "%s: cannot request IRQ%d\n", __func__, irq);
goto fail;
}
ret = rtdm_spi_add_master(&spim->master);
if (ret)
{
dev_err(&pdev->dev, "%s: failed to add master\n", __func__);
goto fail;
}
if (imx6_prepare_clocks(pdev, spim))
{
goto fail_unclk;
}
// Set clock speed
spim->clk_hz = clk_get_rate(spim->clk_per);
// Clears all RX
imx6_reset(spim);
// masks all interrupts
imx6_intctrl(spim, 0);
return 0;
fail_unclk:
clk_disable_unprepare(spim->clk);
clk_disable_unprepare(spim->clk_ipg);
clk_disable_unprepare(spim->clk_per);
fail:
spi_master_put(kmaster);
return ret;
}
static int spi_imx_remove(struct platform_device *pdev)
{
struct rtdm_spi_master *master = platform_get_drvdata(pdev);
struct spi_master_imx6 *spim;
dev_dbg(&pdev->dev, "%s: entered\n", __func__);
spim = container_of(master, struct spi_master_imx6, master);
imx6_reset(spim);
rtdm_irq_free(&spim->irqh);
clk_disable_unprepare(spim->clk);
rtdm_spi_remove_master(master);
return 0;
}
static const struct of_device_id imx6_spi_match[] =
{
{
.compatible = "fsl,imx51-ecspi",
},
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, imx6_spi_match);
static struct platform_driver spi_imx_driver =
{
.driver =
{
.name = DRIVER_NAME,
.of_match_table = imx6_spi_match,
},
.probe = spi_imx_probe,
.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver);
MODULE_LICENSE("GPL");
On Wed, Nov 1, 2017 at 2:19 PM, Philippe Gerum <rpm@xenomai.org> wrote:
> On 11/01/2017 10:14 PM, Philippe Gerum wrote:
> > On 10/07/2017 03:56 AM, Jackson Jones wrote:
> >> I noticed that when I call rtdm_safe_copy_from_user() it causes a mode
> >> switch. I am looking to pass a uint32_t (u32 in kernel space) from
> >> user-space to a rtdm driver.
> >>
> >> Is there a way of doing this that avoids a mode switch. I am doing this
> in
> >> an rtdm spi driver for the imx6. It is used to change the transfer size
> >> (number of words) for the next call to transfer_iobufs (using the
> >> SPI_RTIOC_TRANSFER ioctl).
> >>
> >
> > The mode switch is most likely caused by an access fault taken by the
> > CPU. Is /proc/xenomai/fault confirming this?
> >
>
> More precisely, this is most likely a fault due to a page table miss
> which cannot be resolved on the fly by a simple page table fixup then
> return. Instead, the whole thing has to go through the regular kernel
> page fault handler, which requires Cobalt to switch to secondary mode
> first.
>
> --
> Philippe.
>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2017-11-01 22:40 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-10-07 1:56 [Xenomai] rtdm_safe_copy_from_user() causing mode switch Jackson Jones
2017-11-01 21:14 ` Philippe Gerum
2017-11-01 21:19 ` Philippe Gerum
2017-11-01 22:40 ` Jackson Jones
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.