blob: 273b419225c70207eaaf61d78db09f35f08d50e8 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package nmble
import (
"fmt"
"sync"
log "github.com/sirupsen/logrus"
"mynewt.apache.org/newtmgr/nmxact/nmxutil"
)
type masterState int
const (
MASTER_STATE_IDLE masterState = iota
MASTER_STATE_SECONDARY
MASTER_STATE_PRIMARY
MASTER_STATE_PRIMARY_SECONDARY_PENDING
)
func (s masterState) String() string {
m := map[masterState]string{
MASTER_STATE_IDLE: "idle",
MASTER_STATE_SECONDARY: "secondary",
MASTER_STATE_PRIMARY: "primary",
MASTER_STATE_PRIMARY_SECONDARY_PENDING: "primary_secondary_pending",
}
return m[s]
}
type primary struct {
token interface{}
ch chan error
}
type Preemptable interface {
Preempt() error
}
// Represents the Bluetooth device's "master privileges." The device can only
// do one of the following actions at a time:
// * initiate connection
// * scan
//
// Clients are divided into two groups:
// * primary
// * secondary
//
// This struct restricts master privileges to a single client at a time. It
// uses the following procedure to determine which of several clients to serve:
// If there is one or more waiting primaries:
// If a secondary is active, preempt it.
// Service the primaries in the order of their requests.
// Else (no waiting primaries):
// Service waiting secondary if there is one.
type Master struct {
primaries []primary
secondary Preemptable
state masterState
secondaryReadyCh chan error
mtx sync.Mutex
}
func NewMaster(x *BleXport) Master {
return Master{
secondaryReadyCh: make(chan error),
}
}
func (m *Master) GetSecondary() Preemptable {
return m.secondary
}
func (m *Master) SetSecondary(s Preemptable) error {
m.mtx.Lock()
defer m.mtx.Unlock()
if m.state == MASTER_STATE_SECONDARY ||
m.state == MASTER_STATE_PRIMARY_SECONDARY_PENDING {
return fmt.Errorf("cannot replace master secondary while it is in use")
}
m.secondary = s
return nil
}
func (m *Master) setState(s masterState) {
log.Debugf("Master state change: %s --> %s", m.state, s)
m.state = s
}
func (m *Master) appendPrimary(token interface{}) primary {
c := primary{
token: token,
ch: make(chan error),
}
m.primaries = append(m.primaries, c)
return c
}
func (m *Master) AcquirePrimary(token interface{}) error {
initiate := func() (*primary, error) {
m.mtx.Lock()
defer m.mtx.Unlock()
switch m.state {
case MASTER_STATE_IDLE:
m.setState(MASTER_STATE_PRIMARY)
return nil, nil
case MASTER_STATE_SECONDARY:
c := m.appendPrimary(token)
go m.secondary.Preempt()
return &c, nil
case MASTER_STATE_PRIMARY, MASTER_STATE_PRIMARY_SECONDARY_PENDING:
c := m.appendPrimary(token)
return &c, nil
default:
nmxutil.Assert(false)
return nil, fmt.Errorf("internal error: invalid master state=%+v",
m.state)
}
}
c, err := initiate()
if err != nil {
return err
}
if c == nil {
return nil
}
return <-c.ch
}
func (m *Master) AcquireSecondary() error {
initiate := func() (chan error, error) {
m.mtx.Lock()
defer m.mtx.Unlock()
switch m.state {
case MASTER_STATE_IDLE:
// The resource is unused; just acquire it.
m.setState(MASTER_STATE_SECONDARY)
return nil, nil
case MASTER_STATE_SECONDARY, MASTER_STATE_PRIMARY_SECONDARY_PENDING:
nmxutil.Assert(false)
return nil, fmt.Errorf("Attempt to perform more than one " +
"secondary master procedure")
case MASTER_STATE_PRIMARY:
m.setState(MASTER_STATE_PRIMARY_SECONDARY_PENDING)
return m.secondaryReadyCh, nil
default:
nmxutil.Assert(false)
return nil, fmt.Errorf(
"internal error: invalid master state=%+v", m.state)
}
}
ch, err := initiate()
if err != nil {
return err
}
if ch == nil {
return nil
}
return <-ch
}
func (m *Master) serviceSecondary(err error) {
m.secondaryReadyCh <- err
}
func (m *Master) servicePrimary(err error) {
nmxutil.Assert(len(m.primaries) > 0)
next := m.primaries[0]
m.primaries = m.primaries[1:]
next.ch <- err
}
func (m *Master) Release() {
m.mtx.Lock()
defer m.mtx.Unlock()
switch m.state {
case MASTER_STATE_IDLE:
nmxutil.Assert(false)
case MASTER_STATE_SECONDARY:
if len(m.primaries) == 0 {
m.setState(MASTER_STATE_IDLE)
} else {
m.setState(MASTER_STATE_PRIMARY)
m.servicePrimary(nil)
}
case MASTER_STATE_PRIMARY:
if len(m.primaries) == 0 {
m.setState(MASTER_STATE_IDLE)
} else {
m.servicePrimary(nil)
}
case MASTER_STATE_PRIMARY_SECONDARY_PENDING:
if len(m.primaries) == 0 {
m.setState(MASTER_STATE_SECONDARY)
m.serviceSecondary(nil)
} else {
m.servicePrimary(nil)
}
default:
nmxutil.Assert(false)
}
}
func (m *Master) findPrimaryIdx(token interface{}) int {
for i, c := range m.primaries {
if c.token == token {
return i
}
}
return -1
}
// Removes the specified primary from the wait queue.
func (m *Master) StopWaitingPrimary(token interface{}, err error) {
m.mtx.Lock()
defer m.mtx.Unlock()
idx := m.findPrimaryIdx(token)
if idx == -1 {
return
}
m.primaries = append(
m.primaries[0:idx], m.primaries[idx+1:len(m.primaries)]...)
}
// Removes the specified secondary from the wait queue.
func (m *Master) StopWaitingSecondary(err error) {
m.mtx.Lock()
defer m.mtx.Unlock()
if m.state == MASTER_STATE_PRIMARY_SECONDARY_PENDING {
m.setState(MASTER_STATE_PRIMARY)
m.serviceSecondary(fmt.Errorf("secondary aborted master acquisition"))
}
}
// Releases the resource and clears the wait queue.
func (m *Master) Abort(err error) {
m.mtx.Lock()
defer m.mtx.Unlock()
// Set state such that there are no waiters. We can't clear the current
// owner of the master; it must release on its own.
switch m.state {
case MASTER_STATE_IDLE:
case MASTER_STATE_SECONDARY:
go m.secondary.Preempt()
case MASTER_STATE_PRIMARY:
case MASTER_STATE_PRIMARY_SECONDARY_PENDING:
m.serviceSecondary(err)
m.setState(MASTER_STATE_PRIMARY)
}
for len(m.primaries) > 0 {
m.servicePrimary(err)
}
}